【发布时间】: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 的了解至关重要的问题。