Recent

Author Topic: LoadLibrary + Threads = Crash?  (Read 4845 times)

Benjiro

  • Guest
LoadLibrary + Threads = Crash?
« on: July 21, 2017, 10:03:37 pm »
A interesting problem that i ran into today.

I am loading a dll under windows. When this code is executed directly under the main program, it runs correctly.
When executed under a thread, it fails just as it leaves the CallDll with a segfault.

Yet, i can see in the DLL loading routine, that it correctly performs the function call and gets the "Hello World".

So i assumed its a ownership issue with the variable passing. Replacing the return with a temporary value. Still crash.
Disable the function call but still load / unload the library. No issue / no crash.

Now, if i chance the stdcall/cdecl to "pascal" and replace the PChar to String everywhere, it does not fail anymore ( what is strange as technically pascal is supposed to be a stdcall ). But the string content is simply garbled junk.

The question becomes more or less, is it not possible to run the same function call from a library in multiple threads. Been driving me up the wall...

Code: Pascal  [Select][+][-]
  1. library dll_hello;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. function Process(Num1: Integer): PChar; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
  6. begin
  7.   Result:= 'Hello World';
  8. end;
  9.  
  10. exports
  11.   Process;
  12.  
  13. begin
  14. end.

Code: Pascal  [Select][+][-]
  1. unit xLoader;
  2. {$mode objfpc}
  3.  
  4. interface
  5.  
  6. function CallDll (LibraryName : String) : String;
  7.  
  8. type
  9.     TMyFunc=function (Num1: Integer): PChar; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF}; // The called function parameters ...  cdecl; for linux??
  10.  
  11. implementation
  12.  
  13. uses DynLibs;
  14.  
  15.  
  16.  
  17. //////////////////////////////////////////////////
  18. // CallDll Function
  19. //////////////////////////////////////////////////
  20.  
  21. function CallDll (LibraryName : String) : String;
  22. var
  23.     MyFunc: TMyFunc;
  24.  
  25.     LibHandle: TLibHandle = dynlibs.NilHandle;
  26.     FuncResult: PChar;  // The called function return type
  27. begin
  28.     LibHandle := LoadLibrary(LibraryName + '.dll');// + SharedSuffix);
  29.  
  30.     if LibHandle = dynlibs.NilHandle then
  31.     begin
  32.         Writeln('DLL was not loaded successfully');
  33.         Exit;
  34.     end;
  35.  
  36.     if LibHandle = 0 then
  37.     begin
  38.         Writeln('DLL was not loaded successfully2');
  39.         Exit;
  40.     end;
  41.  
  42.     Writeln('DLL was loaded successfully');
  43.  
  44.     Pointer(MyFunc) := TMyFunc(GetProcedureAddress(LibHandle, 'Process')); // Call the function "Process"
  45.     if @MyFunc = nil then
  46.       Writeln('GetProcedureAddress called and failed');
  47.  
  48.     Writeln('GetProcedureAddress called');
  49.  
  50.     FuncResult := MyFunc (1);  //Executes the function. The reason for the crash actually.
  51.  
  52.     Writeln('Function called');
  53.  
  54.     Writeln(FuncResult); // Gets correct result here
  55.  
  56.     Writeln('Got results back');
  57.  
  58.     Writeln('Before unload');
  59.  
  60.     MyFunc := nil;
  61.     if LibHandle <> DynLibs.NilHandle then
  62.         if FreeLibrary(LibHandle) then LibHandle:= DynLibs.NilHandle;  //Unload the lib, if already loaded
  63.      
  64.      Writeln('After unload');
  65.  
  66.     Result := FuncResult; // Does not return the correct result to the main unit. Pascal usage = garbled. Other like stdcall / PChar = crash...
  67.     // Crash Point ...
  68. end;
  69.  
  70. end.
  71.  

Code: Pascal  [Select][+][-]
  1.  
  2. // Inside Thread = Crash. Same code in main unit body = works perfectly.
  3. responseString := CallDll('dll_hello');
  4.  
  5.  

Phil

  • Hero Member
  • *****
  • Posts: 2737
Re: LoadLibrary + Threads = Crash?
« Reply #1 on: July 21, 2017, 10:10:54 pm »
Code: Pascal  [Select][+][-]
  1. library dll_hello;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. function Process(Num1: Integer): PChar; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
  6. begin
  7.   Result:= 'Hello World';
  8. end;
  9. [code=pascal]
  10.  
  11. Pass strings back from a dynamic library in a buffer that the calling code passes in. See Win SDK and how MS does it. Often you also pass in the size of the buffer so the library won't write past the end of the buffer.
  12.  
  13.  

Benjiro

  • Guest
Re: LoadLibrary + Threads = Crash?
« Reply #2 on: July 21, 2017, 11:36:11 pm »
Pass strings back from a dynamic library in a buffer that the calling code passes in. See Win SDK and how MS does it. Often you also pass in the size of the buffer so the library won't write past the end of the buffer.

Ok, got it. Was barking up the wrong tree all the time.

Thanks. ;D

Benjiro

  • Guest
Re: LoadLibrary + Threads = Crash?
« Reply #3 on: July 22, 2017, 01:10:15 am »
A small update for any other people who might want a solution.

A more easy way to solve passing strings between modules, is simply using a widestring:

=> "The WideString is a wrapper around the COM BSTR type. It allocates and deallocates using the shared COM allocator".

That solves the issue with the allocation.
 
So in short there are multiple solutions:

* Use variable that is allocated in the caller, past the pointer and a buffer size to the callie.
* Use WideString instead of String.

Phil

  • Hero Member
  • *****
  • Posts: 2737
Re: LoadLibrary + Threads = Crash?
« Reply #4 on: July 22, 2017, 01:13:02 am »
=> "The WideString is a wrapper around the COM BSTR type. It allocates and deallocates using the shared COM allocator".

Note that COM is a Windows-only technology.

Benjiro

  • Guest
Re: LoadLibrary + Threads = Crash?
« Reply #5 on: July 22, 2017, 02:53:31 am »
Note that COM is a Windows-only technology.

Here is a updated version for all, that includes buffer + length and that will work with all platforms:

Code: Pascal  [Select][+][-]
  1. library dll_hello;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses SysUtils;
  6.  
  7. function Process(const Buffer: PChar; const Len: Integer; Num1: Integer): Boolean; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
  8. begin
  9.     StrLCopy(Buffer, 'Hello World', Len);
  10.     Result := True;    
  11. end;
  12.  
  13. exports
  14.   Process;
  15.  
  16. begin
  17. end.

Code: Pascal  [Select][+][-]
  1. unit xLoader;
  2. {$mode objfpc}{$H+}
  3.  
  4. interface
  5.  
  6. function CallDll (LibraryName : String) : String;
  7.  
  8. type
  9.     TMyFunc = function (const Buffer: PChar; const Len: Integer; Num1: Integer): Boolean; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF}; // The called function parameters ...  cdecl; for linux??
  10.  
  11. implementation
  12.  
  13. uses DynLibs, sysutils;
  14.  
  15. //////////////////////////////////////////////////
  16. // CallDll Function
  17. //////////////////////////////////////////////////
  18.  
  19. function CallDll (LibraryName : String) : String;
  20. var
  21.     MyFunc: TMyFunc;
  22.  
  23.     LibHandle: TLibHandle = dynlibs.NilHandle;
  24.     FuncBuffer: String;
  25.     BuffLen: Integer;  
  26. begin
  27.     LibHandle := LoadLibrary(LibraryName + '.dll');// + SharedSuffix);
  28.  
  29.     if LibHandle = dynlibs.NilHandle then
  30.     begin
  31.         Writeln('DLL was not loaded successfully');
  32.         Exit;
  33.     end;
  34.  
  35.     Pointer(MyFunc) := TMyFunc(GetProcedureAddress(LibHandle, 'Process')); // Call the function "Process"
  36.     if @MyFunc = nil then
  37.       Writeln('GetProcedureAddress called and failed');
  38.  
  39.     FuncBuffer := '';
  40.     BuffLen := 55;
  41.  
  42.     SetLength(FuncBuffer, BuffLen);     // Allocate space for return value
  43.  
  44.     MyFunc ( PChar(FuncBuffer), BuffLen, 1);  //Executes the function
  45.  
  46.     MyFunc := nil;
  47.     if LibHandle <> DynLibs.NilHandle then
  48.         if FreeLibrary(LibHandle) then LibHandle:= DynLibs.NilHandle;  //Unload the lib, if already loaded
  49.      
  50.     Result := FuncBuffer;
  51. end;
  52.  
  53. end.

I hope this is off use for people in the future.

Phil

  • Hero Member
  • *****
  • Posts: 2737
Re: LoadLibrary + Threads = Crash?
« Reply #6 on: July 22, 2017, 03:00:46 am »
Here is a updated version for all, that includes buffer + length and that will work with all platforms:

You're assuming PChar points to single-byte chars? Not the case with Delphi. Better use PAnsiChar.

See the ndfd.pas library and ndfd_types.pas files in Parts 2 and 3 here for more ideas. It's generally a good idea to fully define your types. See FPC ctypes.pp unit also.

https://macpgmr.github.io/MacXPlatform/PascalDynLibs.html




ASerge

  • Hero Member
  • *****
  • Posts: 2240
Re: LoadLibrary + Threads = Crash?
« Reply #8 on: July 22, 2017, 08:53:07 am »
Here is a updated version for all, that includes buffer + length and that will work with all platforms:
Code: Pascal  [Select][+][-]
  1. //...
  2. function Process(const Buffer: PChar; const Len: Integer; Num1: Integer): Boolean; {$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
  3. begin
  4.     StrLCopy(Buffer, 'Hello World', Len);
  5.     Result := True;    
  6. end;
  7. //...
External data may be incorrect. Behavior, as in WinApi
Code: Pascal  [Select][+][-]
  1. uses Windows, SysUtils;
  2.  
  3. function Process(const Buffer: PAnsiChar; SizeIn: Integer; SizeOut: PInteger): Boolean;{$IFDEF MSWINDOWS}stdcall{$ELSE}cdecl{$ENDIF};
  4. const
  5.   CSomeData = 'Hello World';
  6. begin
  7.   if Buffer = nil then
  8.   begin
  9.     if (SizeIn <> 0) or not Assigned(SizeOut) then
  10.     begin
  11.       SetLastError(ERROR_INVALID_PARAMETER);
  12.       Exit(False);
  13.     end;
  14.     SizeOut^ := Length(CSomeData) + 1; // Required buffer include last #0
  15.     Exit(True);
  16.   end;
  17.   if SizeIn <= Length(CSomeData) then
  18.   begin
  19.     if Assigned(SizeOut) then
  20.       SizeOut^ := Length(CSomeData) + 1;
  21.     SetLastError(ERROR_INSUFFICIENT_BUFFER);
  22.     Exit(False);
  23.   end;
  24.   if Assigned(SizeOut) then
  25.     SizeOut^ := Length(CSomeData); // On success return only length, without last #0
  26.   StrLCopy(Buffer, CSomeData, Length(CSomeData));
  27.   SetLastError(ERROR_SUCCESS);
  28.   Result := True;
  29. end;

 

TinyPortal © 2005-2018