在这种情况下,我什么时候需要调用CoInitialize()函数?

8
我正在使用 Delphi XE2 构建一个多线程的 Windows 服务应用程序,它使用 ADO 数据库组件连接 SQL Server。我之前在线程中多次使用过 CoInitialize(nil);,但在这种情况下,我有一个函数不确定是否需要使用。
这个函数叫做 TryConnect,它尝试使用给定的连接字符串连接到数据库。它返回连接成功的 true 或 false。问题是,这个函数将在主服务线程内外都被使用,并且它将创建自己的临时 TADOConnection 组件,这需要使用 CoInitialize...
我的问题是,我需要在这个函数内部调用 CoInitialize 吗?如果我这样做,并且由于服务的执行过程也使用了 CoInitialize,如果我从服务内部调用此函数,它们会干扰吗?TryConnect 函数位于从主服务线程创建的对象内(但最终将移到自己的线程中)。我需要知道在同一线程中两次调用 CoInitialize() (和 CoUninitialize)会产生干扰,并如何正确处理此场景。
以下是代码...
//This is the service app's execute procedure
procedure TJDRMSvr.ServiceExecute(Sender: TService);
begin
  try
    CoInitialize(nil);
    Startup;
    try
      while not Terminated do begin
        DoSomeWork;
        ServiceThread.ProcessRequests(False);
      end;
    finally
      Cleanup;
      CoUninitialize;
    end;
  except
    on e: exception do begin
      PostLog('EXCEPTION in Execute: '+e.Message);
    end;
  end;
end;

//TryConnect might be called from same service thread and another thread
function TDBPool.TryConnect(const AConnStr: String): Bool;
var
  DB: TADOConnection; //Do I need CoInitialize in this function?
begin
  Result:= False;
  DB:= TADOConnection.Create(nil);
  try
    DB.LoginPrompt:= False;
    DB.ConnectionString:= AConnStr;
    try
      DB.Connected:= True;
      Result:= True;
    except
      on e: exception do begin
      end;
    end;
    DB.Connected:= False;
  finally
    DB.Free;
  end;
end;

为了澄清它真正的作用,我可能会遇到这种情况:

CoInitialize(nil);
try
  CoInitialize(nil);
  try
    //Do some ADO work
  finally
    CoUninitialize;
  end;
finally
  CoUninitialize;
end;

3
你读过STAs的文章吗?使用CoInitialize()表示你有STAs。COM必须为每个线程初始化,并且需要平衡调用CoInitialize()/CoUnitialize()。我不知道Delphi中如何工作,但你可能也需要在不同线程之间传递指针。 - Georg Fritzsche
2个回答

24

CoInitialize必须在每个使用COM的线程中调用,无论它是哪个线程,还是它是否有父线程或子线程。如果线程使用COM,它必须调用CoInitialize

正确答案可能是“取决于情况”。由于您知道服务线程已经调用了CoInitialize,如果TryConnect从服务线程调用,则不需要再次调用。如果可能调用它的其他线程也已经调用了CoInitialize,则不需要调用它,因为该函数将在调用线程下运行。

MSDN文档专门回答了这个问题(强调添加):

通常,COM库只在一个线程上初始化一次。在同一线程上对CoInitialize或CoInitializeEx的后续调用将成功,只要它们不尝试更改并发模型,但将返回S_FALSE。为了优雅地关闭COM库,每个成功的CoInitialize或CoInitializeEx调用(包括返回S_FALSE的调用)必须由相应的CoUninitialize调用平衡。然而,使用0调用CoInitialize(或使用COINIT_APARTMENTTHREADED调用CoInitializeEx)的应用程序中的第一个线程必须是最后一个调用CoUninitialize的线程。否则,在STA上进行后续的CoInitialize调用将失败,应用程序将无法工作。
因此,答案是:如果您不确定,请调用CoInitialize。在try..finally块中执行此操作,并在finally中调用CoUnitialize,或者在构造函数中初始化并在析构函数中取消初始化。

这意味着是的,CoInitialize可以被多次调用,只要它被包含在一个try块中,并且每次调用后都应该最终执行CoUninitialize - Jerry Dodge
是的,正如我回答的最后一段所描述的那样。 - Ken White

4

您的服务线程不应该执行任何工作。它应该专门用于响应服务管理器的调用。服务的OnExecute或OnStart/OnStop应该控制一个代表服务功能的"MainWorkThread"的实例化和执行。请参见https://stackoverflow.com/a/5748495/11225进行示例。

主要工作线程可以执行真正的工作,或将其委托给其他线程。每个可以使用COM的线程都应该有CoInitialize/CoUninitialize调用,最简单的方法是在线程的(重写的)Execute方法的最外层try finally块中编码它们。

TDBPool或任何其他使用COM的类不应涉及CoInitialize和CoUninitialize调用。这些方法需要在每个可能使用COM的线程中调用,而一个类不需要也不应该知道它将在哪个线程中执行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接