| Title | Connectable COM Objects |
| Author | Andre v.d. Merwe dart@iafrica.com |
| Published | March 1998 - Pinnacle Delphi Developer |
| Copyright | Reprinted with permission from Pinnacle Publishing, Inc. http://www.pinpub.com |
| Downloads |
|
| Back to the Zen Home Page |
Introduction:
A connectable COM object is one that supports events. This article shows how to implement connectable COM objects in Delphi 3. The specification for connectable COM objects is well defined by Microsoft. Implementing events in the fashion described in this article will ensure that your COM servers / clients will work seamlessly with other servers / clients. This article will show how to use COM classes new to Delphi 3 to greatly simplify creating COM objects.
What is a "Connectable COM Object"?:
A connectable COM object, or source, is a COM object that supports events. In other words it can fire events in a client. Think of an ActiveX control, say a button. When you click the button an event is fired and the container is notified. This is accomplished with connectable COM objects. This article is not about ActiveX, instead I'll create two very simple examples to demonstrate this concept.
Client and Server:
When dealing with connectable COM objects there are two distinct objects, the client and the server. The client connects to the server. When some condition is met the server fires and event and the client responds. This can be likened to a Delphi component. The application (client) is connected to the server (component). When some condition is met in the component (server) an event is fired and the client responds.
Connection Points and Connection Point Containers
From here on the OLE technology starts to kick in… So take a sip of your favourite cafinated drink and enjoy! Before I get started I'd just like to mention that OLE does not stand for anything anymore. It is just OLE.
To make this discussion a little less abstract I'll explain with the aid of an example.
First an event is needed. As with all COM objects an interface is used to define function(s)/procedure(s) that will be implemented. Here I define an interface, IMyEvents, with one method (event) OnMyEvent.
IMyEvents = interface
['{blablabla}']
procedure OnMyEvent; stdcall;
end;
The blablabla part is where you would place a CLSID, which is generated by pressing ctrl-alt-g while in the Delphi 3 IDE.
The client implements OnMyEvent, and the server causes this event to be fired (executed). Again think of a Delphi component. The component fires and event, which causes code defined by the user (client) to be executed. This is important, the server fires the event but it is the client that implements it.
In COM each 'event interface', as above, is contained in a connection point. A connection point is defined by the IConnectionPoint interface. The connection point's responsibilities include keeping a list of client that are connected to it. In other words multiple clients can be connected to a single connection point.
A server can have multiple connection points, and thus support many 'event interfaces'. A connection point container contains these connection points. IConnectionPointContainer is the interface that defines a connection point container.
See Figure 1 for a depiction of this relation.

Figure 1. Conceptual layout of the client/server relation.
By now you might well be asking whether all this complexity is really necessary for something as simple as events. The reason is that COM is designed to be extensible, and thus the most generic approach is chosen. It is important to remember that the client and server may be written by different people, in fact that is probably the norm. So the client and server COM objects must be able to function with absolutely no knowledge of how the other one is implemented, other than to know that it is a connectable COM client / server.
Having said this I honestly believe that understanding this particular COM technology is not that difficult. Once you've implemented it your self it should make perfect sense.
Delphi:
If you have used connectable COM objects in C/C++ you will know that there is quite a bit of overhead that must be dealt with. For instance another responsibility of the IConnectionPointContainer is to enumerate all contained connection points.
Thankfully Delphi 3 hides must of the drudgery from you. IConnectionPointContainer is implemented by TConnectionPoints, IConnectionPoint by TConnectionPoint. Making use of these classes greatly simplifies COM programming. For other useful COM classes take a look at AxControls.pas in the Delphi 3 source directory (Professional and C/S versions of Delphi only).
The Example:
Now that the basics are covered I will go through the steps of creating both the client and server objects. The server has one callable method - GetPinNumber. When this method is called the server sets the given window handle's caption to a randomly selected number. If any clients are connected to the server the server will fire the OnMyEvent event in each of them.
The server is implemented as a DLL, the client as a EXE.
I will now explain each unit of the server then of the client. I will first give an overview of what must be done for each unit as well as an overview for each function. The overview for each function will be followed by an explanation of what the source code is doing, in point form.
Before continuing, download this months subscriber downloads from the pinnacle web site. Print the following files, which together make up the example that I'm about to start explaining.
|
Server Files |
PinServer.dpr PinServer_U1.pas PinServer_Def.pas |
|
Client |
PinClient_U1.pas |
Once you've printed the source you can refer to it and see exactly how I implement the section that I'm discussing.
Server DLL: - .DPR
Start a new DLL project (File-New DLL), this will be the server DLL.
Every COM DLL must export 4 functions. These functions are DllGetClassObject, DllCanUnloadNow, DllRegisterServer and DllUnregisterServer.
Add these 4 functions to the DLLs exports. Once again Delphi comes to the rescue and implements these functions for you. Just include ComServ in the uses clause.
Also add the {$R *.res} line. This is required for later when the server is registered. Ignore the {$R *.TLB} this will automatically be added by Delphi.
Now add two units to the project, one for the interface definitions and one for the COM server implementation. Your .DPR file should now look like PinServer.dpr
Server DLL: - Definition
Two interfaces are required for this example. The server's interface with one method - GetPinNumber, and the event interface with one event - OnPinEvent. You should generate new CLSIDs for these interfaces, you can do this by pressing ctrl-alt-g in the Delphi 3 IDE.
Two constants are also defined. These constants are GUIDs and are used to make referencing the required interface easier. Were it not for these constants you would have to type the whole GUID every time. IID_IPinServerEvents is the event interfaces GUID, CLSID_IPinServer is the servers GUID.
See PinServer_Def.pas
Server DLL: - Implementation
Now the last unit of the server, this is also the most complex of the units. Add ActiveX, ComServ, AxControls, and PinServer_Def to the units uses clause. PinServer_Def is the name of the definition unit, as explained above.
Refer to PinServer_U1.pas as I explain the class step by step. The first thing that is needed is a class for the COM object. The class is derived from TComObject which implements all the basic COM functions. The server must also support IPinServer so add it to the classes declaration. By doing this you are indication that the class will support all methods in the IPinServer interface. In this example there is only one method to support - GetPinNumber.
Add the Initialize and Destory methods. Destroy is the destructor, but Initialize is not the constructor. Do not use the constructor to do initialisation in a COM class, always use the Initialize method. Next add GetPinNumber and QueryInterface.
Server DLL: - Implementation - Initialize
Server DLL: - Implementation - Destroy
Server DLL: - Implementation - QueryInterface
When a client wants to connect to a server it requests the server's IConnectionPointContainer interface. If you take a look at the definition of the TPinServer (PinServer_U1.pas) class you will see that the class does not implement this interface.
The class does however have a variable called cpcConnection which is a TConnectionPoints class, which is exactly what the client is looking for. For this reason you need to intercept all calls to the servers QueryInterface.
Server DLL: - Implementation - GetPinNumber
GetPinNumber is the procedure where most of the work takes place. This procedure has 2 main tasks. First it must set the given window's caption to a random integer. It must then enumerate through all connected clients and fire their OnPinEvent events.
Task 1)
Task 2)
Take a careful look at these steps again. There is not much code but quite a bit is accomplished, it is important that you understand what is going on.
To summarise task 2
Server DLL: - Implementation - The Class Factory
A class factory is used to create an instance of the server. When you call CreateComObject(…) from the client, the servers class factory is used called on to create an instance of the class.
Yet again Delphi 3 greatly simplifies this task, all that you need to do is create an instance of TComObjectFatory. See PinServer_U1.pas to see how this is done.
Server DLL: - Implementation - Register the Server
The COM server is now complete. It must be registered before it can be used though. There are two ways to do this. The easiest is to register from within Delphi, select "Register ActiveX Control" from the "Run" menu.
You can also use regserv32.exe which is found in the windows system directory. This is the method you would use to install a server on someone else's machine. To register a server using regserv32, give the path to the server DLL as parameter on. For example "c:\windows\system\regsvr32 PinServer.dll"
See PinServer_U1.pas
PinClient:
At this point you have a fully functional COM server, this is not much use without a client to use it though. So start a new application that will be used as the client.
Drop 3 buttons and an edit control on to the main form. See Figure 2 for the layout and component captions.

Figure 2. The client's main form
Add ActiveX, ComObj and PinServer_Def to the units uses clause. PinServer_Def is the unit that contains the servers interface definitions, and CLSID constants.
Since this is just a normal application at this stage the main form's class (TForm1) is derived from TForm. To convert this to a class capable of being the COM server's client, you need to add both IUnknown and IPinServerEvents.
Adding IPinServerEvents means that that this class will support the OnPinEvent event, which is to be fired by the server. When this client tries to connect to the server, the server will perform a QueryInterface on the client to ensure that it does indeed support this interface. IUnknown is needed so that this class can act as a COM client. Every COM object must support IUnknown, in fact that is what makes it a COM object!
Add the 3 IUnknown methods and the IPinServerEvent's OnPinEvent method. As with the server I'll explain each of these methods, using point form if necessary.
PinClient: - QueryInterface
Very simple method. Just call TForm's GetInterface to do the interface lookup. Return S_OK if the interface is found else return E_NOINTERFACE.
PinClient: - _AddRef and _Release
The client does not need to implement reference counting. The client object is contained within the main form class. This class is created when the application is stated and destroyed when the application ends. The client's lifetime is thus 'contained' by the main form's class and reference counting is therefore not needed.
Both _AddRef and _Release just return 1. Dont return 0 as Delphi will then try to free the running application. This would be a nasty bug! Return 1 and you can forget about these two methods.
PinClient: - OnPinEvent
This is the method that is called by the server when the event is fired.
Here I use ShowMessage to display the received number.
PinClient: - OnCreate
PinClient: - "Get Number" Button
The server will respond regardless of whether the client is connected to it or not.
PinClient: - "Connect" Button
PinClient: - "Disconnect" Button
PinClient: - OnDestroy
See PinClient_U1.pas
Overview
You now have a working COM server and client. I'm certain that after you've read through this article you'll agree that it was not too difficult. There is a bit of overhead code that must be written for each client and server, but the results are well worth it.
Having seen all the code you can now see how the client and server interact. Here is a very summarised version of this interaction
All information on these www pages is copyright (©) 1997 Andre .v.d. Merwe
And may not be copied or mirrored without my permission.