Search code examples
c#delphisendmessagewindows-messageswm-copydata

Marshall struct to pass it to delphi record via sendmessage


I am trying to pass a struct to delphi via c#, I have done following to pass the message, I followed the format from pinvoke to copy datat struct from https://www.pinvoke.net/default.aspx/Structures.COPYDATASTRUCT, but on delphi I am receiving no messages. In a way I believe it is because I haven't encoded the struct in a right way. When I pass a string message only, I receive it, but when I try passing the struct, there is nothing

This is what I have done so far.

using System;

using System.Runtime.InteropServices;

using System.Windows.Forms;

namespace ccTestForm2
{
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        SendFingerPrintResult();
    }
    const int WM_COPYDATA = 0x004A;
    //include SendMessage
    [DllImport("user32.dll")]
    public static extern IntPtr FindWindow(string lpszClass, string 
  lpszWindow);
    [DllImport("user32.dll", CharSet = CharSet.Ansi, EntryPoint = "SendMessage", SetLastError = false)]
    public static extern int SendMessageCopyData(IntPtr hWnd, int uMsg, UIntPtr wParam, ref COPYDATASTRUCT lParam);
    [StructLayout(LayoutKind.Sequential)]
    public struct COPYDATASTRUCT
    {
        public IntPtr dwData;
        public int cbData;
        public IntPtr lpData;
    }

    public struct ReturnStruct
    {
        public int i;
        public string card;
        public string name;
        public string responsecode;
        public string responsetext;
        public string approval;
        public string tranid;
        public string reference;
        public double d;
        public string transactionType;
        public string creditCardType;
        public int EMVContact;
        public string applicationName;
        public string applicationIdentifier;
        public string reserved;

        public IntPtr ToPtr()
        {
            IntPtr ret = Marshal.AllocHGlobal(473);

            IntPtr ptr = ret;
            Marshal.WriteInt32(ptr, i); ptr = IntPtr.Add(ptr, 4);
            DelphiShortStringHelper.WriteToPtr(card, ref ptr, 50);
            DelphiShortStringHelper.WriteToPtr(name, ref ptr, 100);
            DelphiShortStringHelper.WriteToPtr(responsecode, ref ptr, 5);
            DelphiShortStringHelper.WriteToPtr(responsetext, ref ptr, 100);
            DelphiShortStringHelper.WriteToPtr(approval, ref ptr, 15);
            DelphiShortStringHelper.WriteToPtr(tranid, ref ptr, 50);
            DelphiShortStringHelper.WriteToPtr(reference, ref ptr, 16);
            Marshal.Copy(new double[] { d }, 0, ptr, 1); ptr = IntPtr.Add(ptr, 8);
            DelphiShortStringHelper.WriteToPtr(transactionType, ref ptr, 24);
            DelphiShortStringHelper.WriteToPtr(creditCardType, ref ptr, 10);
            Marshal.WriteInt32(ptr, EMVContact); ptr = IntPtr.Add(ptr, 4);
            DelphiShortStringHelper.WriteToPtr(applicationName, ref ptr, 50);
            DelphiShortStringHelper.WriteToPtr(applicationIdentifier, ref ptr, 15);
            DelphiShortStringHelper.WriteToPtr(reserved, ref ptr, 10);

            return ret;
        }
    }

    public ReturnStruct GetReturnStruct()
    {
        var ret = new ReturnStruct();

        ret.i = 2;

        ret.card = "1234";

        ret.name = "test";

        ret.responsecode = "mese";

        ret.responsetext = "dork";

        ret.approval = "Plerk";


        ret.tranid = "pse";

        ret.reference = "Ig";


        ret.d = DateTime.UtcNow.ToOADate();

        ret.transactionType = "cit";


        ret.creditCardType = "t2";


        ret.EMVContact = 0;

        ret.applicationName = "mpp";


        ret.applicationIdentifier = "nne";


        ret.reserved = "12";

        return ret;
    }

    public void SendFingerPrintResult()
    {
        // get the window to send struct
        IntPtr receiverHandle = FindWindow("TReceiverMainForm", "ReceiverMainForm");
        if (receiverHandle == IntPtr.Zero) return;

        // Get the struct
        ReturnStruct ret = GetReturnStruct();
        IntPtr ptr = ret.ToPtr();
        try
        {
            var cds = new COPYDATASTRUCT
            {
                dwData = IntPtr(2), // to identify the message contents
                cbData = Marshal.SizeOf(ret),
                lpData = ptr
            };
            SendMessageCopyData(receiverHandle, WM_COPYDATA, UIntPtr.Zero, ref cds);
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
}
class DelphiShortStringHelper
{
    public static void WriteToPtr(string s, ref IntPtr ptr, byte maxChars = 255)
    {
        byte[] bytes = System.Text.Encoding.Default.GetBytes(s);
        int strLen = Math.Min(bytes.Length, (int)maxChars);
        Marshal.WriteByte(ptr, (byte)strLen);
        ptr = IntPtr.Add(ptr, 1);
        Marshal.Copy(bytes, 0, ptr, strLen);
        ptr = IntPtr.Add(ptr, (int)maxChars);
    }
}

}


Solution

  • A few minor bugs in your code:

    • your definition of COPYDATASTRUCT is missing [StructLayout].

    • your definition of SendMessage() is slightly wrong (wParam needs to be a UIntPtr instead of an Int32).

    • you are not freeing any of the memory you allocate with IntPtrAlloc().

    Now, for the main issue:

    You need to use UnmanagedType.ByValTStr instead of UnmanagedType.LPTStr when marshaling strings as fixed-length character arrays (see Strings Used In Structures).

    But, more importantly (based on details you provided in comments instead of in your main question), the Delphi side is expecting the strings in the received struct to be encoded in Short String format, which is slightly different than raw character arrays:

    A ShortString is 0 to 255 single-byte characters long. While the length of a ShortString can change dynamically, its memory is a statically allocated 256 bytes; the first byte stores the length of the string, and the remaining 255 bytes are available for characters. If S is a ShortString variable, Ord(S[0]), like Length(S), returns the length of S; assigning a value to S[0], like calling SetLength, changes the length of S. ShortString is maintained for backward compatibility only.

    The Delphi language supports short-string types - in effect, subtypes of ShortString - whose maximum length is anywhere from 0 to 255 characters. These are denoted by a bracketed numeral appended to the reserved word string. For example:

    var MyString: string[100];
    

    creates a variable called MyString, whose maximum length is 100 characters. This is equivalent to the declarations:

    type CString = string[100];
    var MyString: CString;
    

    Variables declared in this way allocate only as much memory as the type requires - that is, the specified maximum length plus one byte. In our example, MyString uses 101 bytes, as compared to 256 bytes for a variable of the predefined ShortString type.

    When you assign a value to a short-string variable, the string is truncated if it exceeds the maximum length for the type.

    The standard functions High and Low operate on short-string type identifiers and variables. High returns the maximum length of the short-string type, while Low returns zero.

    So, try something more like this instead:

    [StructLayout(LayoutKind.Sequential)]
    public struct COPYDATASTRUCT
    {
        public IntPtr dwData;
        public int cbData;
        public IntPtr lpData;
    }
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct ReturnStruct
    {
        public int i;
    
        public byte card_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
        public string card;
    
        public byte name_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
        public string name;
    
        public byte responsecode_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
        public string responsecode;
    
        public byte responsetext_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
        public string responsetext;
    
        public byte approval_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 15)]
        public string approval;
    
        public byte tranid_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
        public string tranid;
    
        public byte reference_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string reference;
    
        public double d;
    
        public byte transactionType_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)]
        public string transactionType;
    
        public byte creditCardType_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string creditCardType;
    
        public int EMVContact;
    
        public byte applicationName_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
        public string applicationName;
    
        public byte applicationIdentifier_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 15)]
        public string applicationIdentifier;
    
        public byte reserved_len;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string reserved;
    }
    
    public ReturnStruct GetReturnStruct()
    {
        var ret = new ReturnStruct();
    
        ret.i = ...;
    
        ret.card = ...;
        ret.card_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.card), 50);
    
        ret.name = ...;
        ret.name_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.name), 100);
    
        ret.responsecode = ...;
        ret.responsecode_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.responsecode), 5);
    
        ret.responsetext = ...;
        ret.responsetext_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.responsetext), 100);
    
        ret.approval = ...;
        ret.approval_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.approval), 15);
    
        ret.tranid = ...;
        ret.tranid_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.tranid), 50);
    
        ret.reference = ...;
        ret.reference_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.reference), 16);
    
        ret.d = ...;
    
        ret.transactionType = ...;
        ret.transactionType_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.transactionType), 24);
    
        ret.creditCardType = ...;
        ret.creditCardType_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.creditCardType), 10);
    
        ret.EMVContact = ...;
    
        ret.applicationName = ...;
        ret.applicationName_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.applicationName), 50);
    
        ret.applicationIdentifier = ...;
        ret.applicationIdentifier_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.applicationIdentifier), 15);
    
        ret.reserved = ...;
        ret.reserved_len = (byte) Math.Min(System.Text.Encoding.Default.GetByteCount(ret.reserved), 10);
    
        return ret;
    }
    
    public static IntPtr IntPtrAlloc<T>(T param)
    {
        IntPtr retval = Marshal.AllocHGlobal(Marshal.SizeOf(param));
        Marshal.StructureToPtr(param, retval, false);
        return retval;
    }
    
    public static void IntPtrFree(ref IntPtr preAllocated)
    {
        Marshal.FreeHGlobal(preAllocated);
        preAllocated = IntPtr.Zero;
    }
    
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
    public static extern int SendMessage(IntPtr hWnd, int uMsg, UIntPtr wParam, IntPtr lParam);
    
    public void SendFingerPrintResult(string msg)
    { 
        // get the window to send struct
        IntPtr receiverHandle = GetWindow();
        if (receiverHandle == IntPtr.Zero) return;
    
        // Get the struct
        ReturnStruct ret = GetReturnStruct();
        IntPtr ptr = IntPtrAlloc(ret);
        try
        {
            var cds = new COPYDATASTRUCT
            {
                dwData = IntPtr.Zero,
                cbData = Marshal.SizeOf(ret),
                lpData = ptr
            };
    
            IntPtr iPtr = IntPtrAlloc(cds);
            try
            {
                SendMessage(receiverHandle, WM_COPYDATA, senderID, iPtr);
            }
            finally
            {
                IntPtrFree(ref iPtr);
            }
        }
        finally
        {
            IntPtrFree(ref ptr);
        }
    }
    

    You can optionally remove the inner IntPtrAlloc() call by tweaking your SendMessage() definition (see C# using SendMessage, problem with WM_COPYDATA):

    [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "SendMessage", SetLastError = false)]
    public static extern int SendMessageCopyData(IntPtr hWnd, int uMsg, UIntPtr wParam, ref COPYDATASTRUCT lParam);
    
    public void SendFingerPrintResult(string msg)
    { 
        // get the window to send struct
        IntPtr receiverHandle = GetWindow();
        if (receiverHandle == IntPtr.Zero) return;
    
        // Get the struct
        ReturnStruct ret = GetReturnStruct();
        IntPtr ptr = IntPtrAlloc(ret);
        try
        {
            var cds = new COPYDATASTRUCT
            {
                dwData = IntPtr.Zero,
                cbData = Marshal.SizeOf(ret),
                lpData = ptr
            };
    
            SendMessageCopyData(receiverHandle, WM_COPYDATA, senderID, ref cds);
        }
        finally
        {
            IntPtrFree(ref ptr);
        }
    }
    

    You might also consider writing a custom wrapper to help with the marshalling of ShortString values:

    class DelphiShortStringHelper
    {
        public static void WriteToPtr(string s, ref IntPtr ptr, byte maxChars = 255)
        {
            byte[] bytes = System.Text.Encoding.Default.GetBytes(s);
            int strLen = Math.Min(bytes.Length, (int)maxChars);
            Marshal.WriteByte(ptr, (byte)strLen);
            ptr = IntPtr.Add(ptr, 1);
            Marshal.Copy(bytes, 0, ptr, strLen);
            ptr = IntPtr.Add(ptr, (int)maxChars);
        }
    }
    
    public struct ReturnStruct
    {
        public int i;
        public string card;
        public string name;
        public string responsecode;
        public string responsetext;
        public string approval;
        public string tranid;
        public string reference;
        public double d;
        public string transactionType;
        public string creditCardType;
        public int EMVContact;
        public string applicationName;
        public string applicationIdentifier;
        public string reserved;
    
        public IntPtr ToPtr()
        {
            IntPtr ret = Marshal.AllocHGlobal(473);
    
            IntPtr ptr = ret;
            Marshal.WriteInt32(ptr, i); ptr = IntPtr.Add(ptr, 4);
            DelphiShortStringHelper.WriteToPtr(card, ref ptr, 50);
            DelphiShortStringHelper.WriteToPtr(name, ref ptr, 100);
            DelphiShortStringHelper.WriteToPtr(responsecode, ref ptr, 5);
            DelphiShortStringHelper.WriteToPtr(responsetext, ref ptr, 100);
            DelphiShortStringHelper.WriteToPtr(approval, ref ptr, 15);
            DelphiShortStringHelper.WriteToPtr(tranid, ref ptr, 50);
            DelphiShortStringHelper.WriteToPtr(reference, ref ptr, 16);
            Marshal.Copy(new double[]{d}, 0, ptr, 1); ptr = IntPtr.Add(ptr, 8);
            DelphiShortStringHelper.WriteToPtr(transactionType, ref ptr, 24);
            DelphiShortStringHelper.WriteToPtr(creditCardType, ref ptr, 10);
            Marshal.WriteInt32(ptr, EMVContact); ptr = IntPtr.Add(ptr, 4);
            DelphiShortStringHelper.WriteToPtr(applicationName, ref ptr, 50);
            DelphiShortStringHelper.WriteToPtr(applicationIdentifier, ref ptr, 15);
            DelphiShortStringHelper.WriteToPtr(reserved, ref ptr, 10);
    
            return ret;
        }
    }
    
    public ReturnStruct GetReturnStruct()
    {
        var ret = new ReturnStruct();
    
        ret.i = ...;
        ret.card = ...;
        ret.name = ...;
        ret.responsecode = ...;
        ret.responsetext = ...;
        ret.approval = ...;
        ret.tranid = ...;
        ret.reference = ...;
        ret.d = ...;
        ret.transactionType = ...;
        ret.creditCardType = ...;
        ret.EMVContact = ...;
        ret.applicationName = ...;
        ret.applicationIdentifier = ...;
        ret.reserved = ...;
    
        return ret;
    }
    
    public void SendFingerPrintResult(string msg)
    { 
        // get the window to send struct
        IntPtr receiverHandle = GetWindow();
        if (receiverHandle == IntPtr.Zero) return;
    
        // Get the struct
        ReturnStruct ret = GetReturnStruct();
        IntPtr ptr = ret.ToPtr();
        try
        {
            ...
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }