none
Problem marshalling the following structure RRS feed

  • Question

  • Hello,

    I'm experiencing a problem in c# tryin to marshall a pointer to a structure inside another structure

    I'm trying to call the following function hosted in a C++ library:

    HRESULT GetNextRow(MYUSERDATA *userdata);
    
    typedef struct _LDAPATTRIBUTE
    {
      WCHAR attributeName[520];
      WCHAR attributeValue[520];
    } LDAPATTRIBUTE, *PLDAPATTRIBUTE;
    
    typedef struct _MYUSERDATA
    {
      WCHAR objectGUID[80];
      WCHAR distinguishedName[520];
      PLDAPATTRIBUTE attributesList;
    } MYUSERDATA, *PMYUSERDATA;
    
    

    In C#, I have created the following definition:

     

        const int MAX_PATH = 260 * 2;
        const int MAX_OBJECTGUID = 40 * 2;
    
        [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct LDAPATTRIBUTE
        {
          /// WCHAR[520]
          [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
          public string attributeName;
    
          /// WCHAR[520]
          [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
          public string attributeValue;
        }
    
        [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct MYUSERDATA
        {
          /// WCHAR[80]
          [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = MAX_OBJECTGUID)]
          public string objectGUID;
    
          /// WCHAR[520]
          [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
          public string distinguishedName;
    
          /// PLDAPATTRIBUTE->_LDAPATTRIBUTE*
          //[MarshalAs(UnmanagedType.ByValArray)]
          //public System.IntPtr[] attributesList;
          public System.IntPtr attributesList;
        }
    

     

    I do not manage to initialize the structures correctly.

    Here is what I've tried so far:

     

          int numberofAdditionnalAttributes = 2;
          Wrapper.MYUSERDATA userdata = new Wrapper.MYUSERDATA();
          Wrapper.LDAPATTRIBUTE[] attributes;
          attributes = new Wrapper.LDAPATTRIBUTE[numberofAdditionnalAttributes];
          // initialize
          for (int i = 0; i < numberofAdditionnalAttributes; i++)
          {
            // attributes[i] = new LDAPATTRIBUTE(); // tried with or without<br/>
             attributes[i].attributeName = string.Empty;<br/>
            attributes[i].attributeValue = string.Empty;<br/>
          }<br/>
    <br/>
          userdata.attributesList = System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(attributes, 0);<br/>
    
    

     

    And I call it :

    hr = engine.GetNextRow(ref userdata);
    

     

    I've also tried, while defining an IntPtr [] in the struct

     

           userdata.attributesList = new IntPtr[numberofAdditionnalAttributes];
          int attributeSize = Marshal.SizeOf(attributes) / numberofAdditionnalAttributes;
          for (int ix = 0; ix < numberofAdditionnalAttributes; ix++)
          {
            userdata.attributesList[ix] = Marshal.AllocHGlobal(attributeSize);
            Marshal.StructureToPtr(attributes[ix], userdata.attributesList[ix], false);
          }
    
    
    Everytime, I'm getting amemory corruption in the C++ code, while writting to the attribute Name or Value (in the first or second iteration loop)

    wcscpy_s(userdata->attributesList[i].attributeName, szAttributes[i]);

     

    In C, I initialize it in the following way and everything works well:

    	MYUSERDATA userdata;
    
    	PLDAPATTRIBUTE attributes;
    	attributes = new LDAPATTRIBUTE[numberofAdditionnalAttributes];
    
    	userdata.attributesList = attributes;
    
    

    How should I declare it in C# ?

    Thansk a lot.

    Saturday, November 13, 2010 4:42 PM

Answers

  • Hmm, you need a combinations of both variants :). Make MYUSERDATA.attributeList an IntPtr and use the following for marshaling:

      MYUSERDATA userdata = new MYUSERDATA();
      LDAPATTRIBUTE[] attributes = new LDAPATTRIBUTE[2];
    
      int attributeSize = Marshal.SizeOf(typeof(LDAPATTRIBUTE));
      userdata.attributesList = Marshal.AllocHGlobal(attributeSize * attributes.Length);
    
      for (int ix = 0; ix < attributes.Length; ix++)
      Marshal.StructureToPtr(attributes[ix], userdata.attributesList + attributeSize * ix, false);
    
      GetRow(ref userdata);
    
      for (int ix = 0; ix < attributes.Length; ix++)
      attributes[ix] = (LDAPATTRIBUTE)Marshal.PtrToStructure(userdata.attributesList + attributeSize * ix, typeof(LDAPATTRIBUTE));
    
    

    The first variant doesn't work because if you simply take the address of that array then the marshaler doesn't get a change to convert the strings from type String to WCHAR[520].

    The second variant doesn't work because you need an array of structs on the C side but you're creating an array of pointers in C#.

    PS: I think it should be added that the first for loop (the one with StructureToPtr) is not required if the C code doesn't read those attributes. And of course, you should free the memory allocated by AllocHGlobal when you no longer need it.

    • Marked as answer by ilinfo Sunday, November 14, 2010 2:39 PM
    Saturday, November 13, 2010 6:15 PM
    Moderator

All replies

  • Hmm, you need a combinations of both variants :). Make MYUSERDATA.attributeList an IntPtr and use the following for marshaling:

      MYUSERDATA userdata = new MYUSERDATA();
      LDAPATTRIBUTE[] attributes = new LDAPATTRIBUTE[2];
    
      int attributeSize = Marshal.SizeOf(typeof(LDAPATTRIBUTE));
      userdata.attributesList = Marshal.AllocHGlobal(attributeSize * attributes.Length);
    
      for (int ix = 0; ix < attributes.Length; ix++)
      Marshal.StructureToPtr(attributes[ix], userdata.attributesList + attributeSize * ix, false);
    
      GetRow(ref userdata);
    
      for (int ix = 0; ix < attributes.Length; ix++)
      attributes[ix] = (LDAPATTRIBUTE)Marshal.PtrToStructure(userdata.attributesList + attributeSize * ix, typeof(LDAPATTRIBUTE));
    
    

    The first variant doesn't work because if you simply take the address of that array then the marshaler doesn't get a change to convert the strings from type String to WCHAR[520].

    The second variant doesn't work because you need an array of structs on the C side but you're creating an array of pointers in C#.

    PS: I think it should be added that the first for loop (the one with StructureToPtr) is not required if the C code doesn't read those attributes. And of course, you should free the memory allocated by AllocHGlobal when you no longer need it.

    • Marked as answer by ilinfo Sunday, November 14, 2010 2:39 PM
    Saturday, November 13, 2010 6:15 PM
    Moderator
  • Just one word: fantastic.

    I just replaced: Marshal.StructureToPtr(attributes[ix], userdata.attributesList + attributeSize * ix, false);

    which returned Operator '+' cannot be applied to operands of type 'System.IntPtr' and 'int'

    by: Marshal.StructureToPtr(attributes[ix], new IntPtr(userdata.attributesList.ToInt32()+attributeSize * ix), false)

     

    Thank you very much.

    I spent a full afternoon fighting to find the correct syntax and 5 minutes after reading you answer the problem was gone.

    Again, Thank you very very much.

     

    For your curiosity, I will start another thread with a curious exception that I didn't find referenced on the net.

     

    Sunday, November 14, 2010 2:39 PM