3

我一直在使用 IndyTIdTCPServer对象并在事件TXMLDocument期间实例化对象实例TIdTCPServer.OnExecutexml.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_SERVERhttps://docs.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx)有关,但我不明白为什么这些可能是一个问题。

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

4

1 回答 1

8

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 应该每个线程只初始化一次,在这种情况下初始化 COM的最佳位置1是直接在每个线程的Execute()方法中。要做到这一点,显式地将一个TIdSchedulerOfThread衍生组件(TIdSchedulerOfThreadDefault,TIdSchedulerOfThreadPool等)分配给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之前。

于 2021-10-13T03:38:17.997 回答