Class factories

When an external client creates an automation object, the creation process itself is handled by the class factory. An automation class factory takes care of the registration of the class, so an external client can find the factory. When a request is made to create an object the class factory can perform a well defined check whether the client has a license to create objects of the class.

In this chapter I will take a closer look at the way these class factories are implemented in Delphi and I will show how you can adapt a class factory to the needs of your objects.

Creation of the factory

When the Delphi wizard generates code for the implementation of an automation class you will always find this piece of code in the initialization section of the unit

initialization
TAutoObjectFactory.Create(ComServer, TFileZapper, Class_FileZapper,
ciMultiInstance, tmApartment);
end.

As the server is loaded an object of type tAutoObjectFactory is created. This VCL class is found in ComObj.pas and inherits from TComObjectFactory. The most important part of its declaration reads as follows

TComObjectFactory = class(TObject, IUnknown, IClassFactory, IClassFactory2)
   private
  ....
protected
   function GetProgID: string; virtual;
   function GetLicenseString: WideString; virtual;
   function HasMachineLicense: Boolean; virtual;
   function ValidateUserLicense(const LicStr: WideString): Boolean; virtual;
   { IUnknown }
   function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
   function _AddRef: Integer; stdcall;
   function _Release: Integer; stdcall;
   { IClassFactory }
   function CreateInstance(const UnkOuter: IUnknown; const IID: TGUID; out Obj): HResult; stdcall;
   function LockServer(fLock: BOOL): HResult; stdcall;
   { IClassFactory2 }
   function GetLicInfo(var licInfo: TLicInfo): HResult; stdcall;
   function RequestLicKey(dwResrved: Longint; out bstrKey: WideString): HResult; stdcall;
   function CreateInstanceLic(const unkOuter: IUnknown; const unkReserved: IUnknown; const iid: TIID; const bstrKey: WideString; out vObject): HResult; stdcall;
public
constructor Create(ComS ........................ 

The class implements three interfaces defined in the automation specs: the well known Iunknown, IClassFactory and IClassFactory2. IclassFactory.CreateInstance is the workhorse of the factory, this is the place where the actual automation objects themselves are created.

ICLassFactory2 adds three methods to the factory, all of these have to do with licensing.

The VCL is rich in a variety of classfactories. Every type of COM object, from a pure COM object to an ActiveForm has its own factory. All of these inherit from TCOMobjectFactory, each adds some functionality and has an overloaded constructor to pass the necessary info to create the factory.

Lets take a look at the parameters of the constructor of an automation object :

Some of these parameters were options in the automation object wizard. You can change those values here.

The ComServer object

The comserver object is found in the VCL unit ComServ.Pas. It is created in the initialization section of the unit:

initialization
begin
   OleAutHandle := SafeLoadLibrary('OLEAUT32.DLL');
   ComServer := TComServer.Create;
   if not ModuleIsLib then
      begin
      SaveInitProc := InitProc;
      InitProc := @InitComServer;
      AddTerminateProc(@AutomationTerminateProc);
      end;
end;

First OLEAUT32.dll is loaded, this Windows DLL houses the automation API functions. If the project is a standalone exe (not ModuleIsLib) then the code starts tampering with the initialization code in such a way that the automation system is initialized (by a call to the API function CoInitializeEx) and that the server will try to register itself.

When the server is running the ComServer object has some very usable properties. They provide interesting info on the server but can also be used to influence its behavior.

This ComServer object is passed to all objectfactories present in the server. So when the server is loaded there is one ComServer object and a classfactory for every automation class the server implements.

Creating an own classfactory

Now that we have a overview how classfactories work in Delphi it is time to do something with them. First I will do some work in the registration process of the server. After which I will dive deeper in the actual creation of the automation objects and implement licensing.

I will declare an own class factory which inherits from the VCL classfactory

type
   TFileZapperFactory = class(TAutoObjectFactory)
end;

To use my factory, I will have to change the initialization section of the server

initialization
TFileZapperFactory .Create(ComServer, TFileZapper, Class_FileZapper,
ciMultiInstance, tmApartment);
end.

Now the filezapper server will use my classfactory and I can introduce all desired behavior by adapting the TFileZapperFactory.

Adding info in the registration of your class

In the registration process the Windows registry is updated. Here I would like to see the date on which this registration has taken place. tAutoObjectfactory has an UpdateRegistry method, I will override this and do my work there.

type
TFileZapperFactory = class(TAutoObjectFactory)
   public
   procedure UpdateRegistry(Register: Boolean); override;
...
procedure TFileZapperFactory.UpdateRegistry(Register: Boolean);
   const ValueName = 'DateRegistered';
   begin
   inherited;
   if register then
      CreateRegKey(ProgID, ValueName, DateToStr(Date()));
end;

First I will call inherited. The classes tTypedComObjectFactory and tComObjectFactory, two ancestors of  tAutoObjectFactory, will do some work in the registry after which there will be a registry key HKEY_CLASSES_ROOT\FileManager.FileZapper. When the register parameter has a value of true I want to add the date under this key. ProgID is a handy property of the factory class, it returns the progID FileManager.FileZapper of the class. CreateRegKey is a handy API function which will add the value under the key.

After registration the date of registration can be read with the registry editor:

When unregistering the updateregistry method receives false as parameter value. In my case I don't have to do anything as the inherited code will delete the entire key, including my date value. If you want to do something more precise CreateRegKey has a counterpart named DeleteRegKey.

Registration of the ProgId and registration of the CoClass

The ProgId is the user readable identification of the automation server. It is used by late binding clients to create instances of the class. The value of ProgId itself is obtained by a virtual method  TComObjectFactory.GetProgId. It's implementation in the VCL is very straightforward:

function TComObjectFactory.GetProgID: string;
   begin
   if FClassName <> '' then
      Result := FComServer.ServerName + '.' + FClassName else
      Result := '';
    end;

In case you are not satisfied with this string you are free to override this function. Which will, amongst other things, result in a different registry location.

The updateregistry method uses this ProgId to find the HKEY_CLASSES_ROOT\ProgId key in the registry. An early binding client does not use the ProgId but directly the ClassId. This ClassId is also found in the registry under HKEY_CLASSES_ROOT\CLSID\ClassId. When registering the automation server this entry has to be created as well. It is done in TComObjectFactory.RegisterClassObject. This methods makes a call to CoRegisterClassObject, an automation API function. RegisterClassObject is a static method, so it is not possible to add or change any behavior of this part of the registration process.

Registration problems

A standalone server (.exe) will try to register itself every time it is run. In newer Windows versions it is possible to deny a Windows user the right to write in the registry. As we have seen registering an automation server means writing in the registry. All registry updates get called from tComServer.Initialize. A write failure is handled in a very elegant way :

procedure TComServer.Initialize;
begin
   try
      UpdateRegistry(FStartMode <> smUnregServer);
   except
      on E: EOleRegistrationError do
          // User may not have write access to the registry.
         // Squelch the exception unless we were explicitly told to register.
         if FStartMode = smRegServer then raise;
    end;
if FStartMode in [smRegServer, smUnregServer] then Halt;
ComClassManager.ForEachFactory(Self, FactoryRegisterClassObject);
end;

A failure to write to the registry leads to an exception. And this exception is only propagated when there was an explicit wish (or need) to register. Which can be seen by inspecting the startmode property. So if it is not possible to write to the registry this will be skipped in silence.

Things get a little muddy when the server had not been registered yet. Now the registration just has to take place. After a vague general AV message a more informative dialog pops up :

The registration fails and the program terminates. 

The program has to be installed by an user with write access to the registry, after which it can be used by an user with restricted rights. This elegance is not found in Delphi versions prior to 5.01. There the exception in Initialize is not caught and the program always crashes when the user does not have enough rights.

Making the objects licensed

Licensing is an explicit part of the automation specification. The interface definition of IClassFactory2 adds methods and properties to the classfactory to provide for a standard way of handling illegal use of automation software components. The base of this licensing schema makes the following assumptions :

In the Delphi VCL licensing has been implemented for ActiveX controls. To make an automation object licensed will take some work, I will use a simplified version of the ActiveX implementation as a guideline.

The tAutoObjectFactory class has two properties concerning licensing

To see how licensing works in the VCL I will take a deeper look at implementation of tAutoObjectFactory. The creation of objects is taken care of by the CreatInstance method.

function TComObjectFactory.CreateInstance(const UnkOuter: IUnknown; const IID: TGUID; out Obj): HResult;
  begin
  Result := CreateInstanceLic(UnkOuter, nil, IID, '', Obj);
  end;

All this method does is pass the call to the IClassFactory2 method CreateInstanceLic with an empty key. This method does the work :

function TComObjectFactory.CreateInstanceLic(const unkOuter: IUnknown; const unkReserved: IUnknown; const iid: TIID; const bstrKey: WideString;out vObject): HResult; stdcall;
   begin
.....
   // Check for licensing.
   if FSupportsLicensing and
      ((bstrKey <> '') and (not ValidateUserLicense(bstrKey))) or
      ((bstrKey = '') and (not HasMachineLicense)) then
      begin
      Result := CLASS_E_NOTLICENSED;
      Exit;
      end;
...

The methods first checks if the class does support licensing. If so it will check the key passed. This key is always empty, as passed by CreateInstance. The second check is HasMachineLicense, which is a virtual protected method.

Now I can implement the licensing of automation objects in two steps

I will create a new constructor CreateLicensed, the location of the key parameter is inspired by the VCL objectfactory for ActiveX controls.

type
TFileZapperFactory = class(TAutoObjectFactory)
protected
   function HasMachineLicense: boolean; override;

constructor TFileZapperFactory.CreateLicensed(ComServer: TComServerObject;
    AutoClass: TAutoClass;
    const ClassID: TGUID;
    Instancing: TClassInstancing;
    const LicStr: string; 
    ThreadingModel: TThreadingModel);

   begin
   inherited Create(ComServer, AutoClass, ClassId, Instancing, ThreadingModel);
   LicString := LicStr;
   SupportsLicensing := LicStr <> '';
   end;

This constructor will call the default constructor and set the two licensing properties of the factory object.

Next is the actual implementation of the license check in HasMachineLicense

function TFileZapperFactory.HasMachineLicense: boolean;
var     i: Integer;
        FLicFileStrings : tSTringList;
   begin
   if not SupportsLicensing then
      Result := True
   else
      begin
      result:= False;
      fLicFileStrings:= tSTringList.Create;
      try
         FLicFileStrings.LoadFromFile(ChangeFileExt(ComServer.ServerFileName, '.lic'));
         i := 0;
         while (i < FLicFileStrings.Count) and (not Result) do
            begin
            Result := ValidateUserLicense(FLicFileStrings[i]);
            inc(i);
            end;
      except
          // Licence file not found
      end;
      fLicFileStrings.Free;
      end;
end;

The first check is if the factory does support licensing, when not the function only needs to return true. A stringlist is created to hold the textlines of the license file. To find the file the name of the serverfile can be read from the global ComServer object, which will be changed to the correct filename by ChangeFileExt. The file is loaded into the stringlist which then can be scanned for the license key.

To bring it all together I have to change the initialization section so my new constructor will be used

initialization
TFileZapperFactory.CreateLicensed(ComServer, TFileZapper, Class_FileZapper, 
ciMultiInstance, 'Gekko Software', tmApartment);

Whenever a client will try to create a filemanger object a check on a license file containing a line reading Gekko Software will be made. 

Upon failure a dialog will pop up and the client will not have its automation object.

Making a license harder to crack

What really happens inside the license check is up to you. The format of the contents of the license file is up to you. Inside HasMachineLicense you could decrypt any data read any way from the license file and even perform hardware checks on "dongles" and the like. There is only one rule you have to obey, the acceptance of the license has to be done by a call to ValidateUserLicence. As an automation client could make a direct call to CreateInstanceLic passing the license key itself. In which case your HasMachineLicense method will not be called at all. You can override ValidateUserLicence, but you cannot override CreateInstanceLic.

Where are we ?

We have seen that the creation of automation objects is governed by a classfactory. This classfactory is the place where an automation class communicates with the outside world. It takes care of the registration of the class and it takes care of the actual creation of the automation objects. The object oriented way in which classfactories  are implemented in the Delphi VCL makes it not too difficult to change the behaviour of these factories.

What's next ?