来自 Delphi 写入的 DLL 的内存访问冲突。尝试通过 DLLImport 访问

Memory Access Violation from a Delphi written DLL. Trying to access through DLLImport

我需要使用硬件供应商提供的 Delphi 编写的 DLL。在提供的文件中,它提到了以下

int ReadCard(char *room, char *gate,char *stime, char *guestname, char *guestid, char *lift, char *track1, char *track2, long *cardno, int *st, int *Breakfast);

参数详细说明如下:

这是我的问题 - 在尝试了各种方法来 DLLImport 方法后,我不断收到内存访问冲突错误。

错误内容类似于“System.AccessViolationException:试图读取或写入受保护的内存这通常表示其他内存已损坏。”在 ntdll.dll

处抛出错误

以下是我尝试过的一些尝试:

    [DllImport(DLL_FILE_PATH, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
    public unsafe static extern int ReadCard(
        [MarshalAs(UnmanagedType.LPStr)] StringBuilder room,
        [MarshalAs(UnmanagedType.LPStr)] StringBuilder gate,
        [MarshalAs(UnmanagedType.LPStr)] StringBuilder stayPeriod,
        [MarshalAs(UnmanagedType.LPStr)] StringBuilder guestName,
        [MarshalAs(UnmanagedType.LPStr)] StringBuilder guestID,
        [MarshalAs(UnmanagedType.LPStr)] StringBuilder lift,
        [MarshalAs(UnmanagedType.LPStr)] StringBuilder trackData1,
        [MarshalAs(UnmanagedType.LPStr)] StringBuilder trackData2,
        out Int32[] cardNumber,
        out int[] cardStatus,
        out int[] breakfast
    );
    [DllImport(DLL_FILE_PATH, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
    public unsafe static extern int ReadCard(
        [MarshalAs(UnmanagedType.LPStr)] string room,
        [MarshalAs(UnmanagedType.LPStr)] string gate,
        [MarshalAs(UnmanagedType.LPStr)] string stayPeriod,
        [MarshalAs(UnmanagedType.LPStr)] string guestName,
        [MarshalAs(UnmanagedType.LPStr)] string guestID,
        [MarshalAs(UnmanagedType.LPStr)] string lift,
        [MarshalAs(UnmanagedType.LPStr)] string trackData1,
        [MarshalAs(UnmanagedType.LPStr)] string trackData2,
        out long cardNumber,
        out long cardStatus,
        out long breakfast
    );

        [DllImport(DLL_FILE_PATH, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
    public unsafe static extern int ReadCard(
        out char[] room,
        out char[] gate,
        out char[] stayPeriod,
        out char[] guestName,
        out char[] guestID,
        out char[] lift,
        out char[] trackData1,
        out char[] trackData2,
        out long[] cardNumber,
        out int[] cardStatus,
        out int[] breakfast
    );

    [DllImport(DLL_FILE_PATH, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
    public unsafe static extern int ReadCard(
        [MarshalAs(UnmanagedType.LPStr)] out string room,
        [MarshalAs(UnmanagedType.LPStr)] out string gate,
        [MarshalAs(UnmanagedType.LPStr)] out string stayPeriod,
        [MarshalAs(UnmanagedType.LPStr)] out string guestName,
        [MarshalAs(UnmanagedType.LPStr)] out string guestID,
        [MarshalAs(UnmanagedType.LPStr)] out string lift,
        [MarshalAs(UnmanagedType.LPStr)] out string trackData1,
        [MarshalAs(UnmanagedType.LPStr)] out string trackData2,
        IntPtr cardNumber,
        IntPtr cardStatus,
        IntPtr breakfast
    );

知道我错过了什么吗?

您不能将 string 用于 output 需要指向 char[] 缓冲区的参数。根据 Microsoft 的文档,您需要使用 StringBuilderDefault Marshaling for Strings: Fixed-Length String Buffers。对于 输入 参数,string 可以正常工作。

在C#中,在C/C++和Delphi中对int(32位整数)参数使用long(即Int64)是错误的。您需要改用 int(即 Int32)。另一方面,C/C++ 中的long 可能是 32 位或 64 位,具体取决于平台,因此您必须检查 DLL 实际使用的是什么。很有可能,它 可能 是 32 位,假设 Windows。

但是,为什么要将 cardNumbercardStatusbreakfast 参数声明为数组?文档或 C/C++ 声明中没有任何内容表明它们不是单个整数。 cardNumbercardStatus 应该是 out int(或者 maybe out long for cardNumber),或者 IntPtr如果你曾经为他们传递 nullbreakfast 只是输入,所以它应该只是 int.

试试像这样的东西:

[DllImport(DLL_FILE_PATH, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern int ReadCard(
    StringBuilder room,
    StringBuilder gate,
    StringBuilder stayPeriod,
    StringBuilder guestName,
    StringBuilder guestID,
    string lift,
    StringBuilder trackData1,
    StringBuilder trackData2,
    out int cardNumber,
    out int cardStatus,
    int breakfast
);

从 C# 调用此函数时,请确保预先将 StringBuilder 参数预分配为所需的长度,以便 DLL 有足够的内存写入。例如:

StringBuilder roomSB = new StringBuilder(10);
ReadCard(roomSB, ...);
string room = roomSB.ToString();