Niklas Jonsson
Niklas Jonsson

Reputation: 183

Calling an unmanaged DLL from C# passing structs as parameters

I have a DLL file for which I have no type library and no documentation. What I do have is Delphi (Pascal) code that is using it. I am trying to call this DLL using .Net 5 C#.

The error I get is

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

Here is my attempt:

    [StructLayout(LayoutKind.Sequential)]
    public struct MyDLLQuery
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string Frame;

        public int Width { get; set; }

        public double Cost { get; set; }
        ...
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MyDLLResult
    {
        public int NumTubes { get; set; }
        public double Spacing { get; set; }

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string Material;
        ...
    }

    internal static class MyDLLWrapper
    {
        const string DLL = @"bin\MyDLL.dll";
        private static CallbackDelegate delegateInstance;

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void CallbackDelegate();
    
        [DllImport(DLL, CallingConvention = CallingConvention.StdCall)]
        private static extern void CalculateData(MyDLLQuery indata, out MyDLLResult outdata, CallbackDelegate f);

        internal static void Query()
        {
            MyDLLQuery indata = new MyDLLQuery();
            indata.Frame = "G";
            indata.Width = 1300; 
            indata.Cost = 50.0; 
            ...

            MyDLLResult outdata = new MyDLLResult();
    
            delegateInstance = MyFunc;
    
            CalculateData(indata, out outdata, delegateInstance);
        }

        public static void MyFunc()
        {
            
        }
    }

Here is the Delphi code calling the same DLL (that does work):

    TInParams = record
        Frame: array [0..15] of Char;
        Width: Integer;
        Cost: Double;
        ...

    TOutParams = record
        NumTubes: Integer;
        Spacing: Double;
        Material: array [0..15] of Char;
        ...


    TInfoCallBackProcedure = procedure() cdecl;

    const LIBNAME = 'MyDLL.dll';

    procedure CalculateData(InData: TInParams; var OutData: TOutParams; Callback: TInfoCallbackProcedure); stdcall external LIBNAME;


    var
        InParams: TInParams;
        OutParams: TOutParams;


    SetInData(sourceData, InParams); // assigns a value to all fields in InParams
    FillChar(OutParams, SizeOf(OutParams), 0);

    CalculateData(InParams, OutParams, Callback); // No callback = nil 

I've made sure all the fields in the structs are in the same order as in the Delphi code.

Upvotes: 0

Views: 462

Answers (2)

USauter
USauter

Reputation: 335

    StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
    public struct Result
    {
        //SizeCount bezieht sich anscheinden auf Char (hier 2 Byte) und
        // nicht auf die Bytegröße des Felds
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 12)] public string BIC; //Delphi: array[0..10] of char
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 35)] public string IBAN; //Delphi: array[0..33] of char
        [MarshalAs(UnmanagedType.I4)] public BACIbanKonverterStatus Status;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 201)] public string StatusMessage;
        //Delphi: array[0..199] of char

        [MarshalAs(UnmanagedType.Bool)] //Delphi: Longbool
        public bool Success;
    }

Has been implemented in Delphi as follows :

 TRecIban=packed record
public
  Bic          : array[0..10] of char;
  Filler_1     : Char;
  Iban         : array[0..33] of char;
  Filler_2     : Char;
  Status       : integer;
  StatusMessage: array[0..199] of char;
  Filler_3     : Char;
  Success      : Longbool;
end

The end of the string in C # is mapped to the Filler_X in Delphi. Today I would marshal over [MarshalAs (UnmanagedType.LPWStr)] in C #. In Delphi I would use a PChar. Then the filler is unnecessary.

Upvotes: 0

Niklas Jonsson
Niklas Jonsson

Reputation: 183

Finally solved this. I had tried with Charset unicode on the DLL call previously, but I had missed setting it on the structs:

[DllImport(DLL, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
private static extern void CalculateData(MyDLLQuery indata, out MyDLLResult outdata, CallbackDelegate f);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct MyDLLQuery
...

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct MyDLLResult
...

Upvotes: 1

Related Questions