Navigation:  Programming Cookbook > External Interfacing >

Virtual Calls (C++/OLE COM Interface)

Previous pageReturn to chapter overviewNext page

Dolphin can interface directly to objects that obey the C++ virtual calling convention. Obviously this includes C++ objects themselves, but can also include objects implemented in other languages that implement the Microsoft Common Object Model (the basis of OLE), since this is based on the C++ virtual function model. The implementation of such external objects would normally reside in a DLL, or DLLs (under Win32™ there are no special prologs and epilogs for exported functions, so it is always possible to generate a DLL from a class library supplied as a LIB). OLE COM objects can also reside in other executables, and, in the case of Distributed COM, can even reside on remote machines.

In order to interface to C++ objects there needs to be some way to instantiate such objects (or gain access to those which already exist), and then to invoke those objects member functions. These are some of the fundamental features of COM, but we consider the simpler case of interfacing to C++ in DLLs here. We need a Smalltalk class (or classes) to represent those objects in Smalltalk (i.e. to act as a proxy).

For example we might have the simple C++ class:

class CPPBlah

{

      char* m_szName;

public:

      CBlah(const char* szName) { m_szName = strdup(szName); }

      virtual ~CPPBlah() { delete m_szName; }

      virtual const char* getName() { return m_szName; }

      void setName(const char* szName)

              { delete m_szName; m_szName = strdup(szName); }

};

 

We might have an ExternalAddress object pointing at an instance of the C++ class CPPBlah, i.e. the object lives outside the Smalltalk object space, or we might want the C++ object to reside in Smalltalk memory. These are precisely the capabilities of ExternalStructure (which can represent a structure by value or reference), so we could add a subclass called, say, ExternalBlah.

Instantiating C++ objects is achieved by calling either a static member function, or an ordinary dynamic link library function. There is no difference, except that the former is likely to have a long mangled name. At present one must determine the mangled name of the function yourself (e.g. by using "dumpbin /exports" or the linker map file). For example we might have a simple factory function (which could be an exported static member of CPPBlah):

__declspec(dllexport) CPPBlah* __stdcall makeNewBlah(const char* szName)

{

      return new CPPBlah(szName);

}

We could write a method of an ExternalLibrary subclass, BlahLibrary, to invoke this factory function thus:

makeNewBlah: aString

      "Answer a new external C++ Blah object."

      <stdcall: Blah* '_makeNewBlah@4' lpstr>

      ^self invalidCall

 

If we then evaluate the following expression

BlahLibrary default makeNewBlah: 'I'm a new Blah created from Dolphin'

 

We would have an instance of ExternalBlah containing an ExternalAddress object pointing at an instance of the C++ class CPPBlah. Alternatively we could instantiate an ExternalBlah object, and pass that to a library function which constructs a C++ object in that memory (either by calling the constructor directly, or by using the placement syntax of operator new())

Our ExternalStructure subclass, ExternalBlah, is the home for the virtual call methods. We can add additional Smalltalk instance variables to this class as required.

Once we have an object of the appropriate type, then we want to be able to invoke its member functions. These can be either statically bound, or dynamically bound (virtual). The latter are somewhat easier to implement and call, using the same format as an external library call, but with a virtual prefix and specifying a virtual function number (index in the vtbl) instead of a function name or ordinal. Knowledge of the mangled function name is not required, because the function is accessed by offset into the virtual table (this does mean it is very important to get the offset correct, hence this is calculated automatically in the COM add-in package, which generates all external function definitions automatically). We might define the CPPBlah::getName() virtual function as follows:

name

      "Answer a the name of the external C++ Blah object."

      <virtual cdecl: lpstr 1>

     ^self invalidCall

 

The #name message can then be sent to instances of ExternalBlah in the normal way and will answer a string which is a copy of that stored in the referenced C++ object.

Normal (non-virtual) member functions may be supported in future by implementing them in the relevant ExternalLibrary subclass using the thiscall calling convention. For example:

setName: anExternalObject to: aString

      "Set the name of an external C++ Blah object."

      <thiscall: void '_mangle_mangle_setName_mangle@8' lpvoid lpstr>

      ^self invalidCall

 

At present, however, thiscall is not supported (primarily because it is not needed for OLE, as all COM functions must be virtual). The workaround is to ensure that all C++ member functions one might wish to call from Dolphin are declared as virtual, or explicitly declared as with cdecl or stdcall calling conventions.

Ordinary member functions will have to be added to an ExternalLibrary rather than the C++ object's proxy Smalltalk class, because Dolphin needs to be able to locate the functions using GetProcAddress() (for this reason they must also be exported). Furthermore it is necessary to explicitly pass the implicit (in C++) this parameter (being the address from the relevant proxy object). This does make using ordinary member functions considerably less convenient. To mitigate this inconvenience it is suggested that forwarding methods are implemented in the proxy class itself to wrap the external calls.

Static member functions are called in exactly the same way as any other exported dynamic link library functions.

It is suggested that Dolphin's finalization support (see Weak References and Finalization) be used to give the proxy object (e.g. the ExternalBlah instance) a chance to invoke the C++ destructor when the object has no further Smalltalk references. This makes it much easier to synchronize the lifetime of the heap based C++ object with the garbage collected Smalltalk object. We recommend that destructors are always declared as virtual, as this makes it possible to correctly delete a C++ object polymorphically, and, as mentioned, makes it easier to call in a DLL (e.g. from Dolphin).

There is some flexibility over the definition of the proxy class. The virtual call primitive is able to invoke C++ virtual functions against such a class of objects if:

It is a byte object of at least 4 bytes. In this case the primitive assumes that it is the object itself - i.e. the C++ object lives in the Smalltalk memory space.
It is an indirection byte object (i.e. a pointer). ExternalAddress is an example of such a class. In this case the primitive assumes that the objects state is a pointer to the C++ object, which probably lives outside the Smalltalk memory space (i.e. it contains the this pointer). It is not necessary to subclass ExternalAddress to define an indirection class, but see that class to see how to do it. lpvoid return types can be mutated to correctly defined indirection classes by using #becomeA:
It is a pointer object whose first instance variable is as described in (1) or (2), e.g. subclasses of ExternalStructure. This is generally the most powerful and flexible solution.

Normally it is most convenient to add the proxy class as a subclass of ExternalStructure.