Question

i am using the follow test code to add an object to the GlobalInterfaceTable:

function TForm1.AddSomethingToGit(): DWORD;
var
    unk: IUnknown;
    cookie: DWORD;
    git: IGlobalInterfaceTable;
begin
    unk := TCounter.Create;

    if FGit = nil then
    begin
        git := CoGlobalInterfaceTable.Create;
        Fgit := git; //yes, i didn't use InterlockedCompareExchange. Can you imagine trying to explain that syntax to people?
    end;

    OleCheck(Fgit.RegisterInterfaceInGlobal(unk, IUnknown, {out}cookie));

    Result := cookie;
end;

And i call the test code from a button handler:

procedure TForm1.Button1Click(Sender: TObject);
begin
   AddSomethingToGit();
end;

And everything is good. The object it sitting in the global interface table, waiting to be extracted. i know it is still in there because the the destructor in TInterfacedObject has not been run e.g. breakpoint never hit:

enter image description here

Note: if i close the test app right now, then i will see the GlobalInterfaceTable call Release on my object, freeing it. But that's during shutdown, for now i'm still in memory.

But if i call the same test function from an ADO callback:

conn := CreateTrustedSqlServerConnection(serverName, defaultDatabaseName);
dataSet := TADODataSet.Create(nil);
dataSet.Connection := conn;
dataSet.OnFetchComplete := FetchComplete;
dataSet.CursorLocation := clUseClient;
dataSet.CommandText := 'WAITFOR DELAY ''00:00:03''; SELECT GETDATE() AS foo';
dataSet.CommandType := cmdText;
dataSet.ExecuteOptions := [eoAsyncFetch];
dataSet.Open();

with the callback:

procedure TForm1.FetchComplete(DataSet: TCustomADODataSet;
    const Error: Error; var EventStatus: TEventStatus);
begin
   AddSomethingToGit();
end;

the object i placed into the Global Interface Table is destroyed as soon as the callback returns, hitting the breakpoint in TInterfacedObject.

In reality i wouldn't be adding a dummy test object to the GIT during the ADO async callback, i would be adding an actual ADO interface. But when that didn't work we trim the failing code down to the bare-bones.

tl;dr: i try to add an object to the Global Interface Table, but it gets destroyed as soon as i put it in there.

Bonus Chatter

i thought maybe i had to manually call AddRef before placing the object into the GIT, but the GIT register method calls AddRef itself.

How to construct an IGlobalInterfaceTable:

class function CoGlobalInterfaceTable.Create: IGlobalInterfaceTable;
begin
    // There is a single instance of the global interface table per process, so all calls to this function in a process return the same instance.
    OleCheck(CoCreateInstance(CLSID_StdGlobalInterfaceTable, nil, CLSCTX_INPROC_SERVER, IGlobalInterfaceTable, Result));
end;

with the (not my) Delphi translation of the interface:

  IGlobalInterfaceTable = interface(IUnknown)
     ['{00000146-0000-0000-C000-000000000046}']
     function RegisterInterfaceInGlobal(pUnk: IUnknown; const riid: TIID; out dwCookie: DWORD): HRESULT; stdcall;
     function RevokeInterfaceFromGlobal(dwCookie: DWORD): HRESULT; stdcall;
     function GetInterfaceFromGlobal(dwCookie: DWORD; const riid: TIID; out ppv): HRESULT; stdcall;
   end;

And for completeness:

const
  CLSID_StdGlobalInterfaceTable : TGUID = '{00000323-0000-0000-C000-000000000046}';

Update One

i desperately wanted to avoid adding my own object, for fear someone would think my object was screwed up. That's why originally i demonstrated with Delphi's in-built TInterfacedObject. In order to confirm that it really is "my" object that's being destroyed, i changed references in the question from TInterfacedObject to TCounter:

TCounter = class(TInterfacedObject, IUnknown)
private
   FFingerprint: string;
public
   constructor Create;
   destructor Destroy; override;
end;

{ TCounter }

constructor TCounter.Create;
begin
   inherited Create;
   FFingerprint := 'Rob Kennedy';
end;

destructor TCounter.Destroy;
begin
   if FFingerprint = 'Rob Kennedy' then
      Beep;
   inherited;
end;

And my TCounter.Destroy is hit.

Was it helpful?

Solution 2

i figured out the problem; it's likely an (undocumented) fundamental limitation of IGlobalInterfaceTable that makes it essentially unusable:

Any object added to the GlobalInterfaceTable must be retrieved before the adding apartment is torn down.

Running the following from a separate thread:

procedure TAddToGitThread.Execute;
var
   unk: IUnknown;
   cookie: DWORD;
   git: IGlobalInterfaceTable;
begin
    CoInitialize(nil);
    try
        unk := TCounter.Create;

        git := CoGlobalInterfaceTable.Create;
        OleCheck(git.RegisterInterfaceInGlobal(unk, IUnknown, {out}cookie));
        unk := nil;
    finally
        CoUninitialize; <--objects added from this apartment Released
    end;
end;

As soon as the apartment associated with the separate thread is uninitialized: any objects still in the GlobalInterfaceTable are flushed.

This makes it impossible the post messages containing GIT cookie values between threads.

Even artificially inflating the reference count, to prevent object destruction won't help you:

procedure TAddToGitThread.Execute;
var
   unk: IUnknown;
   cookie: DWORD;
   git: IGlobalInterfaceTable;
begin
   CoInitialize(nil);
   try
      unk := TCounter.Create;
      unk._AddRef; //inflate reference count to prevent destruction on apartment teardown

      git := CoGlobalInterfaceTable.Create;
      OleCheck(git.RegisterInterfaceInGlobal(unk, IUnknown, {out}cookie));
      unk := nil;
   finally
      CoUninitialize; <--object added from this apartment is Released, but not freed
   end;
end;

Even though the object was not destroyed (because we inflated the reference count), it will no longer be in the global interface table. Asking for it will just fail:

   hr := _git.GetInterfaceFromGlobal(_cookie, IUnknown, {out}unk);

returns

0x80070057 The parameter is incorrect

You wanted to use that object added to the GIT? You had should have grabbed it while you didn't have the chance!

i hate bugs where i do nothing wrong, but it still fails.

OTHER TIPS

I know it's old, but you can simply schedule an anonymous method in another thread (that will go down at the exit of the application) where you create the object and store it into the GIT. Like that the apartment of this special thread will be managed by you and the object will live in that apartment.

Moroever, it seems from my tests that in git you can register objects created in other apartments too from an apartment. But part of the documentations states differently. I'm still working on this.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top