[FreePascal] is an object pascal compiler written in pascal ! The BeOS API's are mostly exposed as C++ objects. Unfortunately, gcc objects and FreePascal ones don't share the same Application Binary Interface (ABI).
Maybe someone can implement this feature in FreePascal in the future, but he should have a good knowledge of how gcc works...
That is why BePascal use the smallest common denominator : the C calling convention ("cdecl" for pascal users, "__declspec(dllexport)" or "extern "C"" for C users).
For simple functions, it is easy. We can link directly to the system library : a small pascal declaration is enough. For example, the BeOS beep function in Beep.h is declared as follow :
_IMPEXP_BE status_t beep();
The corresponding pascal declaration is :
function beep : TStatus_t; cdecl; external 'be' name 'beep__Fv';
TStatus_t is a pascal declaration of the C type status_s (here TStatus_t is Longint in pascal, in the supportdefs.pp unit).
cdecl is the calling convention
external 'be' tell the linker that the beep function in pascal is a call to a function in the "be" shared object (the linker will link against "libbe.so").
name 'beep__Fv' : this is the real function name in the shared object. Remember that libbe.so is compiled in C++. So, function's names are mangled. To find the decorated names, just search "beep" in libbe.so using [DiskProbe]. The mangled name is beetween the two 00 (in the hex view).
Note: another method to find the mangled function names follows:
- Include the following shell functions in your .profile:
--- cut here ---
function lgrep() {
for libdir in `echo $LIBRARY_PATH|sed "s/%A/./"|tr ':' ' '`; do
for lib in $libdir/*.so ; do
echo "[ $lib ]"
nm $lib|grep $1
done
done
}
function hgrep(){
find /boot/develop/headers -type f -print0 | xargs -0 egrep "$@"
}
--- cut here ---
- Then start a new Terminal and type: "lgrep [function]". Example: "lgrep beep"
As far as i know, object's methods always use an implicit parameter (this in C++ or Self in pascal). This is a pointer to the object instance. using this parameter, methods can work on the right object.
Three steps are required :
The first step is to expose the C++ objects APIS through standard C functions. These function are then exported in a shared object (.so). This is the libbepascal.so file.
Now, we have to import these functions in pascal (see above "Importing functions")
Then we can define pascal objects using these functions to show the BeOS API as pascal objects.
I will use the HideCursor function in BApplication as an example. This function is define as follow in BApplication :
void HideCursor();
The C function that correspond to the HideCursor method is :
void BApplication_HideCursor(BApplication Application)
{
Application->HideCursor();
}
The original function name is prefixed to avoid possible collision with functions in other objects. The first parameter is a pointer to the object instance. This parameter could be compared to Self in pascal or this in C++.
The function's body just call the HideCursor function of the C++ object instance.
To export this function outside the library, this function should be exported using the following directives :
#if defined(__cplusplus)
extern "C" {
#endif
/* code of functions to export */
#if defined(__cplusplus)
}
#endif
This also disable name mangling for exported functions. So, the same name will be used in pascal to import the function.
In BeOS, when the system send a message to an application (mouse or keyboard events for example), objects in the API call special functions. The user should inherits from Be's objects and then override those methods to handle events.
For example, the main feature of BHandler is to handle messages. This is done using the MessageReceived method :
virtual void MessageReceived(BMessage *message)
To receive messages on the pascal side, we need to map this feature in pascal objects. Many steps are involved :
First, we need to override each class that contain at least one hook function. This is mandatory because each hook functions should call some pascal code (the pascal hook function implementation in the pascal class).
Now, we have to call a pascal method in a pascal object from C++. That is a bit harder !
C++ can only call standard pascal functions that use the cdecl calling convention. At startup, the pascal program should install some callback functions in the C++ overrided methods. To handle this, we export some function pointer's variables in the shared object, one for each hook. The C++ overrided methods just call those function's pointers.
in Handler.h, we declare the function type :
typedef void (*BHandler_MessageReceived_hook) (TPasObject PasObject, TCPlusObject message);
in Handler.cpp, we declare the variable in an exported section :
#if defined(__cplusplus)
extern "C" {
#endif
BHandler_MessageReceived_hook Handler_MessageReceived_hook;
#if defined(__cplusplus)
}
#endif
and we call the pointer function from the overrided method :
void BPHandler::MessageReceived_hookCall(BMessage *message)
{
Handler_MessageReceived_hook(GetPasObject(), message);
}
This fonction take a pointer to the pascal object instance as the first parameter. The PasObject field (retrieve by GetPasObject) is setup when a pascal object call the construtor of a BPasObject child (like BPHandler for example). Other parameters are forwarded as is (C++ types).
Now, we have to setup those pointers from pascal code and call right method on right pascal instance.
The first part is quite easy : we have to import variables in the .so. In freepascal, the cvar directive handle this well, so we use it :
var
Handler_MessageReceived_hook : Pointer; cvar; external;
Now, we have to initialize this pointer with a function. This is done in the initialization section.
initialization
Handler_MessageReceived_hook := @Handler_MessageReceived_hook_func;
finalization
Handler_MessageReceived_hook := nil;
Finally, we define the pascal function. This function will call the hook function defined in our pascal class. This is done through a method call on the pascal object instance given in the first parameter. To make the hook function more pascal-ish (aka only pascal types), we have to wrap other C++ objects in the parameters list into pascal one using the Wrap constructor.
procedure Handler_MessageReceived_hook_func(Handler : THandler; aMessage : TCPlusObject); cdecl;
var
Message : TMessage;
begin
try
{$IFDEF DEBUG}
SendText('Hook MessageReceived !');
{$ENDIF}
Message := TMessage.Wrap(aMessage);
try
if Handler <> nil then
Handler.MessageReceived(Message);
finally
Message.UnWrap;
end;
except
on e : exception do
begin
{$IFDEF DEBUG}
SendText(e.Message + 'Handler_MessageReceived');
{$ENDIF}
end;
end
end;
The Wrap constructor is a special constructor introduced in TBeObject (used as the ancestor of all BePascal objects). Other constructors create a pascal object AND the corresponding C++ object. Wrap only initialize a pascal object and use ACPlusObject as the underlying C++ object.