2

I need to work with a Delphi written DLL provided by a hardware vendor. In the document provided, it mentioned the below

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

Further elaboration of the parameters are given as below:

  • room [out]: character string pointer, receive returned room number, 10 bytes recommended.

  • gate [out]: character string pointer, receive returned authorized common gates, can be NULL.

  • Guestname [out]: character string pointer, receive returned guest name, can be NULL.

  • Guestid [out]: character string pointer, receive returned guest ID, can be NULL.

  • Lift [in]:Lift floors, string parameter, “00” stands for default floor, “99”stands for authorization for all floors, and others are specified floor codes, e.g., “010205” stands for authorization for three floors 01,02,05.

  • track1 [out]: receive track 1 data of magnetic card, can be NULL.

  • track2 [out]: receive track 2 data of magnetic card, can be NULL.

  • Cardno [out]: long integer pointer, receive returned card number, can be NULL.

  • St [out]: integer pointer, receive returned card status: 1-normally used, 3-normally erased, 4-lost, 5-damaged, 6-automatically erased, can be NULL. Breakfast

  • [in]: Breakfast number.

Here's my problem - I keep getting memory access violation errors after trying all kinds of ways to DLLImport the method.

The error reads like "System.AccessViolationException: Attempted to read or write protected memory This is often an indication that other memory is corrupt." Error is thrown at ntdll.dll

Below are some of the attempts I've tried:

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

Any idea where did I missed?

JeeShen Lee
  • 3,476
  • 5
  • 39
  • 59
  • You meant replacing out long[] cardNumber with out int32[] cardNumber? I've also updated my questions. I've tried the StringBuilder option, same, getting memory access violation. I noticed Delphi as dynamic array (with zero-based index) and static array, I believe their DLL is using dynamic array. Will assigning new StringBuilder(100) limit the string to grow since we are dealing with dynamic array? – JeeShen Lee Dec 08 '21 at 02:34
  • 2
    Unrelated, but I find it disturbing that the `ReadCard` function doesn't have any parameters to specify sizes of output buffers used and that the documentation doesn't even mention the maximum possible lengths of output strings (except for room number, where it states that 10 bytes are recommended, but even this doesn't sound like a guarantee, but a mere recommendation). This is a recipe for all sorts of buffer overflows and security holes. Looks like a bad design. – heap underrun Dec 08 '21 at 05:32
  • 1
    @heapunderrun Delphi-style dynamic arrays don't play any factor in this situation, so don't worry about them. – Remy Lebeau Dec 08 '21 at 08:00
  • @heapunderrun agreed, though on the other hand, it is possible that those particular parameters are using fixed-length buffers, whose sizes are described elsewhere. – Remy Lebeau Dec 08 '21 at 08:02
  • 1
    in addition to all the other comments, unsafe is needless here – David Heffernan Dec 08 '21 at 12:30

1 Answers1

2

You can't use string for output parameters that expect pointers to char[] buffers. You need to use StringBuilder instead, per Microsoft's documentation: Default Marshaling for Strings: Fixed-Length String Buffers. For input parameters, string will work fine.

In C#, using long (ie Int64) for int (32bit integer) parameters in C/C++ and Delphi is wrong. You need to use int (ie Int32) instead. On the other hand,long in C/C++ may be either 32bit or 64bit, depending on platform, so you will have to check what the DLL is actually using. Chances are, it will probably be 32bit, assuming Windows.

But, why are you declaring the cardNumber, cardStatus, and breakfast parameters as arrays? Nothing in the documentation or C/C++ declaration suggests they are anything but single integers. cardNumber and cardStatus should be out int (or maybe out long for cardNumber), or IntPtr if you ever pass in null for them. breakfast is input only, so it should be simply int.

Try something more like this:

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

When calling this from C#, just be sure to pre-allocate the StringBuilder parameters to the desired lengths beforehand, so the DLL has adequate memory to write to. For example:

StringBuilder roomSB = new StringBuilder(10);
ReadCard(roomSB, ...);
string room = roomSB.ToString();
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks for your answer. It works. For the benefit of others who has this problem. For your info, I ran into another problem while solving the above. The other error is "Arithmetic overflow" exception. In case you encounter a similar problem it's not related to how the DLLImport is used. It has to do with Delphi DLL resetting .net floating point. It's discussed here https://stackoverflow.com/questions/2407040/overflow-or-underflow-in-the-arithmetic-operation-wpf-specific-issue/19722292#comment124271758_19722292 – JeeShen Lee Dec 10 '21 at 06:31