Tuesday, 14 June 2011

OLE Automation with Delphi part II

Going on with my previous post (OLE Automation with Delphi part I) where I was able to use the invocation of the "Word.Application" service using COM. In this post I am going to show you how to detect if a service is available using OLE Automation. First of all, I'm going to give you a brief overview of the windows Registry and where are all these COM class objects allocated, and then I will move forward to show the Delphi code that is used for this purpose.

COM class objects are identified by a CLSID key:
A CLSID is a globally unique identifier that identifies a COM class object. If your server or container allows linking to its embedded objects, you need to register a CLSID for each supported class of objects
source: msdn

If we have installed Microsoft Word and look for "Word.Application" inside the registry, we will find the following information under \HKEY_CLASSES_ROOT\Word.Application:

This entry has the CLSID key = {000209FF-0000-0000-C000-000000000046}. This CLSID key has been set when the component was registered. We can look it up and check what executable or routine is invoking in the next key entry: HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{CLSID}.


As we can see, it shows the path of Microsoft word and with the option /Automation. Now that we know how to deal with the windows registry and how are set out CLSID keys, let's move on how to use this information with Delphi.

Check if the COM Class Object is registered:
To ensure that the service is available, we can use the OLE32 methods available through ActiveX and ComObj units.

unit Activex;

function CLSIDFromProgID;               external ole32 name 'CLSIDFromProgID';

Example of use:

uses
    ActiveX, ComObj;

procedure TForm1.CheckCLSIDClick(Sender: TObject);
var
    clsid: TCLSID;
    sCLSID: string;
    result: hResult;
begin
    // initialize COM
    CoInitialize(nil);

    // Get CLSID from Microsoft Word
    result := CLSIDFromProgID('Word.Application', clsid);
    sCLSID := GUIDToString(clsid);
    try
        OleCheck(result);
        ShowMessage('Class Word.Application recognized with CLSID ' + sCLSID);
    except
        on e: EOleSysError do
        begin
            ShowMessage('Class Word.Application not recognized CLSID ' + sCLSID);
        end;
    end;

    // deinitialize COM
    CoUninitialize;
end;

Invoking the COM Class Object:
Now that we are sure the COM Class is registered, we can cater the CoCreateInstance method to invoke the class with the proper context properties. dwClsContext is the context in which the code that manages the newly created object will run. The values are taken from the enumeration CLSCTX.
We can find this enumerators in the ActiveX unit with the following values:
{$EXTERNALSYM CLSCTX_INPROC_SERVER}
  CLSCTX_INPROC_SERVER     = 1;
  {$EXTERNALSYM CLSCTX_INPROC_HANDLER}
  CLSCTX_INPROC_HANDLER    = 2;
  {$EXTERNALSYM CLSCTX_LOCAL_SERVER}
  CLSCTX_LOCAL_SERVER      = 4;
  {$EXTERNALSYM CLSCTX_INPROC_SERVER16}
  CLSCTX_INPROC_SERVER16   = 8;
  {$EXTERNALSYM CLSCTX_REMOTE_SERVER}
  CLSCTX_REMOTE_SERVER     = $10;
  {$EXTERNALSYM CLSCTX_INPROC_HANDLER16}
  CLSCTX_INPROC_HANDLER16  = $20;
  {$EXTERNALSYM CLSCTX_INPROC_SERVERX86}
  CLSCTX_INPROC_SERVERX86  = $40;
  {$EXTERNALSYM CLSCTX_INPROC_HANDLERX86}
  CLSCTX_INPROC_HANDLERX86 = $80;

Example of use:
If we take a look again at the Registry picture above, we will notice that our COM Class uses a LOCAL_SERVER context, and that's the value that we will have to set in the API method.

This simple example will use the COM CLSID to invoke Microsoft Word and open it:

uses
    ActiveX, ComObj, Ole2;
{$R *.dfm}

procedure TForm1.InvokeCLSIDClick(Sender: TObject);
var
    clsid: TCLSID;
    sCLSID: string;
    result: hresult;
    localCLSCTX: Integer;
    IWordApp: IUnknown;
    dispIWord: IDispatch;
    AttrName: POleStr;
    paramArgument: VARIANTARG;
    disparam: DISPPARAMS;
    dispIDs: TDispID;
    did: TDispID;
begin
    // initialize COM
    CoInitialize(nil);
    localCLSCTX := CLSCTX_LOCAL_SERVER;
    try
        // Get CLSID from Microsoft Word
        result := CLSIDFromProgID('Word.Application', clsid);
        // sCLSID := GUIDToString(clsid);
        OleCheck(result);
        // create Microsoft Word instance
        result := CoCreateInstance(clsid, nil, localCLSCTX, IID_IUnknown, IWordApp);
        OleCheck(result);
        // get IDispatch interface of received object
        IWordApp.QueryInterface(IID_IDispatch, dispIWord);

        // Invoke Microsoft Word
        AttrName := 'Visible';
        paramArgument.vt := VT_BOOL;
        paramArgument.vbool := true;
        disparam.cArgs := 1;
        disparam.rgvarg := @paramArgument;
        dispIDs := DISPID_PROPERTYPUT;
        disparam.cNamedArgs := 1;
        disparam.rgdispidNamedArgs := @dispIDs;

        dispIWord.GetIDsOfNames(GUID_NULL, @AttrName, 1, LOCALE_SYSTEM_DEFAULT, @did);
        result := dispIWord.Invoke(did, GUID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, disparam, nil, nil, nil);
        OleCheck(result);
    except
        on e: EOleSysError do
        begin
            ShowMessage('Class Word.Application not found CLSID ' + sCLSID);
        end;
    end;
    // release unused objects
    dispIWord.Release;
    IWordApp.Release;
    // deinitialize COM
    CoUninitialize;
end;


Related links:

0 comments:

Post a Comment