== Develop COM Components and Automation == == Automation Server == An Automation Server (automation object) allows the client (automation controller) to control what happens on the server. To develop the Server: * Setup Delphi for ''safecall'' code generation. * Select Tools > Options > Delphi Options > Type Library. * SafeCall function mapping = All v-table intefaces * Language: Pascal * Create In-process Server (ActiveX DLL). * Select New > ActiveX > ActiveX Library. * Select New > ActiveX > Automation Object. * CoClass Name: ''MySvr'' (name to be known to the world). * Threading Model: Apartment. * Create interface and class methods and properties using the Type Library Editor. * Select ''IMySvr'', then add Method or Property. * Register the COM Server. Perform one of these: * Select Run > Register ActiveX Server. * Setup project to auto register server, to avoid littering the Windows Registry while developing. * Select Project > Options > Linker > Auto register type library. * Class Constructor. For the Class, if variables or objects need to be initialized, create an ''Initialize'' method to handle the work, instead of a ''Create'' constructor. COM Objects do not use the constructor ''Create''. The Class factory does the actual construction of your COM object so even if you declare a constructor ''Create'', COM will ignore it when it builds your com object. Override the Initialize procedure. When your Com Object is created, and this abstract method is defined in the DLL, COM will call it right away. procedure TMySvr.Initialize; begin inherited; AnObj := TMyCustomClass.Create; end; To develop the Client: * Create a project with a form. * Include the Type Library file (*.tlb) in the project, and add it to the form. If this does not exist, create it by importing the Type Library (Component > Import Component > Import a Type Library). * Write some code to create an instance of the server (launch the server), and to communicate with it: unit clientMain; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, MySvr_TLB; type TfrmClientMain = class(TForm) btnLaunchSvr: TButton; btnGetSvrVersion: TButton; btnClose: TButton; edServerVersion: TEdit; lblServerVersion: TLabel; procedure btnGetEarClick(Sender: TObject); procedure btnCloseClick(Sender: TObject); procedure btnLaunchOptimizerClick(Sender: TObject); private { Private declarations } svr: IMySvr; public { Public declarations } end; var frmClientMain: TfrmClientMain; implementation {$R *.dfm} procedure TfrmClientMain.btnCloseClick(Sender: TObject); begin Close; end; procedure TfrmClientMain.btnGetSvrVersionClick(Sender: TObject); begin edServerVersion.Text := svr.GetSvrVersion; end; procedure TfrmClientMain.btnLaunchSvrClick(Sender: TObject); begin if not Assigned(svr) then begin svr := CoMySvr.Create(); end; if Assigned(svr) then begin btnGetSvrVersion.Enabled := True; end else begin btnGetSvrVersion.Enabled := False; end; end; end. == Wrapping an Existing Application Functionality in an Automation Object == Especially use this method for Out-of-process automation servers: * Open an existing Delphi application project. * Select New > ActiveX > Automation Object. * CoClass Name: ''MySvr'' (name to be known to the world). * Threading Model: Apartment. * Create interface and class methods and properties using the Type Library Editor. * Select ''IMySvr'', then add Method or Property. * Register the COM Server. Perform one of these: * In-process (DLL) automation server: * Select Run > Register ActiveX Server. * Setup project to auto register server, to avoid littering the Windows Registry while developing. * Select Project > Options > Linker > Auto register type library. * Register from command line: C:\> regsvr32 myServer.dll or C:\> tregsvr myServer.dll * Out-of-process (EXE) automation server: * To register, run this from command line: C:\> myapp.exe /regserver * To unregister, run this from command line: C:\> myapp.exe /unregserver == Linking Multiple Clients to a Single Instance of a COM Object == Override the ''AfterConstruction'' and ''BeforeDestruction'' methods in the interface class definition. In ''AfterConstruction'', register the object instance so that multiple clients can lookup the object instance in the Running Object Table (ROT). Class for Interface definition (Server Automation): //----------------------------------------------------------------------------- // description: Code to initialize after object construction. // parameters : None // return : None //----------------------------------------------------------------------------- procedure TMySvr.AfterConstruction; var Unknown: Iunknown; begin inherited; // Operation: Register at startup of main (application) object in the // so-called Running Object Table (ROT). // Purpose: Instead of starting a new instance, an automation client // can connect to the running instance of the application using // the GetActiveObject() and QueryInterface() methods. if (GetActiveObject(MySvr_TLB.Class_MySvr, nil, Unknown) <> MK_E_UNAVAILABLE) then begin RegisterActiveObject(self as IMySvr, Class_MySvr, ACTIVEOBJECT_WEAK, cookie); // register with Running Object Table (ROT) CoLockObjectExternal(self as IMySvr, true {lock}, true); // to prevent premature unloading end; end; //----------------------------------------------------------------------------- // description: Destroy and cleanup before object destruction. // parameters : None // return : None //----------------------------------------------------------------------------- procedure TMySvr.BeforeDestruction; begin // Unregister from the Running Object Table (ROT). // See AfterConstruction() method for more details. CoLockObjectExternal(self as IMySvr, false {lock}, true); // unlock to allow unloading RevokeActiveObject(cookie, nil); // unregister from Running Object Table (ROT) CoDisconnectObject(self as IMySvr, 0); inherited; end; Client code to call the same object instance as the first client that created it: procedure TfrmClientMain.btnLaunchMySvrClick(Sender: TObject); var Unknown: Iunknown; begin if not Assigned(svr) then begin // Check if server object is registered with Running Object Table (ROT). // Use the existing object instance, or create a new one of it does not exist. if (GetActiveObject(MySvr_TLB.Class_MySvr, nil, Unknown) = MK_E_UNAVAILABLE) then begin svr := CoMySvr.Create(); // create a new one, since object does not exist in ROT end else begin Unknown.QueryInterface(MySvr_TLB.IID_IMySvr, svr); // use obj in ROT end; end; end; == Working with SafeArray == === Create a SafeArray === var sfArrBounds: TSafeArrayBound; sfArrData: pointer; sfArr: PSafeArray; begin // create safearray sfArrBounds.lLbound := 0; // lower boundary sfArrBounds.cElements := 12; // element count sfArr := SafeArrayCreate( varInteger, 1 {dimension}, sfArrBounds {lower & upper boundaries}); ... end; === Read a SafeArray === //----------------------------------------------------------------------------- // description: Print the contents of a safearray, using SafeArrayAccessData(). // parameters : sfArr: PSafeArray (assumed the array has been allocated already). // return : None //----------------------------------------------------------------------------- procedure PrnSafearray(sfArr: PSafeArray); var LBound, UBound: integer; sfArrData: pointer; ElemData: integer; i: integer; str: string; begin // get PSafeArray boundaries SafeArrayGetLBound(sfArr, 1, LBound); SafeArrayGetUBound(sfArr, 1, UBound); // read data from safearray str := ''; for i := LBound to UBound do begin if SafeArrayAccessData( sfArr, sfArrData ) = S_OK then begin for i := LBound to UBound do begin ElemData := TIntegerArray(sfArrData)[i]; // get data from safearray str := str + format('sfArr[%d] = %d'+#13#10, [i, ElemData]); // save it for later end; end; SafeArrayUnAccessData(sfArr); ShowMessage(str); end; //----------------------------------------------------------------------------- // description: Print the contents of a safearray, using SafearrayGutElement(). // parameters : sfArr: PSafeArray (assumed the array has been allocated already). // return : None //----------------------------------------------------------------------------- procedure PrnSafearray(sfArr: PSafeArray); var LBound, UBound : integer; ElemData: integer; i: integer; str: string; begin // get PSafeArray boundaries SafeArrayGetLBound(sfArr, 1, LBound); SafeArrayGetUBound(sfArr, 1, UBound ); // read data from safearray str := ''; for i := LBound to UBound do begin SafearrayGetElement(sfArr, i, ElemData); str := str + format('sfArr[%d] = %d'+#13#10, [i, ElemData]); // get data from safearray and print it end; ShowMessage(str); end; Example of how to convert a safearray to an integer array: //----------------------------------------------------------------------------- // description: Convert from SafeArray to integer array. // parameters : sfArr: PSafeArray // return : TIntegerArray (array of Integer) //----------------------------------------------------------------------------- function SafeArrayToIntArr(sfArr: PSafeArray): TIntegerArray; var LBound, UBound: integer; sfArrData: pointer; i: integer; Arr: TIntegerArray; begin // Get boundaries SafeArrayGetLBound(sfArr, 1, LBound); SafeArrayGetUBound(sfArr, 1, UBound); // alloc integer array with same size as safearray SetLength(arr, UBound+1); // copy data from safearray to integer array if SafeArrayAccessData( sfArr, sfArrData ) = S_OK then begin for i:= LBound to UBound do begin arr[i] := TIntegerArray(sfArrData)[i]; end; SafeArrayUnAccessData(sfArr); end; result := Arr; end; === Write to a SafeArray === //----------------------------------------------------------------------------- // description: Initialize a safearray to 99, using SafeArrayAccessData(). // parameters : sfArr: PSafeArray (assumed the array has been allocated already). // return : None //----------------------------------------------------------------------------- procedure InitSafearrayTo99(var sfArr: PSafeArray); var LBound, UBound: integer; sfArrData: pointer; begin // get PSafeArray boundaries SafeArrayGetLBound(sfArr, 1, LBound); SafeArrayGetUBound(sfArr, 1, UBound); // copy data to safearray if SafeArrayAccessData( sfArr, sfArrData ) = S_OK then begin for i := LBound to UBound do begin TIntegerArray(sfArrData)[i] := 99; // copy data to safearray end; end; SafeArrayUnAccessData(sfArr); ... end; //----------------------------------------------------------------------------- // description: Initialize a safearray to 99, using SafearrayPutElement(). // parameters : sfArr: PSafeArray (assumed the array has been allocated already). // return : None //----------------------------------------------------------------------------- procedure InitSafearrayTo99(var sfArr: PSafeArray); var LBound, HBound: integer; i: integer; ElemData: integer; begin // get PSafeArray boundaries SafeArrayGetLBound(sfArr, 1, LBound); SafeArrayGetUBound(sfArr, 1, HBound); // copy data to safearray ElemData := 99; for i := LBound to UBound do begin SafearrayPutElement(sfArr, i, ElemData); end; ... end; Example of converting from integer array to safearray: //----------------------------------------------------------------------------- // description: Convert from integer array to SafeArray. // parameters : arr: TIntegerArray (array of integer) // return : PSafeArray //----------------------------------------------------------------------------- function IntArrToPSafeArray(arr: TIntegerArray): PSafeArray; var sfArrBounds: TSafeArrayBound; sfArrData: pointer; sfArr: PSafeArray; i: integer; begin sfArrBounds.lLbound := Low(arr); sfArrBounds.cElements := High(arr)+1; sfArr := SafeArrayCreate( varInteger, 1, sfArrBounds ); if SafeArrayAccessData( sfArr, sfArrData ) = S_OK then begin for i:= Low(arr) to High(arr) do begin TIntegerArray(sfArrData)[i]:= arr[i]; end; SafeArrayUnAccessData(sfArr); end; result:= sfArr; end; == References == * [[http://www.gekko-software.nl/Delphi/|Delphi and COM]] - Gekko Software * [[http://edn.embarcadero.com/article/27126|Writing COM Automation Events]] - Embarcadero * [[http://www.delphi3000.com/articles/article_2479.asp?SK=|How to do SafeArray access]] - Delphi3000 * [[http://blog.virtec.org/category/delphi/|The Mysteries of PSafeArray]] - Virtech.org * [[http://msdn2.microsoft.com/en-us/library/ms221145.aspx|MSDN Safearray Manipulation Functions]] - Microsoft MSDN * [[http://www.gekko-software.nl/Delphi/art08.htm|Automation Arrays (Safearray, Variant arrays)]] - Gekko Software * [[http://www.gekko-software.nl/Delphi/art10.htm|Server Events for Two-way Communication]] - Gekko Software * [[http://www.techvanguards.com/com/|COM Tutorials and Concepts]] - TechVanguards * [[http://podgeretsky.com/ftp/docs/Delphi/D5/dg/autosrvr.html#3201|Delphi 5 Dev Guide: Creating an Automation Server]]