Delphi Win32 TXMLDocument无法在线程中实例化和使用?

3

我一直在使用Indy TIdTCPServer对象,在TIdTCPServer.OnExecute事件期间实例化一个TXMLDocument对象实例。当将xml.Active设置为true时,我发现出现异常:

Microsoft MSXML未安装

procedure TForm4.tcpRXExecute(AContext: TIdContext);
var
  sResponseXML : string;
  xml:IXMLDocument;
begin
  // get message from client
  sResponseXML := AContext.Connection.IOHandler.ReadLn;

  xml:=TXMLDocument.Create(nil);
  
  // error here:  "Microsoft MSXML is not installed"
  xml.Active:=true;

  xml.Encoding:='UTF-8';

  xml.LoadFromXML(sResponseXML);

  // use the xml document
  
  //AContext.Connection.IOHandler.WriteLn('... message sent from server :)');
end;

深入分析后,我发现异常是由于TMSXMLDOMDocumentFactory.TryCoCreateInstance()无法创建正确的文档对象实例,尽管它从主线程中应用程序的其他部分接收了相同的GuidList。我不明白为什么在组件的线程中调用时没有实例化该对象。

下面是应该实例化对象的Embarcadero代码:

class function TMSXMLDOMDocumentFactory.TryCoCreateInstance(const GuidList: array of TGUID): IUnknown;
var
  I: Integer;
  Status: HResult;
begin
  for I := Low(GuidList) to High(GuidList) do
  begin
    // never successful if the XML document object was being used from the Execute event handler.
    Status := CoCreateInstance(GuidList[I], nil, CLSCTX_INPROC_SERVER or
      CLSCTX_LOCAL_SERVER, IDispatch, Result);
    if Status = S_OK then Exit;
  end;
end;

我觉得这可能与 CLSCTX_INPROC_SERVERCLSCTX_LOCAL_SERVER 有关 (https://learn.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx),但我不明白为什么它们会成为问题。

即使是这个原因,我该如何在事件处理程序中使用 TXMLDocument

1个回答

9

MSXML是基于COM的技术。每个访问COM接口的线程上下文都需要调用CoInitialize/Ex()来初始化COM库,否则在这种情况下,CoCreateInstance()将失败并显示CO_E_NOTINITIALIZED错误。Delphi的RTL会在主线程中为您初始化COM库,但您必须在工作线程(例如TIdTCPServer使用的线程)中自己进行初始化。

默认情况下,TIdTCPServer为每个客户端连接创建一个新线程。在这种情况下,最简单的初始化COM的位置将是服务器的OnConnect事件(因为OnExecute事件是循环的)。

procedure TForm4.tcpRXConnect(AContext: TIdContext);
begin
  CoInitialize(nil);
end;

procedure TForm4.tcpRXDisconnect(AContext: TIdContext);
begin
  CoUninitialize();
end;

然而,由于 TIdTCPServer 支持线程池技术,并且每个线程只需要初始化一次 COM,所以在这种情况下最好的方式是直接在每个线程的 Execute() 方法中初始化 COM (1)。为此,请明确地将一个派生自 TIdSchedulerOfThread 的组件 (TIdSchedulerOfThreadDefaultTIdSchedulerOfThreadPool 等) 分配给 TIdTCPServer.Scheduler 属性(可以在设计时完成),然后在服务器激活之前的运行时设置 TIdSchedulerOfThread.ThreadClass 属性为一个派生自 TIdThreadWithTask 的类,该类覆盖了虚拟的 BeginExecute()AfterExecute() 方法,如此便可以实现初始化。
type
  TMyThreadWithTask = class(TIdThreadWithTask)
  protected
    procedure BeforeExecute; override;
    procedure AfterExecute; override;
  end;

procedure TMyThreadWithTask.BeforeExecute;
begin
  CoInitialize(nil);
  inherited;
end;

procedure TMyThreadWithTask.AfterExecute;
begin
  inherited;
  CoUninitialize();
end;

procedure TForm4.FormCreate(Sender: TObject);
begin
  IdSchedulerOfThreadDefault1.ThreadClass := TMyThreadWithTask;
end;

1: 至少要等到Indy的未来版本中实现了https://github.com/IndySockets/Indy/issues/6才能使用。


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