4

我正在使用以下测试代码将对象添加到 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;

我从按钮处理程序调用测试代码:

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

一切都很好。它位于全局接口表中的对象,等待被提取。我知道它仍然在那里,因为 TInterfacedObject 中的析构函数尚未运行,例如断点从未命中:

在此处输入图像描述

注意:如果我现在关闭测试应用程序,那么我将看到Release对我的对象的 GlobalInterfaceTable 调用,将其释放。但那是在关机期间,现在我还在记忆中。

但是,如果我从 ADO 回调中调用相同的测试函数:

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();

使用回调:

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

一旦回调返回,我放置在全局接口表中的对象就会被销毁,并在TInterfacedObject.

实际上,我不会在 ADO 异步回调期间向 GIT添加一个虚拟测试对象,而是添加一个实际的 ADO 接口。但是当这不起作用时,我们将失败的代码精简到最基本的部分。

tl;博士:我尝试将一个对象添加到全局接口表,但一旦我把它放在那里,它就会被破坏。

奖金喋喋不休

我想也许我必须AddRef在将对象放入 GIT 之前手动调用,但是 GIT注册方法会调用AddRef自身。

如何构建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;

使用界面的(不是我的)Delphi 翻译:

  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;

为了完整性:

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

更新一

我非常想避免添加我自己的对象,因为担心有人会认为我的对象搞砸了。这就是为什么我最初使用 Delphi 的内置TInterfacedObject. 为了确认它确实是被破坏的“我的”对象,我将问题中的引用从更改TInterfacedObjectTCounter

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;

TCounter.Destroy的被​​击中了。

4

2 回答 2

1

我解决了这个问题; 这可能是一个(未记录的)基本限制IGlobalInterfaceTable,使其基本上无法使用:

添加到 GlobalInterfaceTable 的任何对象都必须在添加的单元被拆除之前检索。

从单独的线程运行以下命令:

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;

一旦与单独线程关联的单元未初始化:仍在 GlobalInterfaceTable 中的任何对象都将被刷新。

这使得不可能在线程之间发布包含 GIT cookie 值的消息。

即使人为地增加引用计数以防止对象破坏也无济于事:

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;

即使对象没有被销毁(因为我们夸大了引用计数),它也将不再位于全局接口表中。要求它只会失败:

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

返回

0x80070057 参数不正确

您想使用添加到 GIT 中的那个对象吗?你应该在没有机会的时候抓住它!

我讨厌我没有做错任何事情的错误,但它仍然失败。

于 2013-07-02T15:59:14.413 回答
1

我知道它已经过时了,但是您可以简单地在另一个线程中安排一个匿名方法(该方法将在应用程序的出口处关闭),您可以在其中创建对象并将其存储到 GIT 中。就像这个特殊线程的单元将由你管理,对象将住在那个单元中。

此外,从我的测试看来,在 git 中,您也可以从公寓注册在其他公寓中创建的对象。但是部分文件的说明不同。我仍在努力。

于 2014-12-03T23:05:04.227 回答