【问题标题】:Cannot add objects to GlobalInterfaceTable during ADO async callback在 ADO 异步回调期间无法将对象添加到 GlobalInterfaceTable
【发布时间】:2013-07-02 15:09:57
【问题描述】:

我正在使用以下测试代码向 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 中的析构函数尚未运行,例如断点从未命中:

注意:如果我现在关闭测试应用程序,那么我将在我的对象上看到 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 异步回调 i would be adding an actual ADO interface 期间向 GIT 添加虚拟 test 对象。但是当这不起作用时,我们会将失败的代码精简到最简单的部分。

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

奖金聊天

我想也许我必须在将对象放入 GIT 之前手动调用 AddRef,但 GIT register 方法会调用 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 进行演示的原因。为了确认它确实是被销毁的 "my" 对象,我将问题中的引用从 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 被击中了。

【问题讨论】:

  • 相信您需要持有对 GIT 的引用。我会使用这样的东西:stackoverflow.com/a/1071393/800214。顺便说一句,我对您的其他问题的评论仍然有效,传递数据(如 PODO 对象) - 更简单,尤其是在涉及线程时......
  • 很好地介绍了问题。但是,它缺少一件事:表明您目睹在断点处被销毁的对象与您创建并放入接口表中的对象相同。
  • 需要考虑的事项:线程模型。当您直接调用您的函数时,ADO 使用的线程是否与似乎工作的任何线程模型兼容?
  • @whosrdaddy 您不必持有对 GIT 接口的引用。但是更改了问题代码以保留对它的引用:只是为了避免混淆。
  • @RobKennedy 将 TInterfacedObject 更改为测试对象 TCounter。更新的问题。此外,IGlobalInterfaceTable 的目的是在同一进程中的线程和线程模型之间移动对象;唯一的工作就是允许公寓之间的东西。并且没有任何线程不安全代码:CoCreateInstanceInterlockedIncrement/Decrement

标签: delphi com ado delphi-5


【解决方案1】:

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

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

【讨论】:

  • 您应该使用任何相关代码来支持您的回答。
【解决方案2】:

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

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

从单独的线程运行以下内容:

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 中的那个对象吗?你应该在没有机会的时候抓住它!

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

【讨论】:

  • 看起来像 it's documented:“调用 [RegisterInterfaceInGlobal] 的单元必须保持活动状态,直到相应调用 RevokeInterfaceFromGlobal。”不过,很抱歉你被它烫伤了。
  • @RobKennedy 我发誓我在发现限制后阅读了该文档 3 次,然后你就找到了。 “到底在哪里?哦,关于 add 方法。”
猜你喜欢
  • 2020-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-26
  • 2011-08-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多