Title: Delphi COM implementation in research notes (a) Keywords: Delphi COM Author: dREAMtHEATER Difficulty: Normal [] medium [x] High [] http: //dREAMtHEATER.yeah.netE-Mail: NoteXPad@163.com Completion Date : July 26, 2004, the foreword has written a Windows shell extension with Delphi. You know that the Windows shell extension is actually a kind of application - shell COM, although the entire program is written, but After writing, I still feel a little fog in the realization of COM in Delphi, so I think there is a need to spend a little time to do some of the implementation of COM in Delphi. In addition, I also bought Li Wei's new book - "In-depth core-VCL architecture analysis", there are two chapters involved with COM-related content, I know that the implementation of COM in Delphi is based on the interface (Interface) And the concept of interface in Delphi originated from the support of COM, in short they affect each other, developing into the status of the interface in Delphi, and completely get rid of COM and independently exists. This series focuses on describing COM in Delphi implementation techniques, mainly with VCL source segmentation, does not involve excessive basic concepts, so the reader requires a certain COM and interface concept, I can refer to I am listed at the end of the article. Literature. This article mainly tells the creation process of COM objects in Delphi. In order to make readers to follow my analysis to easily read this article, I will refer to the example of [2], but I have rewritten some code in order to make a clearer explanation. All analyzes are tested on Delphi 7. Demo Source Code Download here. In Delphi, first select menu file -> new -> Other ... New ActiveX Library and save the named SimpleComServer, then create a COM object, named the object in the COM Object Wizard, two in Options The check box can not be used to select other remaining defaults, and now the framework of the COM server has been established. The rest is to implement the code of the declared interface isimpleComobject, and other readers should see the source code, very simple.
Server-side code library SimpleComServer; uses ComServ, SimpleCOMObject in 'SimpleCOMObject.pas', SimpleComInterface in 'SimpleComInterface.pas', exports DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer; {$ R * .RES} beginend.
unit SimpleComInterface; interfaceuses Windows; const Class_SimpleComObject: TGUID = '{3714CF21-D272-11D3-947F-0050DA73BE5D}'; type ISimpleComObject = interface [ '{2E2A6DD0-D282-11D3-947F-0050DA73BE5D}'] function Multiply (X, Y : Integer): Integer; stdcall; function GetClassName: Widestring; stdcall; end; implementationendunit SimpleCOMObject; interface // SimpleCOMObject implementation section uses Windows, ActiveX, Classes, ComObj, SimpleComInterface; type TSimpleComObject = class (TComObject, ISimpleComObject) protected function Multiply (X, Y: Integer): Integer; stdcall; function GetClassName: Widestring; stdcall; end; const Class_SimpleComObject: TGUID = '{3714CF21-D272-11D3-947F-0050DA73BE5D}'; implementationuses ComServ; {TSimpleComObject} function TSimpleComObject.GetClassName : Widestring; begin Result: = TSimpleComObject.ClassName; end; function TSimpleComObject.Multiply (X, Y: Integer): Integer; begin Result: = X * Y; end; initialization TComObjectFactory.Create (ComServer, TSimpleComObje CT, Class_SimpleComobject, 'SimpleComobject', 'a Simple Implementation of a com Object', cimultiinstance, tmapartment; End.
After completing the code of the server, we need to write a client applet to perform interface code within the server side, I only list the key code parts that I have rewritten, other source code.
Client the key code procedure TForm1.Button1Click (Sender: TObject); var aFactory: IClassFactory; begin OleCheck (CoGetClassObject (Class_SimpleComObject, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, nil, IClassFactory, aFactory)); aFactory.CreateInstance (nil, ISimpleComObject, ComInterface); ShowMessage ( 'The result is:' IntToStr (ComInterface.Multiply (StrToInt (Edit1.Text), StrToInt (Edit2.Text)))); ComInterface: = nil; end; procedure TForm1.Button2Click (Sender: TObject); begin ComInterface : = CreateComObject (class_simplecombject) asimplecomobject; showMessage (Cominterface.getClassName); Cominterface: = nil; end;
Now start entering the subject, follow me into the Delphi's COM Framework World. I mainly create a COM object from the client program to analyze the VCL source. In the client code, I use two get the SimpleComobject object and get the ISIMPLECOMOBJECT interface. Once the interface is got, you can freely use the method specified by the interface. Let's take a look at how to create a COM object in Button1Click. The code calls COGETCLASSOBJECT to get the class factory-IClassFactory interface to create a SimpleComobject object, followed by calling the CREATEINSTANCE method to call the interface, returning the ISIMPLECOMOBJECT interface pointer. So how is the entire process of the above implementation in the VCL? Let's start with the API from CogetherClassObject. COGETCLASSOBJECT is a standard COM API for Windows, which exists in OLE32.DLL, which is one of Windows COM DLLs. According to the information corresponding to the information in the system registry (ie, the server-side program, we discuss the server-side program, we discussed one here is a DLL file), then call LoadLibrary (actually a COLOADLIBRARY) function to initialize the server (DLL is loaded into the client program) and calls the DllgetClassObject output function of the component program. The DllgetClassObject function is responsible for creating the corresponding class factory object and returns the iClassFactory interface of the class factory object. The task of this CogetClassObject function is completed, and then the client program continues to call the CreateInstance member function of the class factory object, which is responsible for the creation of COM objects. Note: The Windows COM specification specifies that you must complete and output DllgetClassObject in the server, if this is not discovered, Windows will not pass objects to the client, and DllgetClassObject will be the entry point to our DLL (COM server). It is not difficult to see that the iClassFactory interface is obtained by calling the server-side DllgetClassObject function, the legend is actually starting from this output function. Let's see how it is achieved (if I have a comment in the source code, please see more, no longer prompt below): // Excerpted from ComservServ unit Function DllgetClassObject (Const CLSID, IID: Tguid; var Obj): HRESULT ; var Factory: TComObjectFactory; begin Factory: = ComClassManager.GetFactoryFromClassID (CLSID); if Factory <> nil then if Factory.GetInterface (IID, Obj) then Result: = S_OK else Result: = E_NOINTERFACE else begin Pointer (Obj): = NIL; Result: = Class_e_classNotavailable; end;
What is COMCLASSMANAGER? It is the first class in the Delphi COM Framework we need to introduce.
// Taken ComObj unit var ComClassManagerVar: TObject; function ComClassManager: TComClassManager; begin if ComClassManagerVar = nil then ComClassManagerVar: = TComClassManager.Create; Result: = TComClassManager (ComClassManagerVar); end; for each server in a memory TComClassManager example, i.e. ComClassManagerVar Global object variables are responsible for managing all class factory objects in the COM server (only one class factory in this example). When is the factory factory created? In fact, I have already listed in front, and the initialization section of the SIMPLECOBJECT generated by Com Object Wizard has automatically created a TcomObjectFactory object for us:
Initialization TcomobjectFactory.create (COMSERVER, TSIMPLECOMOBJECT, CLASS_SIMPLECOMOBJECT, 'SimpleComobject', 'a Simple Implementation of a com Object', cimultiinstance, tmapartment;
Delphi Keyword Initialization Tip We dll When you are loaded into the client program process space, you are responsible for creating a class factory TcomObjectFactory that is already created. We know, there is a plurality of COM objects in a server side, and each individual COM object must have a class factory that creates such a class. If there is ten COM objects in the server you designed, then there will be ten A class factory responsible for creating different classes, this ten class factories will be created one by one when the program is initialized. This concept must be built in your mind, otherwise it is not understood later. Tip, a few ClassFactory classes are defined in the VCL, which is responsible for the creation of a type of COM object, and TcomobjectFactory is the simplest of the simplest [1]. So how does COMCLASSMANAGER and TCOMOBJECTFACTORY contact? See the constructor of TcomobjectFactory:
Taken ComObj unit // constructor TComObjectFactory.Create (ComServer: TComServerObject; ComClass: TComClass; const ClassID: TGUID; const ClassName, Description: string; Instancing: TClassInstancing; ThreadingModel: TThreadingModel); begin // ... // inserting themselves Go to ComclassManager.adDObjectFactory (Self); // ... end; // ... end;
Take a look at the COMCLASSMANAGER related implementation code:
// Taken ComObj unit TComClassManager = class (TObject) private // TComClassManager TComObjectFactory maintains a list FFactoryList: TComObjectFactory; FLock: TMultiReadExclusiveWriteSynchronizer; procedure AddObjectFactory (Factory: TComObjectFactory); // ... end; procedure TComClassManager.AddObjectFactory (Factory: TcomObjectFactory; begin flock.beginwrite; try factory.fnext: = ffactoryList: = FactoryList: = Factory; Finally flock.endwrite; end; end; comClassmanagerVar maintains a linked list of all class works in the server, each single type factory The instance is automatically initialized, where you can see in our server Initialization section, and automatically add yourself to the ComclassManager's Link list (FactoryList). Now think about it, this design is not very good. Please follow me to go down. When the client requires DllgetClassObject to return to the designated class factory, the TcomclassManager's getFactoryFromClassID method is called inside the function. This method traverses the FactoryList linked list, finds the corresponding class factory according to ClassID and returns an instance of the class factory object.
// Taken ComObj unit function TComClassManager.GetFactoryFromClassID (const ClassID: TGUID): TComObjectFactory; begin FLock.BeginRead; try Result: = FFactoryList; while Result <> nil do begin if IsEqualGUID (Result.ClassID, ClassID) then Exit; Result: = Result.FNext; end; fin; end;
To analyze the code above, the Link list ffactoryList variable is actually the TcomObjectFactory type. TComObjectFactory is created, get rich information about the related COM object information it wants to create, for example in our sample, classfactory knows it to create The COM object type is TSIMpleComObject, classid is class_simplecomobject .. Wait, all provide extremely important information for the class factory in creating related classes and some auxiliary methods (functions).
Taken ComObj unit // constructor TComObjectFactory.Create (ComServer: TComServerObject; ComClass: TComClass; const ClassID: TGUID; const ClassName, Description: string; Instancing: TClassInstancing; ThreadingModel: TThreadingModel); begin // ... FComServer: = ComServer; FComClass: = ComClass; FClassID: = ClassID; FClassName: = ClassName; FDescription: = Description; FInstancing: = Instancing; FErrorIID: = IUnknown; FShowErrors: = True; FThreadingModel: = ThreadingModel; FRegister: = -1; end; DllGetClassObject obtained After the correct class object, the GetInterface method is called. This method is actually inherited from TOBJECT.GETINTERFACE, Delphi designs a record structure for each interface with the GUID, and implements the TcomobjectFactory object of the IclassFactory interface. the vmtIntfTable points to a TInterfaceTable record, which includes a number of interfaces (the IUnknown, IClassFactory) it implements, TInterfaceEntry recording a corresponding interface information, an interface corresponding TInterfaceEntry record IOffset domain obtained by querying IClassFactory interface in TComObjectFactory object instance The correct location and returns the iClassFactory interface pointer to this location [1] [3].
Taken // System unit function TObject.GetInterface (const IID: TGUID; out Obj): Boolean; var InterfaceEntry: PInterfaceEntry; begin Pointer (Obj): = nil; InterfaceEntry: = GetInterfaceEntry (IID); if InterfaceEntry <> nil then begin If InterfaceEntry ^ .ioffset <> 0 THEN Begin Pointer (OBJ): = Pointer (Integer); if Pointer (OBJ) <> nil dam ._ addref; end else interface (Obj : = INVOKEIMPLGETTER (Self, InterfaceENTRY ^ .implgetter); End; Result: = Pointer (OBJ) <> nil; end;
At this point, COGETCLASSOBJECT calls the server-side DllgetClassObject has been correctly acquired to create an iClassFactory interface for creating a SimpleComObject object. After obtaining this interface, you can call it method createInstance to create a SimpleComobject object and return to the isimpleComobject interface, now you can operate anything about the ISIMPLECOMOBJECT interface. Let's see how you create a SimpleComObject object in ButtonClick2. ButtonClick2 is to call the CreateComObject function to create a SimpleComobject object. The CreateComObject function is just a simple packaging for COM API - CocreateInstance. Why do you want to wrap it, you can look at the parameters of CoCreateInstance, you know why, the parameters are more and more complicated, this is a common problem of the Windows API, but the VCL implementation is very considerate, it passes the CLSID as the only parameter, in fact us, us Most of the COM objects created are CLSID known, and the object is a specified object that resides in the local or internal server. // Taken ComObj unit function CreateComObject (const ClassID: TGUID): IUnknown; begin OleCheck (CoCreateInstance (ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IUnknown, Result)); end;
CoCreateInstance also present in OLE32.DLL, it is also the first to call CoGetClassObject internal function that returns the IClassFactory responsible for creating SimpleCOMObject interface, and then still call CreateInstance to create the interface and returns SimpleCOMObject IUnknown interface of the object, to this point, and in Button1Click The implementation method for creating SimpleComobject is different that Button1Click direct returns directly to the isimpleComobject interface through ClassFactory instead of its iunkNown interface, other do not distinguish, relative to Button1Click method is more intuitive. After obtaining the IUnknown interface of SimpleComobject, we cannot use this interface to call the ISIMPLECOMOBJECT method. In order to communicate with objects, it must first convert it to the IsimpleComobject interface. So readers will ask why CreateComobject does not design to return to the needed interface, I want to simplify the use of this function. Get the ISIMPLECOMOBJECT interface You can query the SimpleComobject object if the QueryInterface method is called by calling the IUNKNOWN interface. Delphi provides us with a simpler method - "AS" keyword. Let's take a look at what AS has done in the scene after the scene (the anti-political source code in the debug state):
Unit1.pas.49: ComInterface: = CreateComObject (Class_SimpleComObject) as ISimpleComObject; 0045B2C6 8D55FC lea edx, [ebp- $ 04] 0045B2C9 A16CD24500 mov eax, [$ 0045d26c] 0045B2CE E8C9F0FFFF call CreateComObject0045B2D3 8B55FC mov edx, [ebp- $ 04] 0045B2D6 8D8314030000 Lea EAX, [EBX $ 00000314] 0045B2DC B93CB34500 MOV ECX, $ 0045B33C0045B2E1 E87AA9FAFF CALL @intfcast can be seen, the AS is converted to call @intfcast, the _intfcast function of the System unit. Oh, it is actually a QueryInterface method for calling the iUnknown interface.
Taken // System unit procedure _IntfCast (var Dest: IInterface; const Source: IInterface; const IID: TGUID); {$ IFDEF PUREPASCAL} // PIC: EBX must be correct before calling QueryInterfacevar Temp: IInterface; begin if Source = nil then DEST: = NIL ELSE BEGIN TEMP: = NIL; if Source.Queryinterface (IID, TEMP) <> 0 THEN Error (ReintFcastError) Else Dest: = Temp; end;
By the two methods of creating a SimpleComobject object are all analyzed. So in the usual application, which method we use to create a COM object is better? In fact, in the official help of Delphi, I have given us a answer: When you only create a single COM object, you can call createcomobject; when you need to create the same class COM object, then choose the class factory directly, or it is fast. After I analyze, do you think the complex COM structure is perfectly packaged by VCL? At least I think it is like this, so I have to admire the high-tech level of the Borland Delphi R & D team. If you haven't heard it yet, then wait for my next article ... References 1. Li Wei. "Analyze the core-VCL architecture" sixth, seventh chapter 2. Fernando Vicaria. "Delphi Com in-Process Servers Under THE Microscope, Part 1 ". Hardcore Delphi Magazine, Mar 20003. Savetime." Delphi's interface mechanism ", Feb 20044. Savetime." "COM principle and application" learning note ", Feb 2004