Unexpected Thread behaviour calling Delphi DLL

2019-02-15 01:08发布

问题:

Continue from my other question: How do I pass and retrieve memory stream from my Application to/from DLL?

I have wrote the DLL using IStream as input/output. The DLL uses IXMLDocument (which at first I thought was related to the follow problem) Tested it, and it worked well in the main UI. Problems began when I was calling the DLL from a worker thread.

The DLL:

library MyDLL;

uses
  Windows,
  Variants,
  SysUtils,
  Classes,
  AxCtrls,
  ActiveX,
  XMLDoc,
  XMLIntf;

{$R *.res}    

procedure Debug(V: Variant);
begin
  OutputDebugString(PChar(VarToStr(V)));
end;

procedure DoProcess(InStream, OutStream: TStream);
var
  Doc: IXMLDocument;
begin
  InStream.Position := 0;
  Doc := TXMLDocument.Create(nil);
  Doc.LoadFromStream(InStream);
  // plans to do some real work...
  OutStream.Position := 0;
  Debug('MyDLL DoProcess OK');
end;

function Process(AInStream, AOutStream: IStream): Integer; stdcall;
var
  InStream, OutStream: TStream;
begin
  try
    InStream := TOleStream.Create(AInStream);
    try
      OutStream := TOleStream.Create(AOutStream);
      try
        DoProcess(InStream, OutStream);
        Result := 0;
      finally
        OutStream.Free;
      end;
    finally
      InStream.Free;
    end;
  except
    on E: Exception do
    begin
      Result := -1;
      Debug('MyDLL Error: ' + E.Message);
    end;
  end;
end;

exports
  Process;

begin
end.

And my caller application:

implementation

uses
  ActiveX,ComObj;

{$R *.dfm}

procedure Debug(V: Variant);
begin
  OutputDebugString(PChar(VarToStr(V)));
end;

const
  MyDLL = 'MyDLL.dll';

{$DEFINE STATIC_DLL}
{$IFDEF STATIC_DLL}
function Process(AInStream, AOutStream: IStream): Integer; stdcall; external MyDLL;
{$ENDIF}

type
  // Dynamic
  TDLLProcessProc = function(AInStream, AOutStream: IStream): Integer; stdcall;

function DLLProcess(AInStream, AOutStream: TStream): Integer;
var
  InStream, OutStream: IStream;
  Module: HMODULE;
  DLLProc: TDLLProcessProc;
begin
  InStream := TStreamAdapter.Create(AInStream, soReference);
  OutStream := TStreamAdapter.Create(AOutStream, soReference);
{$IFDEF STATIC_DLL}
  Result := Process(InStream, OutStream); // Static
  Exit;
{$ENDIF}
  // Dynamic load DLL ...
  Module := LoadLibrary(MyDLL);
  if Module = 0 then RaiseLastOSError;
  try
    DLLProc := GetProcAddress(Module, 'Process');
    if @DLLProc = nil then RaiseLastOSError;
    Result := DLLProc(InStream, OutStream);
  finally
    FreeLibrary(Module);
  end;
end;

type
  TDLLThread = class(TThread)
  private
    FFileName: string;
  public
    constructor Create(CreateSuspended: Boolean; AFileName: string);
    procedure Execute(); override;
  end;

constructor TDLLThread.Create(CreateSuspended: Boolean; AFileName: string);
begin
  FreeOnTerminate := True;
  FFileName := AFileName;
  inherited Create(CreateSuspended);
end;

procedure TDLLThread.Execute;
var
  InStream, OutStream: TMemoryStream;
  RetValue: Integer;
begin
  try
    //CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
    CoInitialize(nil);
    try
      InStream := TMemoryStream.Create;
      try
        InStream.LoadFromFile(FFileName);
        OutStream := TMemoryStream.Create;
        try
          RetValue := DLLProcess(InStream, OutStream);
          Sleep(0);
          Debug('TDLLThread Result=> ' + IntToStr(RetValue));
          if RetValue = 0 then
          begin
            Debug('TDLLThread OK');
          end;
        finally
          OutStream.Free;
        end;
      finally
        InStream.Free;
      end;
    finally
      CoUninitialize;
    end;
  except
    on E: Exception do
    begin
      Debug('TDLLThread Error: ' + E.Message);
    end;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject); // Test
var
  I: Integer;
begin
  for I := 1 to 5 do
    TDLLThread.Create(False, '1.xml');
end;

When running some tests I sometimes get Access Violations which even the exceptions blocks can't catch. And the program simply crashes with Runtime error 216 at xxxxxxx or Invalid pointer operation.

I have tried both static and dynamic DLL linking (figured maybe the dynamic linking has race condition in the LoadLibrary/FreeLibrary).

First I thought IXMLDocument was the main issue:

Doc := TXMLDocument.Create(nil);
Doc.LoadFromStream(InStream);

This sometimes randomly failed with no apparent reason with:

Invalid at the top level of the document.

Or:

A name was started with an invalid character.

I thought maybe it used some shared resources. but even omitting these lines caused AVs!

So the DLL is practically doing nothing special. I also Don't see anything special which could infect DLLMain.

I have no Idea what is going on... Can someone suggest how to handle this situation? (Can someone reproduce this behavior?)


EDIT: I just wanted to add a related question (with similar IsMultiThread solution): Delphi DLL - thread safe

And some tips about IsMultiThread: IsMultiThread Variable

回答1:

The memory manager in Delphi has optimisations for single threaded use. These are enabled by default. If your code is multi-threaded then this optimisation needs to be disabled. Do that by setting IsMultiThread to True.

In a module that creates a Delphi thread, the framework sets IsMultiThread to True when a thread is created. In your program the threads are created by the host and so nothing in the library sets IsMultiThread to True. So you must do that explicitly in the DLL. In the main section of the library .dpr file write this:

begin
  IsMultiThread := True;
end.