All of this hard earned COM knowledge will finally be put to use...
Our first true COM Object program
Let us write our first In-Process Com Object Server. Mind you, this is really just a hack to show a step-by-step process. I really dont think this code could possibly be any smaller.
STEP 1
The first thing we need to do is open Delphi. Close all projects that may be open and select New | ActiveX | ActiveX Library from the menu bar. Next, you need to select New | ActiveX | Com Object. A window like the one shown in example 2.0 will appear. Type in the same information as I have. Click OK when finished.
STEP 2
Delphi in all of it's glory will generate the COM Object code for you, thus sparing you the annoying task of typing it all in on your own. We are now going to do a little surgery, but first, I want to discuss the GUIDS. One of the key things to making a DLL build a Com Object for you is to pass to the DLL the IID and CLSID. If you dont recall, these are the definitions.
IID = Interface Identifier (A specific type of GUID)
CLSID = Class Identifier (Another type of GUID) It uniquely identifies your object.
Keep in mind that a GUID is just a GUID. It is the structure that the GUID is attached to that makes it a specific type of GUID. Okay, back to the surgery. Since I just established the fact that the DLL needs both of these GUIDs, you should ask yourself how your client program is supposed to know what these GUIDs are. Well, the answer is simple. You simple include them in your uses clause. This is what is happening when you include the ActiveX.Pas file in your uses clause. You should open it up to explore it. In this unit, you will find a myriad of IIDs and CLSIDs. This makes it so you have access to both the class and the interfaces that can be used to reference it.
That said, create a new unit called Globals.Pas and move the following line of code from your unit that contains the Com object into your Globals.Pas unit. Then add Globals to the uses clause in the Com Object Unit.
const
//move this to Globals.
Class_DisplaySomething: TGUID =
'{8576CE02-E24A-11D4-BDE0-00A024BAF736}';
|
STEP 3
Next, we need to build the IDisplaySomething interface. This will be done in the Globals.Pas unit as well since this interface will need to be shared with the client application. Below is the full listing of how your DSGlobals should look.
unit DSGlobals;
interface
const
Class_DisplaySomething: TGUID =
'{8576CE02-E24A-11D4-BDE0-00A024BAF736}';
IID_IDisplaySomething : TGUID =
'{8576CE04-E24A-11D4-BDE0-00A024BAF736}';
type
IDisplaySomething = interface
['{8576CE04-E24A-11D4-BDE0-00A024BAF736}']
procedure DisplayMessage(S : string);
end;
implementation
end.
|
An easy trick I did was build the interface first, give it a GUID, and then make the IID_IDisplaySomething constant afterwards because then I can just grab the GUID from the interface without the [ ] as opposed to adding them.
STEP 4
Now you need to provide implementation for our procedure DisplaySomething(S : string);. This will be implemented in the Com Object unit where we have TDisplaySomething defined. Below is the full code for your Com Object unit. After you have implemented it, build your project. It will create the DLL in the projects current directory. You have just created your first in-process COM server!
unit DLL_DisplaySomethingUnit;
interface
uses
Windows, ActiveX, ComObj,
DSGlobals, Dialogs;
type
TDisplaySomething = class(TComObject, IDisplaySomething)
protected
procedure DisplayMessage(S : string);
end;
implementation
uses ComServ;
{ TDisplaySomething }
procedure TDisplaySomething.DisplayMessage(S: string);
begin
ShowMessage(S);
end;
initialization
TComObjectFactory.Create(
ComServer,
TDisplaySomething,
Class_DisplaySomething,
'DisplaySomething',
'A COM Object!',
ciMultiInstance,
tmApartment);
end.
|
STEP 5
We will now need to register our DLL with the Windows OS. In Delphi, select Project | Register ActiveX Server. Delphi will proceed to build the DLL for you and register it in windows. If the process is successful, you will see the message displayed: "Successfully Registered ActiveX Server ...". For fun, you should open Regedit and look at the really cool in-process com server we just created. Since we know the GUID, we can cut and paste it into the Windows Registry Find entry to locate it.
STEP 6
Let us now build the client and have it grab our newly created Com Object. Create a new application and add DSGlobals, ComObj, and ActiveX to your uses clause. Put a single button in called AButton on your form and double click it. Add the following code into the OnClick event:
var
FMyComObject : IDisplaySomething;
begin
OleCheck(CoCreateInstance(
Class_DisplaySomething,
nil,
CLSCTX_ALL,
IDisplaySomething,
FMyComObject));
FMyComObject.DisplayMessage
('I have succeeded!');
end;
|
STEP 7
Build your program and click the button on the form. If you see the message, I have succeeded, then you have just successfully programmed your first Com Object Server and Client.
Download this example COM object.
Important Details
Now that we have created a COM server, we will now need to study some important characteristics within the code.
TCOMObjectFactory
The only thing you need to know about this object is that it will fire up your DLL and pass an interface back to a client program. Delphi automatically creates the call to TComObjectFactory.Create for you. If you desperately want to know a little bit about the Create method, here is some information.
TComObjectFactory.Create(
//Manages your object.
ComServer,
//The implementation of your interface.
TDisplaySomething,
//The CLSID of your com object.
Class_DisplaySomething,
//The name of your class.
'DisplaySomething',
//A description of your class.
'A COM Object!',
//Determines if multiple clients can access this.
ciMultiInstance,
//The chosen threading model.
tmApartment);
|
ActiveX and ComObj
The ActiveX.pas unit contains the majority of COM declarations. They key classes that make Com programming possible in the VCL are defined in ComObj.pas. This unit contains classes like TComObject, TTypedComObject, and TAutoObject.
CoCreateInstance
The CoCreateInstance function tells COM to retrieve your Com Object from your server
OleCheck(CoCreateInstance(
//CLSID of your Com Object
Class_DisplaySomething,
//Used in aggregation. I wont' discuss
//aggregation so set to nil.
nil,
//This is the type of server it is accessing
//the com object from. _All will find it
CLSCTX_ALL,
//The interface for your Com Object.
IDisplaySomething,
//Gets a pointer to your Com Object and
//puts it in this interface variable
FMyComObject)); .
|
OleCheck
You may be wondering why I called the CoCreateInstance from within the OleCheck function. The reason is quite simple. Most OLE functions return an HResult variable. You will always want to check this result since failure to do so can have exceedingly tragic consequences. It is easier to use the OleCheck function than it is to hardcode source that tests each result value that the function could return.
One of the great things about OleCheck is that it will take an error and then convert it to a string error that is written in almost layman terms. (Well, what we coders call layman terms!) A simple rule to remember is: Make sure you always use OleCheck when making OLE function calls!
Use these types when passing information to and from COM Objects
The following types are valid in a type library. The Automation column lists types that can be used by an interface that has its Automation or DispInterface check marked
| Delphi type | IDL type | Variant type | Automation | Description |
| Smallint | short | VT_I2 | Yes | 2-byte signed int |
| Integer | long | VT_I4 | Yes | 4-byte signed integer |
| Single | single | VT_R4 | Yes | 4-byte real |
| Double | double | VT_R8 | Yes | 8-byte real |
| Currency | CURRENCY | VT_CY | Yes | currency |
| TDateTime | DATE | VT_DATE | Yes | date |
| WideString | BSTR | VT_BSTR | Yes | binary string |
| IDispatch | IDispatch | VT_DISPATCH | Yes | pointer to IDispatch |
| SCODE | SCODE | VT_ERROR | Yes | Ole Error Code |
| WordBool | VARIANT BOOL | VT_BOOL | Yes | true=-1 false=0 |
| OleVariant | VARIANT | VT_VARIANT | Yes | Ole Variant |
| IUnknown | IUnknown | VT_UNKNOWN | Yes | pointer to IUnknown |
| ShortInt | byte | VT_I1 | No | 1 byte signed integer |
| Byte | unsigned char | VT_UI1 | Yes | 1 byte unsigned integer |
| Word | unsigned short | VT_UI2 | No* | 2 byte unsigned integer |
| UNIT | unsigned long | VT_UI4 | No* | 4 byte unsigned integer |
| Int64 | __int64 | VT_I8 | No | 8 byte signed real |
| LargeUInt | uint64 | VT_UI8 | No | 8 byte unsigned real |
| SysInt | int | VT_INT | No* | system dependant integer |
| SysUInt | unsigned int | VT_UINT | No* | system dependant unsigned integer |
| HResult | HResult | VT_HRESULT | No | 32 bit error code |
| Pointer | unsigned int | VT_VOID | No | untyped pointer |
| SafeArray | SAFEARRAY | VT_SAFEARRAY | No | OLE Safe Array |
| PChar | LPSTR | VT_LPSTR | No | a pointer to char |
| PWideChar | LPWSTR | VT_LPSTR | No | a pointer to widechar |
*) The types Word, UINT, SYSINT, and SYSUINT may allow automation with certain applications.
Note: Byte (VT_UI1) is Automation-compatible. However, it is not allowed in a Variant or OleVariant since some Automation servers cannot handle the value properly.
Next page > Homework Assignment > Page 1, 2, 3
An Introduction to COM Programming with Delphi: Table of Content
<< Previous Lesson (3): What is the implements directive? What is the Method Resolution Clauses? Pseudo-Multiple Interface Inheritance. Interface properties and other fine tales of horror.
>> Next Lesson (5): Marshaling Data. Behold the power of Variant Arrays. Using Variants and Variant Arrays.