Access violation when calling Delphi DLL from C# in a multi-threaded environment
Asked Answered
H

1

8

I am calling a DLL function written in Delphi XE2 from C# using P/Invoke. It appears to be working when calls are made sequentially from a single thread. However, when multiple threads are calling the function, the C# host application throws System.AccessViolationException seemingly at random.

Why does the code below trigger an access violation and how do I fix this?

Minimum Delphi library code for reproducing the problem:

library pinvokeproblem;

{$R *.res}

uses Windows, SysUtils;

procedure Test(const testByte: byte); stdcall;
begin
  OutputDebugString(PWideChar(IntToStr(testByte)));
end;

exports Test;

end.

Minimum C# host application code to reproduce the problem:

[DllImport(
    "pinvokeproblem.dll",
    CallingConvention = CallingConvention.StdCall,
    EntryPoint = "Test")]
private static extern void Test(byte testByte);

public static void Main(string[] args)
{
    for (int i = 1; i <= 1000; i++) // more iterations = better chance to fail
    {
        int threadCount = 10;
        Parallel.For(1, threadCount, new ParallelOptions { MaxDegreeOfParallelism = threadCount }, test =>
        {
            byte byteArgument = 42;
            Test(byteArgument);
            Console.WriteLine(String.Format("Iteration {0}: {1}", test, byteArgument));
        });
     }
}

Additional information:

  • Platform is x64 Windows 7. C# host application built for x86 in .NET 4.0, Delphi DLL compiled for 32-bit.

  • The library appears to be working OK when used in a multi-threaded Delphi host application.

  • An MSVC version of the DLL with the function signature extern __declspec(dllexport) void __stdcall Test(char testByte) works fine with the C# host (which suggests this is somehow specific to Delphi).

  • The code will not fail if the library function has no return value (void) and arguments.

  • Changing the calling convention in both code to cdecl did not help.

Any ideas would be much appreciated.

Hydrocephalus answered 12/5, 2015 at 11:29 Comment(3)
It looks like maybe the issue is the OutputDebugString(PWideChar(IntToStr(testByte))); in your delphi code. I am not sure you can convert a single byte to a WideChar. I am not a Delphi expert, but something just feels wrong about that statement.Men
@Men That statement is perfectly fineEtam
@mageos: Thank you. That code is valid and the issue does not seem to occur in my Delphi code at all. When I wrapped the Delphi code into a try..catch block, I never managed to catch an exception.Hydrocephalus
P
11

All you have to do is set IsMultiThread to True (as the first line in your DLL's main begin..end block) to switch the memory manager to a thread-safe mode:

IsMultiThread := True;
Peres answered 12/5, 2015 at 12:23 Comment(5)
Welcome. It's rather easy to forget. I just had a very similar problem not too long ago, also with C# interop by coincidence.Peres
@TOndrej: Thank you, it never occurred to me to set this manually (in my defense, normally I use COM interop where class factories set this automatically or otherwise I launch my own threads which also do the same).Hydrocephalus
Exactly, normally you don't need to do this because the relevant units and classes do it for you as part of their initialization.Peres
My code base has IsMultiThread := True in an initialization section of a unit that is always used. Frankly IsMultiThread is something of an anachronism today.Etam
@DavidHeffernan: I agree. Contemporary language runtimes can deal with multi-threaded memory management very well, so I guess there's little to be gained by a dual STA/MTA memory manager model and especially one that occassionally needs to be nudged into one mode or another. At very least, IsMultiThread should be enabled by default.Hydrocephalus

© 2022 - 2024 — McMap. All rights reserved.