The GFA-BASIC 32 approach is loosely based on the article series COM in plain C by Jeff Glatt, specifically Part 1 and Part 2. However, in the first step we are not going to use type libraries, we are not going to store the COM object in a DLL, and we are not going to define another COM object that creates the one we define. COM is merely a binary standard; it dictates how to layout an array of function pointers and where to put the address of this array (of function pointers). It also dictates how to add reference counting. To comply to this standard a COM object must at least contain three pointers to pre-defined functions, also known as the IUnknown interface. In GFA-BASIC 32 this could look like this:
// IUnknownBlog.g32 17.07.2011 Debug.Show // Our COM object: an implementation of IUnknown GUID IID_IUnknownImpl = 9578fdab-97cb-4322-99e4-699abd26be1d Type IUnknownImpl lpVtbl As Pointer IUnknownImplVtbl RefCount As Int EndType // VTABLE (an array of function pointers) Type IUnknownImplVtbl QueryInterface As Long AddRef As Long Release As Long EndType Static IUnknownImplVtbl As IUnknownImplVtbl With IUnknownImplVtbl .QueryInterface = ProcAddr(IUnknownImplVtbl_QueryInterface) .AddRef = ProcAddr(IUnknownImplVtbl_AddRef) .Release = ProcAddr(IUnknownImplVtbl_Release) EndWith // First create a heap allocated instance // of our implementation of IUnknownImpl Dim pIUnk As Pointer IUnknownImpl Pointer pIUnk = mAlloc(SizeOf(IUnknownImpl)) Pointer pIUnk.lpVtbl = *IUnknownImplVtbl pIUnk.RefCount = 1 Dim obIUnk As Object {V:obIUnk} = V:pIUnk Trace obIUnk Dim o As Object // assign to other Set o = obIUnk // AddRef() call // two calls to Release /*** END ***/ GUID IID_IUnknown = 00000000-0000-0000-c000-000000000046 Global Const E_NOTIMPL = 0x80004001 Global Const E_NOINTERFACE = 0x80004002 Declare Function IsEqualGUID Lib "ole32" (ByVal prguid1 As Long, ByVal prguid2 As Long) As Bool Function IUnknownImplVtbl_QueryInterface( _ ByRef this As IUnknownImpl, riid%, ppv%) As Long Naked Trace Hex(*this) {ppv} = Null If IsEqualGUID(riid, IID_IUnknownImpl) || _ IsEqualGUID(riid, IID_IUnknown) {ppv} = *this IUnknownImplVtbl_AddRef(this) Return S_OK EndIf Return E_NOINTERFACE EndFunc Function IUnknownImplVtbl_AddRef(ByRef this As IUnknownImpl) As Long Naked Trace Hex(*this) this.RefCount++ Return this.RefCount EndFunc Function IUnknownImplVtbl_Release(ByRef this As IUnknownImpl) As Long Naked Trace Hex(*this) this.RefCount-- If this.RefCount == 0 Then ~mFree(*this) Return this.RefCount EndFuncCopy it to a new GFA-BASIC 32 application and save as IUnknownBlog.g32. Try to run it; it should compile and run flawlessly. Our first minimalist COM object/class has been defined and is up and running. Of course it doesn't do anything, but the we have an object that integrates with GFA-BASIC 32 and fully complies to the COM binary standard.
Assign to Object
Let us take a brief look at the code. Since this project isn't meant for the beginner, I'll walk you through it in big steps.
The Object data type holds a pointer to a piece of memory of at least 4 (lpVtbl) bytes. Mostly it contains another integer for a reference count. In GFA-BASIC 32 the Ocx variables always define their count in the second slot, we will use that as well.
To put a memory address in an Object variable we usually use Set obj2 = obj2. GFA-BASIC 32 checks for two proper COM object types at compile-time. Since we have mAlloc-ed address only this syntax wouldn't work. We must write it to Object directly. For the same reason we set the RefCount to 1 by hand.
The array of functions (VTABLE) is stored in a Type and shared with all instances of our custom COM object. Therefor a static (global) variable is used and initialized once. Each new COM object should hold the vtable-address in its lpVtbl member.
_QueryInterface, _AddRef, and _Release
The application COM functions _QueryInterface, _AddRef, and _Release are never called directly, but always by GFA or another COM object. They clutter up our application code and are in fact just boiler plate code. We must do something about that, for instance put them in a $Library. Also note the Naked attribute. These functions are never executed in the context of the application and need no TRACE code and Try/Catch-exception handler. They should be as naked as possible to gain the best performance.
The Trace *this commands in the code are to verify the address passed. Also note the type of the this pointer. COM passes the address of the COM object by reference and by adding the correct type into the function declaration we can access its members directly. All other parameters are simple placeholders for addresses we don't use or pass on.
In the next part we will implement an IDispatch object and try to integrate the COM code more into GFA-BASIC 32.
Great stuff - thank you !
ReplyDeleteHarald