SmalltalkResources.gif (4623 bytes)

Implementing COM Interfaces in Visual Smalltalk

Overview

When learning about COM interfaces in Visual Smalltalk, it helps to keep in mind the two roles of user and provider of a COM component.  The user of a COM component accesses services through the interface.  The provider of those services receives requests and makes responses through the interface.

To implement a COM interface in Visual Smalltalk, you must create and coordinate four classes that handle the two sides of the interface:

1.    OLEInterface

2.    OLEInterfacePointer

3.    OLEInterfaceImplementation

4.    OLEObject

If you wish only to use a COM component, you need implement only the first two subclasses. 

Note that these instructions are for calling and implementing "custom" (non-automation) COM interfaces.  Visual Smalltalk also gives you the ability to call and implement automation interfaces (or dispinterfaces... those based on IDispatch) but the procedure for setting these up is somewhat different.

Using COM Interfaces from Visual Smalltalk

By "using a COM interface," I mean invoking a COM component that is implemented by somebody else (either in or out of VSW).  You're not interested in providing a service, only in using it, so you're dealing only with the external side of the interface.  Learning how to use a COM interface from VSW will make available to you many of the COM components delivered by Microsoft, such as the MS XML DOM, as well as by other third parties.  It's also (relatively) straightforward, so it's a good place to cut your teeth.

To use a COM interface, you need to implement only two of the four classes in the VSW OLE Interface framework:  the OLEInterface and OLEInterfacePointer subclasses.  The OLEInterfacePointer subclass handles the details of the vtable, parameter conversions, and so on.  The OLEInterface subclass is the one you deal with to use the interface from Smalltalk.  You can think of them as the private and public faces of the interface.

Subclassing OLEInterfacePointer

Your subclass of OLEInterfacePointer defines the vtable for your interface and handles the conversion of Smalltalk objects into C-style pointer objects.  It is the crucial class in your COM interface.

1.    Create a subclass of OLEInterfacePointer.

2.    Implement initialization code to set the class’s IID.  I do this by adding an #initialize class method which invokes super>>iid:.  You can define the IID as a constant in a pool or get it from your OLEInterface subclass.

3.    Define the vtable for the COM interface.  You do so by implementing Smalltalk methods according to a specified naming convention.  The names you choose for these methods determine the method names you’ll use in the other three subclasses that you’ll write.  For a COM method named Mmm that takes three parameters, define the vtable entry as follows:

·       You need a low-level interface method, similar to a DLL interface method in subclasses of DynamicLinkLibrary, to specify the parameters of the API call.  You can name it Mmm:_:_: or invokeMmm:_:_: or primitiveMmm:_:_: .  The former is suitable if the parameters do not require any conversion before passing to COM; otherwise, use one of the latter two method names.  The integer number you specify in the <ole:nn .. call determines the vtable slot of the method (see the VSW document for details on the syntax).

·       If the parameters require conversion of Smalltalk objects into C-style pointer objects, name your low-level method invokeMmm:_:_: or primitiveMmm:_:_: and write a wrapper method named Mmm:_:_: to do the conversion and then relay the parameters through to the low-level API method.

Subclassing OLEInterface

Your subclass of OLEInterface is what you use in Smalltalk to talk to the COM component, regardless of where the component gets served (i.e., Smalltalk or elsewhere).  It allocates memory for parameters and creates value references.  Think of it as the public representation of the interface, where the OLEInterfacePointer subclass is the private representation..

1.    Create a subclass of OLEInterface.

2.    I usually define a class method here to answer the IID, so I can refer to it from the other interface classes without setting up a pool.Implement initialization code to set the class’s IID.  I do this by adding an #initialize class method which invokes super>>iid:.

3.    For each COM method in the vtable, add an instance method to invoke the corresponding method in the OLEInterfacePointer subclass.  For example, to implement a COM interface method named “Mmm” that takes three parameters, you would define a method named Mmm:_:_: that relays the message to the OLEInterfacePointer subclass.

Specifying the Vtable

When defining an OLEInterfacePointer subclass, you’ll need to know the layout of the vtable for the COM interface.

For interfaces that you are implementing in Smalltalk, you make this up yourself.  Because  your interface inherits directly from IUnknown (as all COM interfaces do), you have to start at index 3, to leave room in the vtable for the 3 methods in IUnknown.  If you are creating a dual interface that supports IDispatch, you have to start at index 7 (I think).

For interfaces implemented by someone else, you’ll have to find some source code or documentation that describes the vtable.  The IDL (Interface Description Language) definition of the interface or a type library (either in a .TLB file or embedded in an executable's resources) is best, but you can also usually figure it out from a .h file too.  If the interface has a type library that is registered in the windows registry, you can use the T4OLEVtableAnalyzer to print out a listing of the interfaces it contains, along with each method name and vtable offset.  Simply invoke it as follows:

            T4OLEVtableAnalyzer describeLibid: myGUID.

Serving COM Interfaces from Visual Smalltalk

Serving a COM interface is where you implement the actual behaviour, the COM component (or OLE object).  To do so, you need to implement two more classes:  the OLEInterfaceImplementation and OLEObject subclasses.  These classes handle the other side of the COM interface, receiving method invocations and parameters from COM, performing the required actions, and returning the requested results.

You'll only need to worry about this pair of classes when you want to implement the services of a COM component in your Smalltalk application.  Note also that the architecture of VSW limits you to producing out-of-process COM servers.  To provide an in-process server, you would need to provide a DLL containing your COM component, something that VSW obviously doesn't allow.

Subclassing OLEInterfaceImplementation

1.    Create a subclass of OLEInterfaceImplementation.

2.    Implement initialization code to set the class’s IID.  I do this by adding an #initialize class method which invokes super>>iid:.  You can define the IID as a constant in a pool or get it from your OLEInterface class.

3.    For each method in the COM interface, you must define a triad of methods to relay that message through to your OLEObject.  The names of these methods depend on the interface methods defined in your OLEInterfacePointer subclass.  For example, to implement a COM interface method named “Mmm” that takes three parameters, you would define the following:

·       dispatchMmm:_:_: invokes the appropriate function handler (method) in your OLEObject instance, based on vtable position, and passes in the supplied parameters.  Answers the result code (HRESULT) from the invoked OLEObject method.  Important: the function handler dictionary is 1-based while the vtable is 0-based.  Therefore, increment the vtable offset by 1 in order to call the correct function handler for that interface method.

·       invokeMmm:_:_: receives the COM message from an external caller and relays it to dispatchMmm:_:_: after converting C-style pointer objects into Smalltalk objects.  Answers the result code (HRESULT) from the dispatchMmm:_:_: method.

·       Mmm:_:_: receives the COM message from internal (Smalltalk) callers and relays it to dispatchMmm:_:_:.  Raises an exception if an error occurs; otherwise answers the result code (HRESULT) of the invoked dispatchMmm:_:_: method.

Subclassing OLEObject

1.    Create a subclass of OLEObject.

2.    If your component has its own CLSID, override OLEObject class>>clsid to answer it.

3.  Implement registerClassFactory and startUpApplication on the class side of your subclass.  Steal example implementations of these from the class OLERandomNumberGenerator in the "OLE COM Sample" that comes with VSW.  Tweak your image startup code so that startUpApplication is called upon image startup.

4.    For each interface your component will serve, you must implement support to manage that interface:

a)    Add an instance variable.

b)    Override OLEObject>>initializeInterfaces to create the interface and assign it to the corresponding instance variable.  (Alternatively, you can lazy-initialize the interfaces in #getInterfaceForIID: if they aren’t likely to be needed.)

c)    Override OLEObject>>allocatedInterfacesDo: to include the interface in enumerations.

d)    Override OLEObject>>getInterfaceForIID: to answer the interface when requested.

e)    Override OLEObject>>resetAllocatedInterfaces to clear the instance variables during instance destruction.

5.   If your component aggregates or contains other components you will also need to:

a)    Override OLEObject>>releaseInnerObjects.

b)    Override OLEObject>>releaseResources.

c)    Other stuff?  I haven’t yet tried out aggregation or containment.

6.   For each method in each interface, you must define a Smalltalk method that implements the behavior of the COM method.  For example, to implement a com interface method named “Mmm” that takes three parameters, you would define a Smalltalk method named Mmm:_:_: in your OLEObject subclass.  This method should answer the result code (HRESULT) for the COM method.

Calling your component from outside of VSW

You should now be able to call your component from the same image in which it resides.  Before you can call your component from outside of VSW however, there is some work that needs to be done outside of Smalltalk.  

Create and compile an interface definition file written in IDL

An IDL (Interface Definition Language) file is used to formally describe your COM interfaces and act as a source file for generating Type Libraries and Proxy/Stub DLLs.  An IDL file to be compiled with the Microsoft IDL compiler (MIDL) looks similar to this:

import "oaidl.idl";
import "ocidl.idl";

[
	object,
	uuid(EF691B2D-C32A-4470-8D95-8B0C5EBB6536),
	helpstring("IMyInterface interface")
]
interface IMyInterface : IUnknown
{
	HRESULT Method1(
		[in]BSTR pwcsIncomingParam,
		[out,ref,retval]IMyInterface ** ppmi
	);
}
// ----------------------------------------------------- 
[
    uuid(C516D2CD-FDF9-4F82-B416-34AD653A51FB),
	version(1.0)
]
library MyLibrary
{
	importlib("stdole32.tlb");
	importlib("stdole2.tlb");

	interface IMyInterface;
	[
		uuid(3BFB79D4-D153-442E-BF41-3979DFC58772),
	]
	coclass MyObject
	{
		[default] interface IMynterface;
	};

};

Basically, the first half (before the dashed line) is the definition of your COM interfaces and will be used to generate proxy/stub DLL code.  The UUIDs in this half must correspond to the IID's you assigned to your interface classes (the OLEInterface subclasses).  The second half (which you will notice makes references back to the interface half) is used to define your type library and so not only includes interfaces but also COM objects that implement those interfaces.  The UUID above any coclass type must correspond to the CLSID that you defined on the OLEObject subclass that you made.  The UUID defined above the library type should be newly generated and used just to refer to the Type Library.  Type Libraries are used mainly as documentation for programmers writing client code to talk to your COM server.

Since we are creating custom (not dispatch) interfaces, all interfaces in the IDL should inherit from IUnknown.  Also, each parameter in each method should be marked with attributes such as [in], [out], or [in,out] to tell the marshalling code how/when to copy them from one process to another.  The [retval] attribute is a nicety to tell client code which parameter can be considered a 'return value' since the actual return value of any COM call is an HRESULT.  If you use pointers to structures, arrays, or to any other kind of pointer, then many other attributes are required to get the marshalling to work properly... I recommend you find a good IDL reference guide!  Since getting the parameter marshalling to work is one of the hardest tasks of implementing a COM server, I recommend you stick to LONG for numbers, BSTR for strings (especially if your client code will be in VB), and other interfaces as parameters.

To compile your IDL file with MIDL, simply create a Utility project in Visual Studio, add your IDL file to the project, and hit Compile.  A Type Library will be placed in your output directory, and some proxy/stub code will be generated and placed in the input directory.  Using this proxy/stub code, you can create a second Visual Studio project to create a proxy/stub DLL as described below. 

Create a proxy/stub DLL

When calling your interfaces from outside Smalltalk, the incoming and outgoing parameters need to be marshalled across process boundaries (since we are creating an out-of-process server with Smalltalk).  This is the job of the proxy/stub DLL.  Luckily, if you've restricted your interfaces to using the more primitive base types, then the proxy/stub code was already written for you when you compiled your IDL file, above.  You will notice the following four files get spit out by the IDL compiler:

dlldata.c
mycomponent.h
mycomponent_i.c
mycomponent_p.c

To compile these into a DLL, do the following with your Visual Studio C++ compiler:

  1. Create a Win32 Dynamic Link Library project.
  2. Add dlldata.c, mycomponent_i.c, and mycomponent_p.c to the project.
  3. Create a fourth file, named mycomponent.def and type the following into it:

LIBRARY            proxystub.dll
DESCRIPTION    'Proxy/Stub DLL"
EXPORTS
    DllGetClassObject    @1 PRIVATE
    DllCanUnloadNow    @2 PRIVATE
    DllRegisterServer    @3 PRIVATE
    DllUnregisterServer    @4 PRIVATE

  1. In the Preprocessor Definitions settings for the compiler, add the symbols REGISTER_PROXY_DLL, _WIN32_DCOM
  2. In the Library Modules setting for the linker, add the files rpcndr.lib, rpcns4.lib, and rpcrt4.lib.
  3. Build the DLL.
  4. Register the proxy/stub DLL using the RegSvr32 tool.  Note that this registration of the proxy/stub is an installation requirement for your smalltalk project... it has to be done on any machine that will be running your COM server. 

You're almost done! With the help of the Type Library that was generated from your IDL, you should now be able to write a client in any COM-aware language and call your VSW COM server.  If this long-winded primer wasn't enough, then I suggest reading Inside COM by Dale Rogerson (Microsoft Press) or Essential COM by Don Box (Addison Wesley) for details on these topics.

Copyright © 2004 Tec4 Systems Inc. 
* Home

Send mail to our WebMaster with questions or comments about this web site.