2

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

}

Gkush
  • 45
  • 8
  • "There is nothing" Really, what can this possibly mean. No message arrives? A message arrives but doesn't contain what you expect? Surely it would be easier to try with a simpler example, perhaps a struct containing a single string? Then the example would be simpler. You could then make a complete minimal reproduction. `UnmanagedType.LPTStr` is quite wrong. That's a pointer. You need an inline array, `UnmanagedType.ByValTStr`. But then you have fixed length strings. Which is nasty. So why don't you pass an encoded JSON map as a single string? – David Heffernan Nov 28 '18 at 16:43
  • when the lpData is a string not a structure which is marshalled as `[MarshalAs(UnmanagedType.LPTStr, SizeConst = 24)]` I get the string. when I pass it as `UnmanagedType.ByValTStr` there is only gibberish, that's why I sticked to `UnmanagedType.LPTStr`. – Gkush Nov 28 '18 at 16:56
  • Well, it's wrong though. The system can marshal the outer struct, but not those inner pointers. `ByValTStr` is needed for values inside a struct. But the JSON approach is preferable. – David Heffernan Nov 28 '18 at 16:58
  • And what I mean by no message is, there is only a new line there when passing the struct – Gkush Nov 28 '18 at 16:58
  • It's no fun communicating like this, with so a low signal to noise ratio. Try to be clearer. It's time for that [mcve]. – David Heffernan Nov 28 '18 at 16:58
  • Sorry for that, I am fairly new to Delphi, Can you provide me an example regarding JSON map. In the mean time, I will try with ByValTstr and create a simpler program to test with – Gkush Nov 28 '18 at 17:03
  • No, I'm not going to write an example for you. There are lots of examples of working with JSON in both C# and Delphi. And you already know how to supply a string. So you have all the pieces already. Also, you should be aware that `SizeConst` does not go with `ByValTStr`. Think about it. `ByValTStr` is a pointer to a null terminated string. – David Heffernan Nov 28 '18 at 17:12
  • @DavidHeffernan "*you should be aware that `SizeConst` does not go with `ByValTStr`*" - actually, it does. Microsoft's own examples show `SizeConst` being used with `ByValTStr` when marshalling a `string` as a char array, and even the [documentation](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedtype?view=netframework-4.7.2) for `ByValTStr` says to use `SizeConst`: "*Used for in-line, fixed-length character arrays that appear within a structure. ... **Always use the SizeConst field to indicate the size of the array**.*" Maybe you meant `LPTStr` instead? – Remy Lebeau Nov 28 '18 at 17:57
  • @remy thanks, you are right, it clearly does go with ByValTStr. I meant LPTStr of course. – David Heffernan Nov 28 '18 at 18:38

1 Answers1

4

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);
    }
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I tried it and I got `String type Gibberish ` . The only problem is I am only provided with a test Delphi applictaion which I don't have control over. When i changed the int i to a string I was able to get only the " i" value from the struct I am sending. – Gkush Nov 28 '18 at 18:20
  • If you don't have control of the Delphi code, how do you know what kind of struct it is expecting to receive in the first place? Also, it is common practice for `WM_COPYDATA` senders to set the `dwData` to a unique value, not 0, such as from `RegisterWindowMessage()`. How do you know the Delphi app is not expecting that? Even the VCL uses `WM_COPYDATA` internally, so messages need to be uniquely identified so they don't get misintepretted. – Remy Lebeau Nov 28 '18 at 18:25
  • From the notes on the app. Data is returned in a SampleRecord structure ` TSampleRecord = packed record i : integer; card : string[50]; name : string[100]; responsecode : string[5]; responsetext : string[100]; approval : string[15]; tranid : string[50]; reference : string[16]; d : TDateTime; transactionType: string[24]; creditCardType: string[10]; EMVContact: integer; applicationName: string[50]; applicationIdentifier: string[15]; reserved: string[10]; ` – Gkush Nov 28 '18 at 18:27
  • 4
    That is information that should have been included in your question up-front. Now we are getting somewhere. That record contains Delphi **Short Strings**, which is different than raw char arrays. A ShortString contains a leading byte to specify the array size. Your C# struct is not declaring the arrays correctly to account for that. I'll tweak my answer in a minute – Remy Lebeau Nov 28 '18 at 18:28
  • Thank you so much. I have not much experience working with delphi structure at all. I have only done code migration from delphi to C# for few of the web application and that was it – Gkush Nov 28 '18 at 18:30
  • 4
    The lesson here is to provide [mcve]. Especially critical in an interop question. – David Heffernan Nov 28 '18 at 18:54
  • @RemyLebeau I tried it, the only thing I am receiving is ㄲ symbol. – Gkush Nov 28 '18 at 19:45
  • I am getting this error. System.ArgumentException HResult=0x80070057 Message=Type 'Form1.GeneralMethods+ReturnStruct' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed. Source=mscorlib StackTrace: at System.Runtime.InteropServices.Marshal.SizeOfHelper(Type t, Boolean throwIfNotMarshalable) – Gkush Nov 28 '18 at 20:27
  • @Gkush You should have mentioned that it is only the `DelphiShortStringMarshaler` example that is throwing the exception. The `ByValTStr` example works perfectly fine. The `DelphiShortStringMarshaler` example is [failing because of this](https://stackoverflow.com/questions/23239320/), so I will try to fix it. – Remy Lebeau Nov 28 '18 at 20:48
  • 1
    @Gkush apparently you can't use a custom marshaller on struct fields at all, only on function parameters and return values. Even if you could work around the `Marshal.SizeOf()` issue, `Marshal.StructureToPtr()` would throw a "*Custom marshalers cannot be used on fields of structures*" exception instead. So, I have replaced the `DelphiShortStringMarshaler` example with a different approach. – Remy Lebeau Nov 28 '18 at 23:22
  • I tried that too, it doesn't seem to work. I have no choice but to talk with the owner of the delphi end to make sure, if everything is ok at his end . WIll reply to this thread if I have any updates. – Gkush Nov 29 '18 at 14:07
  • 1
    @Gkush it does work, I tested it before posting it this time. So you must not be applying it to your project correctly – Remy Lebeau Nov 29 '18 at 14:33
  • @RemyLebeau Thank you so much. Turns out for dwData it should have been `(IntPtr)2' I reached out ... I am at least being able to see the response messages more properly. messages upto Name is properly being passed to the app. After that, There is nothing there. WIll fiddle with it more, to solve it – Gkush Nov 29 '18 at 16:51