【问题标题】:Delphi Win32 TXMLDocument can't be instantiated and used from a thread? [duplicate]Delphi Win32 TXMLDocument 不能从线程中实例化和使用? [复制]
【发布时间】:2021-12-01 12:56:09
【问题描述】:

我一直在使用 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://docs.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx) 有关,但我不明白为什么这些可能是个问题。

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

【问题讨论】:

    标签: delphi indy win32com indy10 txmldocument


    【解决方案1】:

    MSXML 是一种基于 COM 的技术。您需要调用CoInitialize/Ex() 来在每个访问COM 接口的线程上下文中初始化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,所以在这种情况下1 初始化 COM 的最佳位置是直接在每个线程的Execute() 方法中。为此,将TIdSchedulerOfThread 派生组件(TIdSchedulerOfThreadDefaultTIdSchedulerOfThreadPool 等)显式分配给TIdTCPServer.Scheduler 属性(可以在设计时完成),然后设置TIdSchedulerOfThread.ThreadClass 属性(必须在运行时完成,在服务器被激活之前)到一个覆盖虚拟BeginExecute()AfterExecute() 方法的TIdThreadWithTask 派生类。

    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:至少在 https://github.com/IndySockets/Indy/issues/6 在 Indy 的未来版本中实现之前。

    【讨论】:

      猜你喜欢
      • 2012-12-03
      • 1970-01-01
      • 1970-01-01
      • 2019-02-11
      • 1970-01-01
      • 1970-01-01
      • 2022-09-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多