C# – char array marshaling in C#

cpinvoke

I am new to C#/.net programming.

I am marshaling the following C# struct from WPF code to a C++ class in an unmanaged C++ dll.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
 public struct CallbackParams
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string displayName;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string userName;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string sipIdentity;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string sipProxyAddress;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string password;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string sipurl;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string calleeURI;

        [MarshalAs(UnmanagedType.U4)]
        public UInt32 releaseCallId;

        [MarshalAs(UnmanagedType.U4)]
        public UInt32 answerCallId;

        [MarshalAs(UnmanagedType.U4)]
        public UInt32 answerCode;

        [MarshalAs(UnmanagedType.U4)]
        public UInt32 timeout;

        [MarshalAs(UnmanagedType.U4)]
        public UInt32 rate_percent;
    }

The C++ class looks like this

    typedef struct CallbackParams_s {
    char displayName[80];
    char userName[80];
    char sipIdentity[80];
    char sipProxyAddress[80];
    char password[80];
    char sipurl[80];
    char calleeURI[80];

    unsigned int releaseCallId;
    unsigned int answerCallId;
    unsigned int answerCode;
    unsigned int timeout;
    unsigned int rate_percent;
} CallbackParams;

In the C# code

    public CallbackParams cb;
    public int code;
    [DllImport("XXXDll.dll", CharSet = CharSet.Ansi, CallingConvention =  CallingConvention.Cdecl)]
    public static extern int DLLCBFunc1([MarshalAs(UnmanagedType.I4)] int someCode, ref CallbackParams cbParams);

    cb.displayName = "XYZ";
    ... ...
    ... ...
    ...Init other fields in struct ....
    ...
    cb.calleeURI = "ABC";
    code = 0;
    DLLCBFunc1(code, ref cb);

In the above call the cb struct is marshalled correctly to the unmanaged dll.
Now,

    cb.calleeURI = "DEF";
    code = 1;
    DLLCBFunc1(code, ref cb);

When the DLLCBFunc1 is called again, the code parameter is marshalled correctly in the unmanaged dll but cb.calleeURI is still set to the earlier "ABC" than "DEF".

What am I missing?

Appreciate your help.

EDIT:
Edited to provide more code
C++ code 
Class MyClass {
...
...
public:
   void SetCBParams(CallbackParams *cb) { cbParams = cb };
private:
   CallbackParams *cbParams;
}

MyClass *m_class;
extern "C" __declspec(dllexport) int DLLCBFunc1(int code, CallbackParams *cb) 
{
    if(code == 0) {
        m_class = new MyClass;
    }
    m_class->setCBParams(cb);
    ....
    ..call some func ...
}

Best Answer

answer:

Have a look at David Heffernan's comments - he seems more knowledgeable and to have spent a lot more time on this.

previous attempts:

Then again, after some more search, that should answer your exact question.

If you want to marshal to char* (which basically is the same as char[]) you will need to use a StringBuilder.

See this completely unrelated example that shows the signature from pinvoke.net.

You should then either recreate your structure or internally copy/convert it (if used elsewhere as well) to the following:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
 public struct CallbackParams
 {
     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
     public StringBuilder displayName;

     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
     public StringBuilder userName;
} 

Also be careful that StringBuilder already does all the marshalling automatically.. also there is no need for the ref anymore. Actually, using ref will cause problems.

cb.displayName = "XYZ";
cb.userName = "ABC";
code = 0;
DLLCBFunc1(code, cb);

Don't know out of my head about assigning strings to stringbuilders.. might be you have to do

cb.displayName = new StringBuilder("XYZ");

edit

As David Hefferman has pointed out.. I have missed the point. :)

So... in your case you probably will have to declare the char[80] as Byte[80] arrays and manually copying the string's content, also making sure to mind the likely unicode->ansi conversion. Also, after the call, you will need to copy back.

You can find some info about doing that here on StackOverflow.

edit

Put answer to top.