Search code examples
delphiindywin32comindy10txmldocument

Delphi Win32 TXMLDocument can't be instantiated and used from a thread?


I've been working with an Indy TIdTCPServer object and instantiating a TXMLDocument object instance during the TIdTCPServer.OnExecute event. I find it quite surprising that I get an exception when xml.Active is set to 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;

Looking deeper, I found that the exception occurs because TMSXMLDOMDocumentFactory.TryCoCreateInstance() is unable to create the correct document object instance despite receiving the same GuidList as it received in other parts of the application from the main thread. I don't understand why the object isn't instantiated if called from the component's thread.

Here's the Embarcadero code where the object should be instantiated:

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;

I expect it must have something to do with CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER (https://learn.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx), but I don't see why these could be a problem.

Even if that was the cause, how can I use TXMLDocument from within that event handler?


Solution

  • MSXML is a COM-based technology. You need to call CoInitialize/Ex() to initialize the COM library in every thread context that accesses COM interfaces. Otherwise, in this case, CoCreateInstance() will fail with a CO_E_NOTINITIALIZED error. Delphi's RTL initializes the COM library for you in the main thread, but you have to do it yourself in worker threads, such as those used by TIdTCPServer.

    By default, TIdTCPServer creates a new thread for each client connection. In this case, the easiest place to initialize COM would be in the server's OnConnect event (since the OnExecute event is looped).

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

    However, since TIdTCPServer supports thread-pooling, and COM should be initialized only once per thread, the best place to initialize COM in this case 1 is directly in each thread's Execute() method. To do that, explicitly assign a TIdSchedulerOfThread-derived component (TIdSchedulerOfThreadDefault, TIdSchedulerOfThreadPool, etc) to the TIdTCPServer.Scheduler property (can be done at design-time), and then set the TIdSchedulerOfThread.ThreadClass property (must be done at runtime, before the server is activated) to a TIdThreadWithTask-derived class that overrides the virtual BeginExecute() and AfterExecute() methods.

    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: at least until https://github.com/IndySockets/Indy/issues/6 is implemented in a future version of Indy.