Menu

HowBePascalWorks

Olivier Coursière

[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).

Importing functions

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();
  • _IMPEXP_BE is a macro that expand to "__declspec(dllexport)" when you compile a shared object.

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"

Mapping C++ objects to pascal's ones

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.

First step

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.

Handling hooks functions

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 :

The C++ side

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).

The pascal side

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.

constructor and destructor


Related

Wiki: DiskProbe
Wiki: FreePascal