从 C# 调用非托管 DLL,将结构作为参数传递

Posted

技术标签:

【中文标题】从 C# 调用非托管 DLL,将结构作为参数传递【英文标题】:Calling an unmanaged DLL from C# passing structs as parameters 【发布时间】:2021-08-19 14:42:16 【问题描述】:

我有一个没有类型库和文档的 DLL 文件。我所拥有的是使用它的 Delphi (Pascal) 代码。我正在尝试使用 .Net 5 C# 调用此 DLL。

我得到的错误是

System.AccessViolationException: '试图读取或写入受保护的内存。这通常表明其他内存已损坏。'

这是我的尝试:

    [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()
        
            
        
    

这是调用相同 DLL 的 Delphi 代码(确实有效):

    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 

我已确保结构中的所有字段的顺序与 Delphi 代码中的顺序相同。

【问题讨论】:

如果你从 c# 到 c 语言,调用约定需要是 CallingConvention.Cdecl(不是 StdCall)。 Stdcall 将转到 Fortran 或 Basic。 我试过了,结果一样。我怀疑 DLL 是用 Pascal 编写的。 检查pascal编译选项。它可以使用标准或 c 约定。 检查帕斯卡整数的大小,可能不是 32 位。还要检查字符串类型是 8 位、16 位还是组合。请参阅:docs.microsoft.com/en-us/dotnet/framework/interop/… @jdweng Delphi 中的整数是 32 位的,映射到 C# 中的 int。导出函数的调用约定是 stdcall,在代码中显式声明。这是一个对 Delphi 的了解至关重要的问题。 【参考方案1】:

终于解决了。我之前在 DLL 调用中尝试过使用 Charset unicode,但我错过了在结构上设置它:

[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
...

【讨论】:

【参考方案2】:
    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;
    

在Delphi中实现如下:

 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

C#中字符串的结尾映射到Delphi中的Filler_X。今天我将在 C# 中编组 [MarshalAs (UnmanagedType.LPWStr)]。在 Delphi 中,我会使用 PChar。那么填充物就不需要了。

【讨论】:

以上是关于从 C# 调用非托管 DLL,将结构作为参数传递的主要内容,如果未能解决你的问题,请参考以下文章

C#和C++传递结构体

如何将数据从非托管应用程序传递到 C# COM DLL

将 C# 字符串作为参数发送到非托管 C++ DLL 函数

使用 .NET Native 构建时,参数不会传递给 x86 上的非托管 DLL

当结构仅在运行时已知时,将结构从 c++ 传递到 c#

从 C++ 非托管 dll 传递 C# 函数