在ADO异步回调期间无法将对象添加到GlobalInterfaceTable

时间:2013-07-02 15:09:57

标签: delphi com ado delphi-5

我使用以下测试代码将对象添加到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中的析构函数尚未运行,例如断点从未命中:

enter image description here

  

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

但是如果我从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添加虚拟测试对象i would be adding an actual ADO interface。但是当这种方法无效时,我们会将失败的代码修改为简单的代码。

tl; dr :我尝试将一个对象添加到全局接口表中,但是只要我将它放在那里就会被销毁。

Bonus Chatter

我想在将对象放入GIT之前我可能需要手动调用AddRef,但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演示的原因。为了确认它确实是“我的”对象被销毁,我将问题中的引用从TInterfacedObject更改为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;

我的TCounter.Destroy被击中了。

2 个答案:

答案 0 :(得分: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的对象吗?你应该抓住它而你没有机会!

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

答案 1 :(得分:1)

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

但是,从我的测试中可以看出,在git中你也可以在公寓中注册在其他公寓中创建的对象。但部分文件的说法不同。我还在努力。