Smur
Smur

Reputation: 3113

Accessing Delphi DLL throwing ocasional exception

When I'm calling a Dll method it sometimes throws an exception, and sometimes doesn't.

I'm calling it like this:

public class DllTest
{

    [DllImport(@"MyDll.dll")]
    public extern static string MyMethod(string someStringParam);
}


class Program
{       

    static void Main(string[] args)
    {
        DllTest.MyMethod("SomeString");
    }
}

And the exception I get sometimes is this:

AccessViolationException

Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

Does anyone have any idea why I only get this exception sometimes? Why does it run smoothly sometimes?

Upvotes: 1

Views: 2403

Answers (2)

David Heffernan
David Heffernan

Reputation: 612954

You clearly have a mismatch between the p/invoke code and the Delphi code. You have not shown the Delphi code but the C# code is enough to know what the Delphi code should look like.

Your DllImport attribute uses default values for calling convention and character set. This means that the calling convention is stdcall and the character set is ANSI. You have not specified any marshalling attributes so default marshalling must be used.

Therefore your Delphi code must look like this:

function MyMethod(someStringParam: PChar): PChar; stdcall;
begin
  Result := ??;
end;

And now here's the problem. The p/invoke marshaller treats a string return value in a very special way. It assumes that it is the responsibility of the p/invoke marshaller to deallocate the return value's memory. And it has to use the same allocator as the native code. The assumption made by the marshaller is that the shared COM allocator will be used.

So the rule is that the native code must allocate the memory with the COM allocator by calling CoTaskMemAlloc. My bet is that your code does not do that and that will certainly result in errors.

Here is an example of how you could make a native Delphi function that works with the C# signature in your code.

function MyMethod(someStringParam: PChar): PChar; stdcall;
var
  Size: Integer;
begin
  Size := SizeOf(Char)*(StrLen(someStringParam)+1);//+1 for zero-terminator
  Result := CoTaskMemAlloc(Size);
  Move(someStringParam^, Result^, Size);
end;

Whilst you could adopt this approach I recommend an alternative. Marshal all your strings as BSTR on the C# side and WideString on the Delphi side. These are matching types which are also allocated by the COM allocator. Both parties know exactly what to do with these types and will make your life easier.

Unfortunately, you cannot return a WideString from a Delphi function across an interop boundary because Delphi uses a different ABI for function return values. More details of this issue can be found in my question Why can a WideString not be used as a function return value for interop?

So to work around that, we can declare the return type from the Delphi code to be TBStr. Your code would then look like this:

C#

[DllImport(@"MyDll.dll")]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string MyMethod(
    [MarshalAs(UnmanagedType.BStr)]
    string someStringParam
);

Delphi

function MyMethod(someStringParam: WideString): TBStr; stdcall;
begin
  Result := SysAllocString(POleStr(someStringParam));
end;

Upvotes: 13

alex
alex

Reputation: 21

For me, marshalling of Delphi WideString to .Net string using UnmanagedType.BStr works very well in case of In and Out parameters. But it fails in case of functions returning strings. I have a Delphi functions -

function WS(val: WideString): WideString; stdcall;
begin
  result := val;
end;

procedure WS1(out result: widestring); stdcall;
begin
  result := 'ABCDE';
end;

and a correspondent .Net declarations -

[DllImport(@"my.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string WS(
  [MarshalAs(UnmanagedType.BStr)]
  string val
  );

[DllImport("my.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
static extern void WS1(
  [MarshalAs(UnmanagedType.BStr)]
  out string res);

calling to WS1() works just fine, while WS() raises an exception. And exception depends on what units are included in a Delphi project. If "SysUtils" or "Classes" are included - the .Net application raises a SEHException "External component has thrown an exception", if both units are excluded, the application shows a "Runtime error 203 at 009C43B4" error dialog and terminates its execution. BTW, usage of "ShareMem" unit doesn't change anything.

Upvotes: 2

Related Questions