Object Pascal Port of ICU Wrapper
by
Milan Marusinec



Creating Direct Class Wrapper for ICU library
(Object Pascal Port)

by Milan Marusinec © 2007

Introduction
Direct Class Wrapper is a sophisticated binding method for creating C++ class library ports with identical API, researched by me in the first months of the year 2007 when working on ICU4PAS port project.

This method is general, and can be used to wrap completely any C/C++ shared library into the Object Pascal language. In the following explanation I am focusing on ICU, but same method can be used on any other project.

On the beginning, there was the ICU 3.6 library with it's 12 megs of source code in C/C++ and a wish to have it's whole functionality available in Object Pascal. How to do that ? There are essentialy two methods - native port or bindings wrapper. The first choice of porting natively can seem to be the best pascal solution - most portable and without any external code dependencies. Projects like
PasJPeg and PasZLib proved to be a great benefit for pascal community. Native port of ICU is doable. But frankly, to port 12 megs of C/C++ would take a year and the result would be error prone. Also there is a new release of ICU each year, so in the time of finishing the native port, it would be already outdated. Recognizing this starting conditions, creation of bindings wrapper becomes more reasonable. However, binding ports are not as interesting as native ports because they don't allow the possibility of static linking doing that kind of solution less portable and more run-time problematic. But in the case of ICU, it seemed to be a suitable solution. ICU library itself is in the binary form distributed as a shared library, so making a wrapper around it for pascal sounded good.
Creating the bindings wrapper

Well, after deciding to produce a wrapper, there left only 3 megs of source code - the complete ICU API in C & C++ headers. The C part of the API, that was easy - just elementary bindings with the external keyword, really not worth to mention it here more. But the challenge, that was the C++ Class API which is the majority of ICU and provides the most interesting functionality (like Layout engine). So the question was, how to bind to C++ classes and provide the same API interface in Object Pascal ? In some existing approaches, pascal authors use to create a new classes with methods wich are different or similar to original implementation. Then these new classes in the end call the C library functions through the elementary external linking mechanism. This kind of class-wrapping can be used on external C/C++ libraries, which export just C API functions. Another approach called "Flattening" exists, which in the end also uses bindings to C functions. To solve the requirement for direct C++ classes calls bindings, let's first look, what's inside the C++ shared library (DLL on Windows and SO on Linux).

Openning the icuuc36.dll with PE resource editing program we can see in the export table traditional C function calls like u_isdigit_3_6.

There are also a C++ function calls like:

?append@UnicodeString@icu_3_6@@QAEAAV12@PB_WHH@Z

Which in demangled state mean:

UnicodeString& UnicodeString::append(wchar_t const*, int, int );

This mangled function call name is what we need to call the C++ class code. The specific issue by this kind of calls is the way, by which the pointer to the already initialized class instance gets in, as a part of the calling convention. On the assembler level, MS Visual C++ uses ecx register and GCC passes the class instance pointer as a first parameter on the stack. Thus before fetching such a call, we need to have a properly initialized instance of the class, whose method we want to call. So before revealing, how to make such a calls, we must firstly figure out, how in pascal can we obtain the properly initialized instances of the C++ classes.

Discovering the content of the DLL library we can see, there are function call names for it's classes constructors and destructors. In the following explanation we focus on UnicodeString ICU class. One of the constructors for UnicodeString class has a form:

??0UnicodeString@icu_3_6@@QAE@XZ

This is the default constructor of UnicodeString class and takes no parameters. C/C++ compilers know, how to create constructor and destructor calls in the final executable. Pascal compilers know that not, but there is the asm block directive, by using which we can simulate the behaviour of the C/C++ compiler.

Now, we will try to create and properly initialize the UnicodeString class instance in pascal, so it could be passed as a pointer (self/this) to the C++ library class method calls. Following code does a half of that job:

program wrapper01;



const US_STACKBUF_SIZE = 7;



type

 uint16_t  = Word;

 int32_t   = Longint;

 UChar     = widechar;

 UChar_ptr = ^UChar;



 UnicodeString = class

   fLength   ,

   fCapacity : int32_t;

   fArray    : UChar_ptr;

   fFlags    : uint16_t;



   fStackBuffer : array[0..US_STACKBUF_SIZE - 1 ] of UChar;



  end;



procedure UMemory_New; external 'icuuc36.dll'

name '??2UMemory@icu_3_6@@SAPAXI@Z';



procedure UnicodeString_UnicodeString; external 'icuuc36.dll'

name '??0UnicodeString@icu_3_6@@QAE@XZ';



var

 str : UnicodeString;



BEGIN

 asm

  push $20

  call UMemory_New

  add  esp ,4

  mov  dword ptr [str] ,eax



  mov  ecx ,dword ptr [str]

  call UnicodeString_UnicodeString



 end;



END.

The code above really works. Try to cut and paste it into your Delphi and see. However, there is one problem which we are approaching. As I said, it is just a half of the job of creating and properly initializing the C++ class instance. So far, we have called the UMemory::new() operator and the UnicodeString::UnicodeString() class default constructor. If you will inspect the content of the str variable now, you will see that:

fLength is "0",
fCapacity is "7",
fArray points to fStackBuffer (+18 from pointer(str))
and
fFlags is "2".

No exception, balanced stack and presence of above values in str's fields is evidence, we have been successfull in creating the C++ class instance. Now we should finish the initialization part of our C++ behaviour simulation. The call to the C++ class constructor did all the initialization job except of setting up the pointer to the virtual method table. So we should continue with code like this:

asm

 mov eax ,dword ptr [str]

 mov dword ptr [eax] ,offset icu_3_6::UnicodeString::'local vftable'

 	

end;

But we can't. In pascal, it is not possible to externally link to the data in DLL. That's the reason, why also all constants have to be redefined in pascal, when creating binding ports. So what's next ? Are we in a blind alley ? Answer is no, but we had to get here to understand, why certain design decisions must be made when creating a fully functional Direct Class Wrapper.

Helper Class Library in C++

In the previous part of this article it was shown, we can't properly initialize C++ class instances by simulating the C++ behaviour directly from the native pascal code, because we can't access external data in DLL, which we need for this purpose (VMT setup).

Another solution to this problem has to take a place. We will create a helper class library in C++ whose purpose will be a creation and deletion of the ICU C++ class instances. From this helper class library we will export C function call names returning the ready made pointers of the ICU C++ class instances. In the ICU4PAS project, this helper library is located in the /icu4pas.lib subdirectory. After compiling this C++ code, there will be one additional shared library file, which should be on a path of application using the ICU4PAS interfaces. You could argue against it at this point, but this one more external DLL/SO is a must if we want to create a fully working Direct Class Wrapper.

The code in C++ for the icu4pas helper library will be as follows:
(simplified to
UnicodeString)

#include <unicode/unistr.h>



#define ICU4PAS_API extern "C" __declspec(dllexport)

#define EXP __cdecl

#define VER(name) name ## _3_6



typedef UnicodeString* UnicodeString_ptr;



ICU4PAS_API UnicodeString* EXP VER(UnicodeString_New_v1)

()

{

	return new UnicodeString();

}



ICU4PAS_API void EXP VER(UnicodeString_Delete)

(UnicodeString_ptr& obj )

{

	delete obj;

	obj=0;

}



ICU4PAS_API UnicodeString EXP VER(UnicodeString_Constructor_v1)

()

{

	return UnicodeString::UnicodeString();

}



ICU4PAS_API void EXP VER(UnicodeString_Destructor)

(UnicodeString& obj )

{

	obj.UnicodeString::~UnicodeString();

}

Now, we are able to properly create and initialize ICU C++ class instances.
The new version of pascal code will look like this:

program wrapper02;



const US_STACKBUF_SIZE = 7;



type

 uint16_t  = Word;

 int32_t   = Longint;

 UChar     = widechar;

 UChar_ptr = ^UChar;



 UnicodeString_ptr = ^UnicodeString;

 UnicodeString = object

   __vtable : pointer;

 

   fLength   ,

   fCapacity : int32_t;

   fArray    : UChar_ptr;

   fFlags    : uint16_t;



   fStackBuffer : array[0..US_STACKBUF_SIZE - 1 ] of UChar;



  end;



function UnicodeString_New : UnicodeString_ptr; cdecl;

external 'icu4pas36.dll' name 'UnicodeString_New_v1_3_6';



procedure UnicodeString_Delete(var obj : UnicodeString_ptr ); cdecl;

external 'icu4pas36.dll' name 'UnicodeString_Delete_3_6';



procedure UnicodeString_Constructor(var return : UnicodeString ); cdecl;

external 'icu4pas36.dll' name 'UnicodeString_Constructor_v1_3_6';



procedure UnicodeString_Destructor(var obj : UnicodeString ); cdecl;

external 'icu4pas36.dll' name 'UnicodeString_Destructor_3_6';



var

 str : UnicodeString_ptr;



BEGIN

// Creation of UnicodeString C++ class instance on heap

 str:=UnicodeString_New;



// Deletion of UnicodeString C++ class instance on heap

 UnicodeString_Delete(str );



END.

Notice the change of class type keyword to object type keyword in this new pascal code. The purpose for this is to keep the new pascal C++ Class API translation as consistent and error free as possible. Since we created a C++ class instance (with call to the UnicodeString_New), we really don't need all the stuff, which comes in Object Pascal by implicitely deriving from TObject. If our new interfaced UnicodeString type would still be a class type, a call like size:=str.InstanceSize; would compile, but at run-time it would raise an exception due to an attempt to access unexisting data at negative -40 offset of str's VMT table. We could try to additionaly enable the pascal's class type features by calling the TObject.InitInstance method, but that would overwrite an already properly assigned value of VMT pointer table at class's offset 0. By using the object type, this way we prevent user from trying to produce a calls like that, because compiler would complain about it. Another rationale for the use of object types instead of class types is achieving a better binary compatibility with C++. It is known that pascal's class types cannot be instantiated on a stack (or data segment), while object types can, which is more similar behaviour like in C++. And in this ICU4PAS port, there were really some cases, where class instances on stack (not on heap) were required. (Many of the ICU classes return the whole Locale class instances instead of just references to them for example.) This explains also, what is the purpose of the UnicodeString_Constructor & UnicodeString_Destructor functions in our C++ helper class library example above. So if we want to create an UnicodeString object on stack (or data segment), we do it this way:

...



var

 str : UnicodeString;



BEGIN

// Creation of UnicodeString C++ class instance on stack (or data segment)

 UnicodeString_Constructor(str );



// Deletion of UnicodeString C++ class instance on stack (or data segment)

 UnicodeString_Destructor(str );



END.
Adding functionality to wrapped C++ classes by calling their methods
So far we have demonstrated, how to create and destroy the instance of ICU C++ UnicodeString class - either on heap or on stack. Now we will proceed by introducing the "special bindings mechanism", by which we will directly call the class functions in ICU C++ shared libraries. There are essentially two types of calls, we have to figure out. The static and virtual calls. In both cases, we will simulate the C++ compiler behaviour using the asm block to feed parameters in, make the call and get the result out. Note that we cannot use one of the predefined pascal's calling convention like cdecl or stdcall, because none of them is compatible with MS Visual C++ calling convention, which is passing the value of self (this) pointer in the ecx register.
Static calls

For the purpose of demonstrating the most basic principle of static call binding in Direct Class Wrapper, we will use the setTo method of UnicodeString class, which is in C++ defined as:

UnicodeString &setTo(UBool isTerminated,

                     const UChar *text,

                     int32_t textLength);

Then the pascal code, which directly binds to this C++ function will be:

program wrapper03;



...



function UnicodeString__setTo_v7(

          isTerminated : UBool; 

          text : UChar_ptr; 

          textLength : int32_t ) : UnicodeString_ptr;

external 'icuuc36.dll'
name '?setTo@UnicodeString@icu_3_6@@QAEAAV12@CPB_WH@Z';



var

 str ,ref : UnicodeString_ptr;



 text : WideString;

 from : UChar_ptr;



BEGIN

// Creation of UnicodeString C++ class instance

 str:=UnicodeString_New;



// Assigning some text to UnicodeString class instance

 text:='Testing direct static call binding ...';

 from:=@text[1 ];



 asm

  mov  eax ,-1      // textLength

  push eax



  mov  eax ,[from ] // text

  push eax



  mov  eax ,1       // isTerminated

  push eax



  mov  ecx ,str     // @self (this)

  call UnicodeString__setTo_v7



  mov  [ref] ,eax   // result:=UnicodeString_ptr



 end;



// Deletion of UnicodeString C++ class instance

 UnicodeString_Delete(str );



END.
Virtual calls

Binding to C++ virtual class functions is almost the same as static. The only difference is in the call instruction, which goes to the address retrieved from the class VMT entry for the desired virtual function. So if setTo method was virtual, then instead of:

  call UnicodeString__setTo_v7

in the code of wrapper03 example above, we would write:

  mov  eax ,[ecx]

  mov  edx ,vmt_entry_offset

  call dword ptr [eax + edx]  

The vmt_entry_offset is an integer value of the offset of the virtual function call entry in the VMT of the C++ class we are wrapping around, and can be properly calculated with technique I call "Virtual Method Table Reconstruction" (VMTR). We will show now, how to do a VMTR for one another class from ICU library - the BreakIterator. UnicodeString has also a few virtual functions, but BreakIterator perfectly shows the difference in methods of constructing the order of VMT's by various C++ compilers, in our case MS Visual C++ on Windows and GCC 4.0 on Linux.

Inheritance diagram for BreakIterator:

  UMemory 

   |

  UObject (2 virtual functions) 

   |  

  BreakIterator (17 virtual functions)

Virtual Method Table Reconstruction for BreakIterator in pascal code is:

const

 NE = 4; // vtable next entry offset

   

// Virtual Method Table Reconstruction for UObject class

{$IFDEF ICU_MSVC }

 vtable_UObject_Destructor = 0;

{$ENDIF }



{$IFDEF ICU_GCC4 }

 vtable_UObject_Destructor = NE;

{$ENDIF }



 vtable_UObject_getDynamicClassID = vtable_UObject_Destructor + NE;

 vtable_UObject__LastEntry = vtable_UObject_getDynamicClassID;



// Virtual Method Table Reconstruction for BreakIterator class

{$IFDEF ICU_MSVC }

 vtable_BreakIterator_operator_is_equal = vtable_UObject__LastEntry + NE;

 vtable_BreakIterator_clone = vtable_BreakIterator_operator_is_equal + NE;

 vtable_BreakIterator_getText = vtable_BreakIterator_clone + NE;

 vtable_BreakIterator_getUText = vtable_BreakIterator_getText + NE;

 vtable_BreakIterator_setText_v2 = vtable_BreakIterator_getUText + NE;

 vtable_BreakIterator_setText_v1 = vtable_BreakIterator_setText_v2 + NE;

 vtable_BreakIterator_adoptText = vtable_BreakIterator_setText_v1 + NE;

 vtable_BreakIterator_first = vtable_BreakIterator_adoptText + NE;

 vtable_BreakIterator_last = vtable_BreakIterator_first + NE;

 vtable_BreakIterator_previous = vtable_BreakIterator_last + NE;

 vtable_BreakIterator_next_v2 = vtable_BreakIterator_previous + NE;

 vtable_BreakIterator_next_v1 = vtable_BreakIterator_next_v2 + NE;

 vtable_BreakIterator_current = vtable_BreakIterator_next_v1 + NE;

 vtable_BreakIterator_following = vtable_BreakIterator_current + NE;

 vtable_BreakIterator_preceding = vtable_BreakIterator_following + NE;

 vtable_BreakIterator_isBoundary = vtable_BreakIterator_preceding + NE;

 vtable_BreakIterator_createBufferClone = vtable_BreakIterator_isBoundary + NE;

{$ENDIF }



{$IFDEF ICU_GCC4 }

 vtable_BreakIterator_operator_is_equal = vtable_UObject__LastEntry + NE;

 vtable_BreakIterator_clone = vtable_BreakIterator_operator_is_equal + NE;

 vtable_BreakIterator_getText = vtable_BreakIterator_clone + NE;

 vtable_BreakIterator_getUText = vtable_BreakIterator_getText + NE;

 vtable_BreakIterator_setText_v1 = vtable_BreakIterator_getUText + NE;

 vtable_BreakIterator_setText_v2 = vtable_BreakIterator_setText_v1 + NE;

 vtable_BreakIterator_adoptText = vtable_BreakIterator_setText_v2 + NE;

 vtable_BreakIterator_first = vtable_BreakIterator_adoptText + NE;

 vtable_BreakIterator_last = vtable_BreakIterator_first + NE;

 vtable_BreakIterator_previous = vtable_BreakIterator_last + NE;

 vtable_BreakIterator_next_v1 = vtable_BreakIterator_previous + NE;

 vtable_BreakIterator_current = vtable_BreakIterator_next_v1 + NE;

 vtable_BreakIterator_following = vtable_BreakIterator_current + NE;

 vtable_BreakIterator_preceding = vtable_BreakIterator_following + NE;

 vtable_BreakIterator_isBoundary = vtable_BreakIterator_preceding + NE;

 vtable_BreakIterator_next_v2 = vtable_BreakIterator_isBoundary + NE;

 vtable_BreakIterator_createBufferClone = vtable_BreakIterator_next_v2 + NE;

{$ENDIF }



 vtable_BreakIterator__LastEntry = vtable_BreakIterator_createBufferClone;

As you can see, there are some differences in how C++ compilers construct VMT tables. Note the different order of entries for setText and next virtual functions. Up to my findings, the rules of building VMT per compiler is following:

MS Visual C++

    1. The first user defined virtual function starts at offset 0.
    2. Virtual function entries are layed out in the same order as they appear in source code, except for overloaded functions.
    3. Overloaded virtual functions are layed out in reverse order one after another, starting at the position of occurence of the first overloaded function.

GCC 4.0

    1. The first user defined virtual function starts at offset 4.
    2. Virtual function entries are layed out in the same order as they appear in source code including the overloaded ones.
Glueing it all together - creating Direct Class Wrapper for ICU

Above mentioned techniques is all we need to create a fully functional Direct Class Wrapper, which will provide the same API interfaces, as in original C++ class library. Let's define in Object Pascal the complete wrapping object for the BreakIterator class:

BreakIterator_ptr = ^BreakIterator;

BreakIterator = object(UObject )

 private

  fBufferClone : UBool;



  actualLocale : array[0..ULOC_FULLNAME_CAPACITY - 1 ] of char;

  validLocale  : array[0..ULOC_FULLNAME_CAPACITY - 1 ] of char;



  __align01 : array[0..2 ] of char;



 public

  function  operator_is_equal    (rhs : BreakIterator_ptr ) : UBool; {virtual;}

  function  operator_is_not_equal(rhs : BreakIterator_ptr ) : UBool; //inline;



  function  clone : BreakIterator_ptr; {virtual;}



 {function  getDynamicClassID : UClassID; virtual; /inherited}



  function  getText : CharacterIterator_ptr; {virtual;}

  function  getUText(

             fillIn : UText_ptr; 

             status : UErrorCode_ptr ) : UText_ptr; {virtual;}



  procedure setText(text : UnicodeString_ptr ); overload; {virtual;}

  procedure setText(

             text : UText_ptr; 

             status : UErrorCode_ptr ); overload; {virtual;}



  procedure adoptText(it : CharacterIterator_ptr ); {virtual;}



  function  first : int32_t; {virtual;}

  function  last : int32_t; {virtual;}

  function  previous : int32_t; {virtual;}

  function  next : int32_t; overload; {virtual;}

  function  current : int32_t; {virtual;}



  function  following(offset : int32_t ) : int32_t; {virtual;}

  function  preceding(offset : int32_t ) : int32_t; {virtual;}



  function  isBoundary(offset : int32_t ) : UBool; {virtual;}



  function  next(n : int32_t ) : int32_t; overload; {virtual;}



  function  createBufferClone(

             stackBuffer : pointer;

             BufferSize : int32_t_ptr;

             status : UErrorCode_ptr ) : BreakIterator_ptr; {virtual;}



  function  isBufferClone : UBool; //inline;



  procedure getLocale(

             var return_ : Locale; 

             type_ : ULocDataLocaleType; 

             status : UErrorCode_ptr );



  function  getLocaleID(

             type_ : ULocDataLocaleType; 

             status : UErrorCode_ptr ) : PChar;



 end;



{ GLOBAL PROCEDURES }

procedure BreakIterator_Delete(var obj : BreakIterator_ptr ); cdecl;



procedure BreakIterator_Destructor(var obj : BreakIterator ); cdecl;



function  BreakIterator_createWordInstance(

           where : Locale_ptr; 

           status : UErrorCode_ptr ) : BreakIterator_ptr; cdecl;



function  BreakIterator_createLineInstance(

           where : Locale_ptr; 

           status : UErrorCode_ptr ) : BreakIterator_ptr; cdecl;



function  BreakIterator_createCharacterInstance(

           where : Locale_ptr; 

           status : UErrorCode_ptr ) : BreakIterator_ptr; cdecl;



function  BreakIterator_createSentenceInstance(

           where : Locale_ptr; 

           status : UErrorCode_ptr ) : BreakIterator_ptr; cdecl;



function  BreakIterator_createTitleInstance(

           where : Locale_ptr; 

           status : UErrorCode_ptr ) : BreakIterator_ptr; cdecl;



function  BreakIterator_getAvailableLocales(

           count : int32_t_ptr ) : Locale_ptr; cdecl; overload;



function  BreakIterator_getAvailableLocales : 

           StringEnumeration_ptr; cdecl; overload;



function  BreakIterator_getDisplayName(

           objectLocale : Locale_ptr;

           displayLocale : Locale_ptr;

           name : UnicodeString_ptr ) : UnicodeString_ptr; cdecl; overload;



function  BreakIterator_getDisplayName(

           objectLocale : Locale_ptr;

           name : UnicodeString_ptr ) : UnicodeString_ptr; cdecl; overload;



function  BreakIterator_registerInstance(

           toAdopt : BreakIterator_ptr;

           locale : Locale_ptr;

           kind : UBreakIteratorType;

           status : UErrorCode_ptr ) : URegistryKey; cdecl;



function  BreakIterator_unregister(

           key : URegistryKey; 

           status : UErrorCode_ptr ) : UBool; cdecl;

In pascal version, it is also derived from UObject class, which is:

UObject_ptr = ^UObject;

UObject = object

 private

  __vtable : pointer;



 public

  function  getDynamicClassID : UClassID; {virtual;}



 protected

 { Special bindings mechanism }

  procedure dynlib_class_proc(

             method : pointer ); overload;

  ...

  function  dynlib_class_byte(

             method : pointer; 

             p1 ,p2 : int32_t ) : int8_t; overload;

  ...

  function  dynlib_class_long(

             method : pointer; 

             p1 ,p2 ,p3 : int32_t ) : int32_t; overload;

  ...

  procedure class_virtual_proc(

             vtable : int32_t; 

             p1 : int32_t ); overload;

  ...

  function  class_virtual_long(

             vtable : int32_t; 

             p1 : int32_t; p2 : int8_t ) : int32_t; overload;

  ...

  function  class_virtual_double(

             vtable : int32_t ) : double; overload;

  ...

 

 end;

This way we have a coherent BreakIterator object, which we can use as usual in pascal code without knowing nothing about details of it's internal bindings implementation. We can even derive a new objects with extended data fields or a new functionality.

In this ICU4PAS project, instead of writing assembler binding statements for each method, I decided to create a sublayer with methods like dynlib_class_xxx or class_virtual_xxx. These sub-methods, declared in UObject, comprise the core of the "special bindings mechanism" of the ICU4PAS Direct Class Wrapper. Using this kind of "gateway" also simplified the porting of more than 2000 of method calls contained in original ICU to about 100 routines written in assembler simulating the calling convention of particular compiler. For more details on this topic please study the source code of ICU4PAS library.

Overriding virtual methods in wrapped object of C++ library class
It's been already said, you can use objects of Direct Class Wrapper as any other native objects. That means, you can extend those objects by deriving a new objects with additional data fields and methods. Sounds good, but experienced programmer will ask now, how can be overriden a method declared in shared DLL of another programming language ? I solved this kind of problem by deriving a new subclass in the icu4pas C++ helper library from the class, I wanted to provide a new virtual functions implementation. For example, when porting the Layout Demo in the native pascal code, there was the LEFontInstance class which functionality had to be overriden. So I created LEFontInstance_interface class derived from LEFontInstance in C++ helper library. Then I provided a callback methods for all virtual functions of the inherited class. These callbacks can be configured at runtime from pascal code with the implement_method() function call. For more details on this topic please study the source code of ICU4PAS library.
Conclusion

In this article I described a method of creating an Object Pascal bindings port for ICU C/C++ class library. I call this method of porting The Direct Class Wrapper. This method is general. It can be used in wide variety of porting projects. I have searched the internet and didn't found any complete aproaches like this, so I assume I have researched this method and used it for the first time.

Here is my definition of Direct Class Wrapper:


Direct Class Wrapper is a sophisticated binding method, providing identical object oriented programming interface API and possibilities as the original work in original programming language.

Direct Class Wrapper consist of:

    1. Helper class library in source language, whose purpose is creation and deletion of objects of source library. Also classes for overriding in port language should be defined here.

    2. Special bindings mechanism - a way of simulating the source language calling convention on class methods.

    3. Virtual Method Table Reconstructions(VMTR).
      All objects with virtual methods must have this.

    4. Elementary bindings - all class non-virtual methods and also global functions are linked by traditional external binding mechanism provided by port language compiler.

    5. Native ported code. Some parts of the source project must be completely native ported.
      In C/C++ these are for example all macros and inline methods.

Direct Class Wrapper

    • Is compiler specific - for each target external shared library compiled in particular source project compiler, there must be an individual implementation of special bindings mechanism and individual set of link names. Also VMTR is compiler specific.

    • Can provide a complete cross-platform solutions.

    • Uses native inheritance on wrapped objects.

The methodology mentioned in this article and also the ICU4PAS project itself is the thing I consider to be a Recommended practice for creation of Direct Class Wrapper projects. If you have any comments on this topic, please mail me. I can provide advice to individuals decided to use this method in some other projects.

What's next ?
While working on the ICU4PAS project I realized, there is a potential possibility of creating a Direct Class Wrapper without the need of external linkage to shared libraries. The idea is based on embedding the C++ object files while using the methodology described above. However I have not tried this approach, because it seems to be difficult to compile a working version of ICU with Borland C++, whose OMF object files are needed for this.


05.05.2007
Milan Marusinec


Copyright © 2007, Milan Marusinec alias Milano