Marshaling nested structures from C# to C++
Asked Answered
I

2

1

I have the following types in C++:

typedef void* keychain_handle;

typedef struct {
  const char* keyHolderName;
  unsigned int numKeys;
  key* keys;
} key_holder;

typedef struct {
  const char* keyName;
  unsigned int keySize;
} key;

And I have the following methos:

int createKeyChain(
  int id, 
  key_holder* keyHolders,
  keychain_handle* handle);

I need my C# to create key holders with keys, send it to the C++ code and receive a handle.

This is my C# code:

    /* Structs */
    [StructLayout(LayoutKind.Sequential)]
    public struct Key
    {
          public string key;
          public uint size;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct KeyHolder
    {
          public string name;
          public uint keys;
          public IntPtr keys;
    }


    /* Sync API */
    [DllImport("keys.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
    public static extern uint createKeyChain(uint id, KeyHolder[] keyHolders, ref IntPtr handle);


        Key[] myKeys = new Key[1];
        myKeys[0] = new Key { key = "tst", size = 5 };

        KeyHolder keyHolder = new DllWrapper.KeyHolder
        {
            name = "tst123",
            items = 1,
            keys = Marshal.AllocHGlobal(Marshal.SizeOf(typeof (Key))*myKeys.Length)
        };

        IntPtr c = new IntPtr(keyHolder.keys.ToInt32());

        for (int i = 0; i < myKeys.Length; i++)
        {
            Marshal.StructureToPtr(myKeys[i], c, false);
            c = new IntPtr(c.ToInt32() + Marshal.SizeOf(typeof(Key)));
        }

        Marshal.StructureToPtr(c, keyHolder.keys, false);

        IntPtr handle = IntPtr.Zero;

        var ret = createKeyChain(111, new []{keyHolder}, ref handle);

Everything is working well except for the internal string inside the Key object, which is corrupt. I suspect it is the StructureToPtr that is corrupting it. How can I make the string show on the C++ side?

Thanks.

Introrse answered 18/3, 2015 at 15:59 Comment(10)
can you please show what have you tried in C#?Psychosurgery
please add Key and KeyHolder definitionsPsychosurgery
@nikis, sorry I forgot to add it, added now.Introrse
That's quite a nasty one. Going to require quite a bit of manual marshalling. I think you'll need to marshal the array of keys as IntPtr, and then marshal each key individually. Going to need one call to Marshal.StringToHGlobalAnsi for each element of the array. Is C++/CLI an option?Witting
Yeah I guess it is an option.Introrse
The [MarshalAs] attribute on the IntPtr is not valid, you have to delete it. And you have to get rid of m_Keys, the private keyword has no effect on marshaling.Norean
@Introrse you have declared keys twice in your KeyHolder struct, m_keys is redundantPsychosurgery
Yeah I remove the problems in KeyHolder and now it works, only the strings are corruptIntrorse
The keyHolder string is fine but the inner keys strings are corrupt.Introrse
I don't think the marshaller is going to do it for you..........Witting
W
1

Without making changes to the unmanaged code, you have no option but to marshal this all yourself. Which looks something like this:

[StructLayout(LayoutKind.Sequential)]
public struct _Key
{
    public IntPtr keyName;
    public uint keySize;
}

[StructLayout(LayoutKind.Sequential)]
public struct _KeyHolder
{
    public string name;
    public uint numKeys;
    public IntPtr keys;
}

public struct Key
{
    public string keyName;
    public uint keySize;
}

public static _KeyHolder CreateKeyHolder(string name, Key[] keys)
{
    _KeyHolder result;
    result.name = name;
    result.numKeys = (uint)keys.Length;
    result.keys = Marshal.AllocHGlobal(keys.Length * Marshal.SizeOf(typeof(_Key)));
    IntPtr ptr = result.keys;
    for (int i = 0; i < result.numKeys; i++)
    {
        _Key key;
        key.keyName = Marshal.StringToHGlobalAnsi(keys[i].keyName);
        key.keySize = keys[i].keySize;
        Marshal.StructureToPtr(key, ptr, false);
        ptr += Marshal.SizeOf(typeof(_Key));
    }
    return result;
}

public static void DestroyKeyHolder(_KeyHolder keyHolder)
{
    IntPtr ptr = keyHolder.keys;
    for (int i = 0; i < keyHolder.numKeys; i++)
    {
        _Key key = (_Key)Marshal.PtrToStructure(ptr, typeof(_Key));
        Marshal.FreeHGlobal(key.keyName);
        ptr += Marshal.SizeOf(typeof(_Key));
    }
    Marshal.FreeHGlobal(keyHolder.keys);
}
Witting answered 18/3, 2015 at 17:31 Comment(6)
The function actually takes an array of key holders, but that's not actually the issue. The only issue I have left is the key string not making it to the C++ side. Can you give an example to what you have just explained?Introrse
I've updated my post to contain my latest code, where I iterate over all the keys and marshal them.Introrse
What you suggested regarding the Key array is not working: Cannot marshal field 'keys' of type 'KeyHolder': Invalid managed/unmanaged type combination (Arrays fields must be paired with ByValArray or SafeArray).Introrse
Ok. I deleted that. I can't write any more code right now. If you are still stuck later, I'll have time.Witting
Well I am officially stuck. I even tried to change the string to one(!) char, and it comes out corrupt.Introrse
Apparently I can't access my computer remotely. I'll try it first thing in the morning and report back. Thanks!Introrse
G
-1

You still need to tell .NET to marshal the strings as PChars:

[StructLayout(LayoutKind.Sequential)]
public struct Key
{
      [MarshalAs(UnmanagedType.LPStr)]
      public string key;
      public uint size;
}

[StructLayout(LayoutKind.Sequential)]
public struct KeyHolder
{
      [MarshalAs(UnmanagedType.LPStr)]
      public string name;
      public uint keyCount;

      public Key[] keys;
}
Girandole answered 18/3, 2015 at 17:5 Comment(4)
Even if I add that attribute, the key string is corrupt. I think the Marshal.StructureToPtr for the entire Key object corrupts it.Introrse
@Introrse Aaah, of course, you can't do that when you're using Marshal.StructureToPtr. Either make the invoke work all the way through (e.g. Key[] instead of IntPtr), or handle the strings manually as well - IntPtr key, and Marshal.PtrToStringAnsi.Girandole
Isn't PtrToStringAnsi the opposite? I need to convert a string to IntPtr, right?Introrse
@Tsury: Oh, I thought you want to read those. If you want to pass some, Marshal.StringToHGlobalAnsi is your man. Do make sure either you (usually you, right after the method call) or the library free up the memory, though.Girandole

© 2022 - 2024 — McMap. All rights reserved.