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:

To develop the Client:

Wrapping an Existing Application Functionality in an Automation Object

Especially use this method for Out-of-process automation servers:

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