A Fun Windows Hack (Part Two)

| | Comments (6)

If you recall from last time, I showed you the in-memory layout of a COM object. It looked like this:

ClassPtr->vTable->QueryInterface
...AddRef
Release
...

I had asked you if you could think of a way to emulate this in REALbasic.

If you had said "with MemoryBlocks, Modules and the AddressOf operator", then you were absolutely correct.

The ClassPtr is the COM object itself. It's simply a MemoryBlock whose first four bytes points to a vTable of interface functions. Since the caller has already defined the order of the functions, you're able to take advantage of this and lay the function pointers out in memory in the proper location. Any data following the vTable in the ClassPtr is pure gravy -- you can put anything in there you'd like.

When one of the methods is called from the vTable, it is passed in a "this" (or me) pointer. This pointer is to the ClassPtr itself. In this manner, each of the interface method gets a data pointer (as its first parameter) to the class instance itself.

Now that we've got all the information we need, let's see some code!

First, let's create an instance of our COM object. Let's assume (for clarity's sake) that our COM object only implements the IUnknown interface. So we're responsible for adding the following methods (in this order) to satisfy the contract:
[rbcode]
QueryInterface( refid as Integer, ByRef out as Integer ) as Integer
AddRef() as Integer
Release() as Integer
[/rbcode]
So the first thing we need to do is initialize our COM object
[rbcode]
// First, create the memory block for the COM object.
// It's going to be 4 bytes long since it only holds a Ptr
// to the vTable
dim comObject as new MemoryBlock( 4 )

// Now we're going to make the COM object's vTable
// This needs four bytes for each method that we need
// to expose.
dim vTable as new MemoryBlock( 4 * 3 )
[/rbcode]

Now comes the trickier part. We need to add the functions to the vTable itself. Here's where the new AddressOf operator comes in handy. We're going to stick our "callback" methods into a module so that we can use the AddressOf operator to get a function pointer to them. We can then stuff that address into the vTable MemoryBlock.
[rbcode]
// The next thing we need to do is construct the
// vTable for the COM object. We do this by using
// the AddressOf operator and pass in methods declared
// within this module.

// Here's the IUnknown interface that every COM object
// must implement
vTable.Ptr( 0 ) = AddressOf QueryInterface
vTable.Ptr( 4 ) = AddressOf AddRef
vTable.Ptr( 8 ) = AddressOf Release

// Now let's set the COM object's vTable
comObject.Ptr( 0 ) = vTable
[/rbcode]
Don't worry about the structure of the methods themselves -- I'll be getting to that in a little bit. First, we have other things to work on. Since we're using module methods, we're fairly restricted. That means we can't really have multiple instances of our COM object. That's no good -- so let's remove that limitation.

We're going to use a Dictionary to map an RB class to one of our MemoryBlock-made COM objects. Remember, we're given a "this" pointer which is equal to our MemoryBlock's base address. We can use this knowledge to our advantage by storing the address of our comObject MemoryBlock into the Dictionary as the key, and the class instance we want to hook into as the Value. Like this:
[rbcode]
// And set the COM object's extra information so
// that we can locate the RB object we're interested
// in.
dim comObjectPtr as new MemoryBlock( 4 )
comObjectPtr.Ptr( 0 ) = comObject

// Add the class to our object map
mObjectMap.Value( comObjectPtr.Long( 0 ) ) = c
[/rbcode]
At this point, the comObject variable is ready to be handed out to any Win32 API needing an interface instance. Our COM object is complete! So let's turn our attention to the module methods I was talking about earlier.

The first parameter to any of our COM interface-driven methods is always an Integer representing the C++ "this" pointer. Basically, that integer is the base address of our MemoryBlock. We mapped the base pointer to an actual class instance though, so we've got a way to go from our COM object to our RB object easily. Let's assume that we're working with a class named Class1 and it also is implementing our IUnknown interface (which you should model after the methods described above).

All of the module methods we were using the AddressOf operator on are going to look very similar. They're essentially nothing more than proxy methods which forward the request on to the proper RB interface implementor. We're going to pull a class out of the object map, do some sanity checking on it, and forward the method call. Here's how the AddRef method will look:
[rbcode]
Private Function AddRef(this as Integer) As Integer
// Find the object in our map
dim mappedClass as Object = mObjectMap.Lookup( this, nil )
if mappedClass <> nil and mappedClass IsA IUnknown then
return IUnknown( mappedClass ).AddRef
end if

return 0
End Function
[/rbcode]
As you can see, the proxy methods are quite simple to implement. The true guts of the implementation need to happen within your RB class (the RB interface implementor). But once you've got those implemented, then you're set! Voila! Instant COM interface gratification.

As you can see, we've covered the entire process from start to finish. You simply layout the COM object in memory using a MemoryBlock, map the MemoryBlock to an RB class instance -- and this class instance is where you'd put your implementation.

I've created a sample project with a partially working code sample. I say partially working because I didn't want to muck the entire thing up with too much detail. So instead of putting in the "interesting" guts part of the class, I mostly put in Break statements. So when you run the application and press the PushButton, you'll break into the debugger in one of the OS-called COM methods. I made the project using RB2005r2, but it should be possible to use it (perhaps with minor modifications) in earlier versions of REALbasic as well.

6 Comments

I must confess this is advanced for me, but I find it interesting because some times I found some Win32 APIs that required the COM object interface and I didn't know how to use them. Now I see there's a way with RB... but I still don't know how to use it :(

It would be great if you could provide an example of a Win32 API that uses this - don’t know if the autocomplete combo you were working on is too complex or not... maybe a simpler example.

Stuff like this just makes me giddy... but I'm not exactly sure what it's supposed to do. Is this so that you can use COM objects exposed by other applications/dlls, or is it to expose objects within an RB application as COM objects? (Either would be cool, the second would be especially cool.)

Hi Aaron,
For me this is also very complex, i am not a genius like you. The way you describe it is very general and it should be so to make it widely usable but that makes it harder to understand too. So if it's possible take an activex that uses the com interface and fit it in the example and explain it with references to your description. May i suggest the SoActiveX from Open Office so Mac-users and Linux-users can also benefit from your article and we all can have a cross-platform office support, eh.. at least a start for it.
Thanks.
Andre

Have there been any changes to RB since you wrote this that makes COM interfacing easier, and if so is it documented anywhere? I am using RB 2008 v2, and find I can read variables in a COM library, but I am not able to write to them. A VB example file supplied with the COM library can both read and write.

@David -- I'm sorry, but there's not been any work done specifically to make COM easier to work with. A lot of the new language features certainly help if you want to do things the hard way, but the IDE hasn't automated any of it for you. Your best bet is still usually to just write a plugin.

I hate to confess that I understood all the memoryblock hoop dancing. Yikes! The one thing that confuses me is the implementation of COMWrappers.QueryInterface and RBCOMClass.QueryInterface.

1) RBCOMClass.QueryInterface calls DoTheMagicDance, which finds the object in the Dictionary and creates one if it doesn't already exist. COMWrappers already extracts the object from the dictionary. Under what circumstances does RBCOMClass ever need to do the dance again? Doesn't the object have to already exist in the dictionary to even get this far?

2) Couldn't COMWrappers.QueryInterface perform the entire function? Is there really a need for RBCOMClass to implement QueryInterface? Or would an actual implementation do more than just return the pointer?

Could your COMWrappers module form the basis for enhanced built-in COM support? Add some ISA checks in DoTheMagicDance to build the vTable based on which interfaces the object implements? It could really open up COM support for RB developers.

Thanks, Aaron, for all the valuable information you provide to us.
Tim

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.