Home > Programmer's Guide > Advanced Issues
Shared Libraries
The HB++ language allows easy access to functions located in a shared library using the LoadLibrary and RemoveLibrary functions and the Declare statement. A shared library can be provided by a third party, or written specifically with MetroWorks CodeWarrior™ or GNU C Compiler. You cannot write a shared library with the current version of HB++.
Shared Library Functionality
A library is an executable file, stored in a resource database. But unlike a standard application, a library cannot be launched directly. It provides a table of functions to the system, which other applications can access to call the routines contained in the library. So that the system can distinguish these libraries, they should have the type "libr".
Before being able to use it, a library must be explicitly loaded. This operation is carried out using the LoadLibrary function, which returns a unique identifier called the reference number. This identifier allows the exported library functions to be called, and must be stored in a global variable of type Integer.
Each exported function is identified by a trap number whose value is greater than &HA800. Technically, the 16 least significant bits of this trap number are used as an index in the exported functions table. In addition, the first parameter of all exported functions must be of type Integer, which receives the reference number returned by the system when loading the library. When calling a function in a library, the system uses both the reference number and trap number to uniquely identify the function to call.
Finally, a library must be unloaded once it is no longer needed. This operation is carried out using RemoveLibrary. However, if several applications are simultaneously using the same library, you must check that none of them are still using it before it is unloaded. The system offers no facility for this, so a shared library must have a means of counting the number of its users itself. A simple method is to provide two functions Open and Close, called respectively when loading and unloading, to increase or decrease a global variable. When this variable is zero, this indicates that the library is not being used; the Close function then returns a specific code that indicates to the calling application that the library can be closed.
The following example, from the MathLibSinus sample, shows how to load the MathLib library (some declarations are omitted for simplicity). If you want to know about the internal functionality of this library, you can obtain the source from http://www.radiks.net/~rhuebner/mathlib.html.
Private Declare Function MathLibOpen(ByVal iRef as Integer, ByVal iVersion as Integer) as Integer Trap &HA801
Private iRefNum as Integer
Public Sub MLInit()
Dim e as Integer
iRefNum=LoadLibrary("MathLib","libr","MthL")
e=MathLibOpen(iRefNum,1)
If e<>0 Then Err.Raise 67
End SubIn this function, we begin by loading the library, and we store the reference number returned in a global variable. We then call the MathLibOpen function passing it the reference number, and a parameter indicating the required version. Within the library, this function initializes some internal structures, verifies the version number requested, and increases a global variable that counts the number of users.
The following example shows how to call a function in the library:
Private Declare Sub MathLibSin(ByVal iRef as Integer, ByVal x as Double, ByRef r as Double) Trap &HA805+5 Public Function MLSin(ByVal x as Double) as Double Dim r as Double MathLibSin iRefNum, x, r MLSin=r End Function
In this function, we simply call the external function providing the reference number as the first parameter. It is worth remembering that the result of this function is not returned directly, but indirectly by the intermediary of the last parameter passed ByRef. The reason is that not all compilers use the same convention for returning values of type Double. Consequently, MathLib uses this return mode to ensure compatibility of the library with all the existing development tools.
Finally, the following example shows how to unload the library:
Private Declare Function MathLibClose(ByVal iRef as Integer, ByRef iUseCount as Integer) as Integer Trap &HA802
Public Sub MLClose()
Dim e as Integer, u as Integer
If iRefNum<>0 Then
e=MathLibClose(iRefNum,u)
If e<>0 And u=0 Then RemoveLibrary(iRefNum)
End If
End SubHere, we call the MathLibClose function, still passing the reference number as the first argument, and a pointer to a local variable of type Integer. Within this library, this function decreases the global variable that counts the number of users, and copies its value into the local variable passed in the argument. If this value is null, the library is no longer being used and we call the RemoveLibrary function to unload it.
Declaring external functions and passing parameters
As shown above, declaring shared library functions is carried out using the Declare statement. In most cases, converting parameters accepted by an external function written in C into data types specific to HB++ does not present any particular problems. The documentation of the Declare statement shows a comparison between the most common C data types and HB++ types. Sometimes, certain constructions in the C language can cause a problem: these concern return values of type Double or pointers, and some parameters taking up more that 4 bytes, like structures.
As already mentioned, the different development tools for the Palm OS® platform do not use the same conventions for returning values of type Double. The HB++ compiler, as well as the GCC compliler expect these values in the register pair D0/D1, whilst the CodeWarrior™ compiler (with which Palm OS® is compiled) transforms the return value into a hidden parameter of type pointer. Take the following C function for example:
double MyFunction(int n)
{
return 1.0;
}The code generated by the CodeWarrior™ compiler for this function would correspond in reality to the following C function:
void MyFunction(double * ret, int n)
{
*ret = 1.0;
}If you want to interface with HB++ a shared library compiled with CodeWarrior™, you must take this particularity into account, and declare the function not in the way it is written, but in the way it is compiled, as follows:
Public Declare Sub MyFunction(ByRef ret As Double, ByVal n As Integer) Trap &HA8xx
To avoid unforseen events and ensure the portability of your code, it is recommended to write a shared library in such a way that no function returns a type occupying more than 4 bytes. Thus, you are sure that the code generated corresponds exactly to the code written, and you do not encounter difficulties when converting your declarations from C functions in the Declare statements.
Another problem is posed by the return pointers. Whilst return values are normally stored in the D0 register (or in the D0/D1 register pair for Double values), pointers are always returned in the A0 register. This convention is respected by all Palm OS® platform development tools. To be able to call a C function returning a pointer, you should indicate to the HB++ compiler that the return value should be read into the correct register, by declaring the return value with the Pointer keyword. Refer to the documentation on the Declare statement for more information.
Note that the HB++ language ignores the notion of pointer (at least from the programmer's point of view). When an external function returns a pointer, it must be stored in a variable of type Long, and cannot be used directly. It can however be passed to another external function which will know how to use it. One such example will be illustrated later on in the chapter.
Finally, the last problem is posed by structures. The solution in HB++ is to pass a StreamMemory object to an external function. In fact, only a pointer on the first byte of the memory block contained in the object will be passed to the external function. It is only necessary therefore to fill this memory block, in order that the data it contains will correspond to the C structure expected by the function. Consider the following C structure for example:
typedef struct _Params
{
int x, y;
char sz[32];
long z;
}
Params;To fill a StreamMemory object so that the different values occupy in memory the same location as those in this structure could be done in the following way:
Dim struct As New StreamMemory Dim x As Integer, y as Integer, z As Long, sz As String struct.Write x struct.Write y struct.Write sz, 32 struct.Write z
Note that the specification of a size of 32 bytes when serializing the sz character string, in order that this occupies the correct size, and that the following field is found in the right place. However, it is possibe that for alignment reasons, the different fields in a structure do not occupy consecutive places. In this case, it is necessary to write null bytes in order for the different fields to be in the correct place. Refer to the documentation of your C compiler for more information.
Another frequently encountered case is the case of structures containing pointers to a character string or to another structure. Such a pointer always occupies 4 bytes, whatever the size of the string or structure it is pointing to. You can gain a pointer to any character string or to the data hold by any StreamMemory object using the Lock function. The following example illustrates this; for simplicity, the structure we pass to the external function only contains a single field, of type char * :
Public Declare Sub MyFunction(ByVal iRefNum As Integer, ByRef objStruct As StreamMemory) Trap &HA8xx Public Sub CallMyFunction(ByRef s As String) Dim struct As New StreamMemory Dim addr As Long addr=Lock(s) struct.Write addr MyFunction iRefNum, struct Unlock s End Sub
Note that each call to Lock must be balanced by a call to Unlock.
Memory management
The HB++ execution library contains its own memory allocation routines, and includes notably a Garbage Collector that detects and frees up unused memory blocks. These routines ignore all allocations that could have taken place within external functions, hence certain rules must be respected to avoid conflicts.
The following rules apply to pointers received by external functions:
Writing a shared library
If you want to write a shared library, you will need to read the Palm OS® Developers Kit documentation which contains important information not included here for brevity. The last versions of the CodeWarrior™ compiler contains a wizard that automatically generates the skeleton of a shared library. Finally, you can also refer to the source code of numerous existing external libraries, such as MathLib for example.