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:
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.