Search code examples
c#delphidllinteropunmanaged

Calling a Delphi DLL from C# - Issues


First: I read a lot of similar questions, tried a lot of different solutions, yet I couldn't achieve to work with the Delphi DLL.

The Delphi DLL provides the following method:

procedure FetchData(var infoIn: INParameter; var infoOut: OUTParameter; var details: OUTDetails); stdcall; external DLL_Name;

The types are defined like this:

type
  Info1 = record
    Param1: Byte;
    Param2: Byte;
    Param3: Byte;
    Param4: Byte;
    Param5: integer;
  end;
  PInfo1 = ^Info1;
  
  Info2 = record
    Param1: Byte;
    Param2: integer;
    Param3: integer;
    Param4: integer;
    Param5: long;
    Param6: string;
    Param7: integer;
    Param8: array [0 .. 8] of integer;
    Param9: string;
  end;
  PInfo2 = ^Info2;

  INParameter = record
    Param1: integer;
    Param2: integer;
    Param3: integer;
    Param4: Byte;
    Param5: Byte;
    Param6: Byte;
    Param7: Byte;
    Param8: Byte;
    Param9: Byte;
    Param10: Byte;
    Param11: Byte;
    Param12: Byte;
    Param13: Byte;
    Param14: Byte;
    Param15: Byte;
    Param16: Byte;
    Param17: Byte;
    Param18: Byte;
    Param19: Byte;
    Param20: Byte;
    Param21: Byte;
    Param22: Byte;
    Param23: integer;
    Param24: integer;
    Param25: Byte;
    Param26: Byte;
    Param27: Byte;
    Param28: Byte;
    Param29: Byte;
    Param30: Byte;
    Param31: Byte;
    Param32: Byte;
    Param33: integer;
    Param34: PInfo1;
    Param35: PInfo2;
  end;
  
  OUTParameter = record
    Param1: integer;
    Param2: integer;
    Param3: integer;
    Param4: integer;
    Param5: bool;
    Param6: bool;
    Param7: integer;
    Param8: integer;
    Param9: bool;
    Param10: bool;
    Param11: bool;
    Param12: bool;
    Param13: bool;
    Param14: bool;
    Param15: integer;
    Param16: integer;
    Param17: integer;
    Param18: bool;
    Param19: bool;
    Param20: integer;
  end;
  
  Pos = record
    Param1: integer;
    Param2: integer;
    Param3: Byte;
    Param4: Byte;
  end;
  
  OUTDetails = record
    Poss: array [0 .. 3, 0 .. 20] of Pos;
  end;

I've translated the types to C# like this:

public structure Info1
{
    public Byte Param1;
    public Byte Param2;
    public Byte Param3;
    public Byte Param4;
    public int Param5;
}
    
public structure Info2
{
    public Byte Param1;
    public int Param2;
    public int Param3;
    public int Param4;
    public long Param5;
    public string Param6;
    public int Param7;
    public int[] Param8;
    public string Param9;
}
    
public structure INParameter
{
    public int Param1;
    public int Param2;
    public int Param3;
    public Byte Param4;
    public Byte Param5;
    public Byte Param6;
    public Byte Param7;
    public Byte Param8;
    public Byte Param9;
    public Byte Param10;
    public Byte Param11;
    public Byte Param12;
    public Byte Param13;
    public Byte Param14;
    public Byte Param15;
    public Byte Param16;
    public Byte Param17;
    public Byte Param18;
    public Byte Param19;
    public Byte Param20;
    public Byte Param21;
    public Byte Param22;
    public int Param23;
    public int Param24;
    public Byte Param25;
    public Byte Param26;
    public Byte Param27;
    public Byte Param28;
    public Byte Param29;
    public Byte Param30;
    public Byte Param31;
    public Byte Param32;
    public int Param33;
    public PInfo1 Param34;
    public PInfo2 Param35;
}
    
public structure OUTParameter
{
    public int Param1;
    public int Param2;
    public int Param3;
    public int Param4;
    public bool Param5;
    public bool Param6;
    public int Param7;
    public int Param8;
    public bool Param9;
    public bool Param10;
    public bool Param11;
    public bool Param12;
    public bool Param13;
    public bool Param14;
    public int Param15;
    public int Param16;
    public int Param17;
    public bool Param18;
    public bool Param19;
    public int Param20;
}
    
public structure Pos
{
    public int Param1;
    public int Param2;
    public Byte Param3;
    public Byte Param4;
}
    
public structure Details
{
    public Pos[] Poss;
}

Then, in a console app, I've added the DLL extern methods definition:

[DllImport("DelphiDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern void FetchData(IntPtr paramIn,
                                      IntPtr paramOut,
                                      IntPtr detailsOut);

I tried different definitions, using:

  • ref INParameter, ref OUTParameter
  • out OUTParameter
  • [Out, MarshalAs(UnmanagedType.Struct)] out OUTParameter

I finally stated that Delphi needs a pointer and will write data in it. So I gave IntPtr.

For the input/output parameters Pointers, I did this:

IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(oneOUTParameter));
Marshal.StructureToPtr(oneOUTParameter, pntForOneOUTParameter, false);

And called FetchData(pntForINParameter, pntForOUTParameter, pntForOUTDetailParameter);

Every time I end up with an error:

Exception thrown at 0x004F0928 (DelphiDLL.dll) in DelphiDLLTest.exe: 0xC0000005: Access violation reading location 0x00002EE0.

Can you help me?

EDIT: Replace ShortString by String, and other types for the one in C#.


Solution

  • I see some interop problems in your code:

    • raw pointers like PInfo1 and PInfo2 are not used in C#, so replace them with IntPtr.

    • string in Delphi is very different than string in C#. In Delphi, a string is a pointer to the character data, so you would have to use IntPtr on the C# side. If you need to read from a string field that Delphi has allocated, you can simply use Marshal.PtrToStringAnsi() (Delphi 2007 or earlier) or Marshal.PtrToStringUni() (Delphi 2009 or later). But, if you need to pass in a string that Delphi will read, good luck. You will have to manually allocate memory for not only the character data, but also the StrRec record that precedes the character data (and the contents of that record can vary depending on Delphi version). For this reason, passing string over the DLL boundary is a bad idea in interop. You should instead use a flat Char[] array (or a ShortString), or a null-terminated PChar pointer.

    • C# arrays are allocated dynamically, so when a fixed-sized array is contained in a struct, you need to use MarshalAs on the C# side to specify the size of the array.

    • a bool in Delphi is 1 byte in size, but in C# interop a bool is marshalled as a 4-byte integer (a LongBool in Delphi) by default. You need to use MarshalAs to change that.

    • a Windows LONG is a 4-byte integer. A Longint in Delphi is 4 bytes in size on most platforms, but is 8 bytes in size on Posix 64bit systems. a long in C# is always 8 bytes.

    With that said, the Delphi code you have shown would translate to something more like the following in C#:

    [StructLayout(LayoutKind.Sequential, Pack=8)]
    public struct Info1
    {
        public byte Param1;
        public byte Param2;
        public byte Param3;
        public byte Param4;
        public int Param5;
    }
    
    [StructLayout(LayoutKind.Sequential, Pack=8)]
    public struct Info2
    {
        public byte Param1;
        public int Param2;
        public int Param3;
        public int Param4;
        public int Param5; // long; use IntPtr or nint instead, if needed
        public IntPtr Param6; // string
        public int Param7;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=9)]
        public int[] Param8;
        public IntPtr Param9; // string
    }
    
    [StructLayout(LayoutKind.Sequential, Pack=8)]
    public struct INParameter
    {
        public int Param1;
        public int Param2;
        public int Param3;
        public byte Param4;
        public byte Param5;
        public byte Param6;
        public byte Param7;
        public byte Param8;
        public byte Param9;
        public byte Param10;
        public byte Param11;
        public byte Param12;
        public byte Param13;
        public byte Param14;
        public byte Param15;
        public byte Param16;
        public byte Param17;
        public byte Param18;
        public byte Param19;
        public byte Param20;
        public byte Param21;
        public byte Param22;
        public int Param23;
        public int Param24;
        public byte Param25;
        public byte Param26;
        public byte Param27;
        public byte Param28;
        public byte Param29;
        public byte Param30;
        public byte Param31;
        public byte Param32;
        public int Param33;
        public IntPtr Param34; //PInfo1
        public IntPtr Param35; //PInfo2
    }
      
    [StructLayout(LayoutKind.Sequential, Pack=8)]
    public struct OUTParameter
    {
        public int Param1;
        public int Param2;
        public int Param3;
        public int Param4;
        [MarshalAs(UnmanagedType.U1)]
        public bool Param5;
        [MarshalAs(UnmanagedType.U1)]
        public bool Param6;
        public int Param7;
        public int Param8;
        [MarshalAs(UnmanagedType.U1)]
        public bool Param9;
        [MarshalAs(UnmanagedType.U1)]
        public bool Param10;
        [MarshalAs(UnmanagedType.U1)]
        public bool Param11;
        [MarshalAs(UnmanagedType.U1)]
        public bool Param12;
        [MarshalAs(UnmanagedType.U1)]
        public bool Param13;
        [MarshalAs(UnmanagedType.U1)]
        public bool Param14;
        public int Param15;
        public int Param16;
        public int Param17;
        [MarshalAs(UnmanagedType.U1)]
        public bool Param18;
        [MarshalAs(UnmanagedType.U1)]
        public bool Param19;
        public int Param20;
    }
      
    [StructLayout(LayoutKind.Sequential, Pack=8)]
    public struct Pos
    {
        public int Param1;
        public int Param2;
        public byte Param3;
        public byte Param4;
    }
      
    [StructLayout(LayoutKind.Sequential, Pack=8)]
    public struct OUTDetails
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=84)]
        public Pos[] Poss;
    }
    
    [DLLImport("DLL_Name", CallingConvention = CallingConvention.StdCall)]
    void FetchData(ref INParameter infoIn, ref OUTParameter infoOut, ref OUTDetails details);