none
Marshalling native C struct and function to C# RRS feed

  • Question




  • Hello,

    I have to use a C function from C# via the dllImport attribute. The native C function is as follows:

    int C2CcreateContainer(C2CC *c2cc,const char *name,size_t rowSize);


    In native C code, the struct C2CC is defined as follows:

    struct C2CContainer {
      int checkLabel;
      void *table;
      char name[MAX_C2C_TABLENAME_SIZE];
      size_t rowSize;
    };
    typedef struct C2CContainer *C2CC;


    So I tried to do the translation. First the funktion in C#, that follows the dllImport Attribute:

    public static extern int C2CcreateContainer(out C2CContainer c2CContainer, ref string name, uint size);
    Here I took "out C2CContainer" because in native code C2CCointainer is a pointer and in the native function call dereferenced. For "const char *name" I chose "ref string name", cause I heard not to take StringBuilder for chars* constants.

    Further on, I had to do the translation work for the native struct to managed C#. I did it as follows:

        [StructLayout(LayoutKind.Sequential)]
        public struct C2CContainer
        {
    
            public int checkLabel;
    
            public IntPtr table;
            
            [MarshalAs(UnmanagedType.ByValArray, SizeConst=9)]
            public char[] name;
    
            public uint rowSize;
    
        }


    For the native "void *table" I took "IntPtr table".

    Now, there must be at least one mistake.

    I got a problem, because the dll throws an error that it was not successful without any further information.
    Using the dll in native C works perfect, so I'm sure my marshalling causes the error.

    Can anyone help?

    Thanks a lot,

    Markus
    • Edited by Markus.Net Monday, July 27, 2009 12:15 PM
    • Moved by Robert Breitenhofer Thursday, July 30, 2009 2:02 PM English instead German (From:Visual C#)
    • Moved by Figo Fei Friday, July 31, 2009 2:14 AM (From:Visual C# Language)
    Monday, July 27, 2009 12:09 PM

Answers

  • Tough signature.  The 2nd argument first.  Declaring it ref string is wrong, that will corrupt the heap.  Just plain string will work.

    Next, C2CContainer.  The name field declaration is wrong, the 'C' function isn't likely to use Unicode.  Declare it like this:

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct C2CContainer {
          public int checkLabel;
          public IntPtr table;
          [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
          public string name;
          public uint rowSize;
        }

    The 1st argument of the function is tricky, it is C2CContainer**.  That makes it likely that the memory is owned by the DLL.  That's a potential problem, you can't release that memory.  If you have to, you can't use P/Invoke but have to write a C++/CLI wrapper.  If you can, declare the argument type as out IntPtr and marshal the structure yourself:

      IntPtr mem = IntPtr.Zero;
      int retval = C2CcreateContainer(out mem, "Blah", 42);
      if (retval == Okay) {
        C2CContainer cont = (C2CContainer)Marshal.PtrToStructure(mem, typeof(C2CContainer));
        // etc...
      }

    Call this function frequently in a loop and pay attention to the program's memory usage.  If it keeps climbing, you've got a memory leak that needs to be plugged by a wrapper. 
     

    Hans Passant.
    • Marked as answer by Zhi-Xin Ye Friday, July 31, 2009 12:54 PM
    Friday, July 31, 2009 11:08 AM
    Moderator

All replies

  • Tough signature.  The 2nd argument first.  Declaring it ref string is wrong, that will corrupt the heap.  Just plain string will work.

    Next, C2CContainer.  The name field declaration is wrong, the 'C' function isn't likely to use Unicode.  Declare it like this:

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct C2CContainer {
          public int checkLabel;
          public IntPtr table;
          [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
          public string name;
          public uint rowSize;
        }

    The 1st argument of the function is tricky, it is C2CContainer**.  That makes it likely that the memory is owned by the DLL.  That's a potential problem, you can't release that memory.  If you have to, you can't use P/Invoke but have to write a C++/CLI wrapper.  If you can, declare the argument type as out IntPtr and marshal the structure yourself:

      IntPtr mem = IntPtr.Zero;
      int retval = C2CcreateContainer(out mem, "Blah", 42);
      if (retval == Okay) {
        C2CContainer cont = (C2CContainer)Marshal.PtrToStructure(mem, typeof(C2CContainer));
        // etc...
      }

    Call this function frequently in a loop and pay attention to the program's memory usage.  If it keeps climbing, you've got a memory leak that needs to be plugged by a wrapper. 
     

    Hans Passant.
    • Marked as answer by Zhi-Xin Ye Friday, July 31, 2009 12:54 PM
    Friday, July 31, 2009 11:08 AM
    Moderator
  • Thank you, your answer helps a lot!
    Tuesday, August 25, 2009 9:40 AM

  • Hello,

    perhaps anybody can help me once more:

    I have to use a function via pInvoke, in original c++ it goes as follows:

    int C2CaddRow(C2CC c2cc,const char *formatName,int dataLen, const void *data);

    The c++ object C2CC is defined as follows:


    struct C2CContainer {
      int checkLabel;
      void *table;
      char name[MAX_C2C_TABLENAME_SIZE];
      size_t rowSize;
    };
    typedef struct C2CContainer *C2CC;

    I marshalled it - like nobugz told in the thread above:


        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct C2CContainer
        {
            public int checkLabel;
            public IntPtr table;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
            public string name;
            public uint rowSize;
        }

    So, in c#, I marshalled the function like this:


    public static extern int C2CaddRow(ref C2CContainer C2CContainer, [System.Runtime.InteropServices.InAttribute()] 
    
    [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)] string formatName, 
    
    int dataLen, IntPtr data);

    The last argument can be c++ objects which have a structure like this:


    struct GPLOG {
    char MANDA   [2]; 
    char WERKE   [2];  
    char TRCSW   [1];   
    };
    typedef struct GPLOG T_GPLOG;
    typedef struct GPLOG *T_GPLOG_ptr;

    Such Objects, consisting of char arrays, I marshalled like this:


    [StructLayout(LayoutKind.Sequential, Size = 5, Pack = 0, CharSet = CharSet.Ansi)]
    public struct GPLOG
    {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 02)]
            public string MANDA;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 02)]
            public string WERKE;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 01)]
            public string TRCSW;
    }

    So, a complete call in c# looks like this (with objects gPLOG of type GPLOG and c2CContainer of type C2CContainer):


    IntPtr ptGPLOG = Marshal.AllocHGlobal(5);
    Marshal.StructureToPtr(gPLOG, ptGPLOG, false);
    result = Interop.C2CaddRow(ref c2CContainer, "CYG4GPLG", Marshal.SizeOf(gPLOG), ptGPLOG);


    Something still seems to be wrong with this marshalling. In c++, this funcion works fine, in c# I get back an error-return code, but no more hints.


    Can anybody help?

    Thanks a lot -

    Markus

     

    Tuesday, September 1, 2009 10:02 AM
  • struct GPLOG {
    char MANDA [2];
    char WERKE [2];
    char TRCSW [1];
    };

    The TRCSW member of this struct cannot store a string.  In unmanaged C/C++ code strings are arrays of characters terminated by a 0.  Since TRCSW only has one element, it can only ever store an empty string.  The other members could only store a string with a single character.

    Not sure what it they really store but declaring the members in C# as type string is wrong.  Your next best guess is char[].  You'll also need Pack = 1 to make it take 5 bytes.

    Hans Passant.
    Tuesday, September 1, 2009 10:25 AM
    Moderator