Delphi Win32 TXMLDocument 无法从线程中实例化和使用?
Delphi Win32 TXMLDocument can't be instantiated and used from a thread?
我一直在使用 Indy TIdTCPServer
对象并在 TIdTCPServer.OnExecute
事件期间实例化 TXMLDocument
对象实例。当 xml.Active
设置为 true:
时,我发现异常令人惊讶
Microsoft MSXML is not installed
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_SERVER
或 CLSCTX_LOCAL_SERVER
(https://docs.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx) 有关,但我不明白为什么这些会成为问题。
即使那是原因,我如何从该事件处理程序中使用 TXMLDocument
?
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 每个线程只应初始化一次,因此 最好 在这种情况下初始化 COM 的地方 1直接在每个线程的Execute()
方法中。为此,明确地将 TIdSchedulerOfThread
派生的组件(TIdSchedulerOfThreadDefault
、TIdSchedulerOfThreadPool
等)分配给 TIdTCPServer.Scheduler
属性(可以在设计时完成),然后将 TIdSchedulerOfThread.ThreadClass
属性(必须在运行时完成,在服务器激活之前)设置为 TIdThreadWithTask
-派生的 class 覆盖虚拟 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:至少在 https://github.com/IndySockets/Indy/issues/6 在 Indy 的未来版本中实现之前。
我一直在使用 Indy TIdTCPServer
对象并在 TIdTCPServer.OnExecute
事件期间实例化 TXMLDocument
对象实例。当 xml.Active
设置为 true:
Microsoft MSXML is not installed
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_SERVER
或 CLSCTX_LOCAL_SERVER
(https://docs.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx) 有关,但我不明白为什么这些会成为问题。
即使那是原因,我如何从该事件处理程序中使用 TXMLDocument
?
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 每个线程只应初始化一次,因此 最好 在这种情况下初始化 COM 的地方 1直接在每个线程的Execute()
方法中。为此,明确地将 TIdSchedulerOfThread
派生的组件(TIdSchedulerOfThreadDefault
、TIdSchedulerOfThreadPool
等)分配给 TIdTCPServer.Scheduler
属性(可以在设计时完成),然后将 TIdSchedulerOfThread.ThreadClass
属性(必须在运行时完成,在服务器激活之前)设置为 TIdThreadWithTask
-派生的 class 覆盖虚拟 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:至少在 https://github.com/IndySockets/Indy/issues/6 在 Indy 的未来版本中实现之前。