orikon
orikon

Reputation: 393

SafeArrayTypeMismatchException when passing c# struct to unmanaged C++ DLL

Hi I'm new to C# and C++ programming and trying to use an unmanaged C++ dll in a C# project (.NET 3.5). I'm stuck on this error:

System.Runtime.InteropServices.SafeArrayTypeMismatchException: Specified array was not of the expected type.

Here is the header file for the DLL

#ifdef FUNCTIONLIB_EXPORTS
#define FUNCTIONLIB_API __declspec(dllexport)
#else
#define FUNCTIONLIB_API __declspec(dllimport)
#endif
typedef struct _FunctionOpt 
{
    char                UserName[32];
    char                Password[32];
    char                ServerIP[128];
    int                 ServerPort;
    int                 Index;          
    char                TargetSubChannel;
    long                Timeout;
    char                Filepath[256];
    bool                isFirst;        
    DWORD               SyncTime;               
    char                *pOutBuf;               
    int                 OutBufSize;
    unsigned short      OutImgResW;
    unsigned short      OutImgResH;

}STFUNCTIONOPT, *PSTFUNCTIONOPT;

FUNCTIONLIB_API int FunctionLib_Snapshot( PSTFUNCTIONOPT pstFunctionOpt ); 

I don't have access to the C++ code so I can only change the following C# code. My relevant code is as follows:

    public unsafe struct PSTFUNCTIONOPT            
    {
        public char[] UserName;
        public char[] Password;
        public char[] ServerIP;
        public int ServerPort;
        public int Index;                      
        public char TargetSubChannel;              
        public long Timeout;              
        public char[] Filepath;
        public bool isFirst;               
        public uint SyncTime;                       
        public char *pOutBuf;   // example C++ usage: myStruct.pOutBuf = (char*)malloc(1920 * 1080 * 3);
        public int OutBufSize;
        public ushort OutImgResW;
        public ushort OutImgResH;
    }

    // need to use dumpbin to get entrypoint name due to c++ mangling
    [DllImport(@"C:\Location\To\Project\bin\FunctionLibLib.dll", EntryPoint = "?FunctionLib_Snapshot@@YAHPAU_FunctionOpt@@@Z")]
    public static extern int FunctionLib_Snapshot(PSTFUNCTIONOPT pstFunctionOpt);

    public unsafe int FunctionLib_Run()
    {
        PSTFUNCTIONOPT stFunctionOpt = new PSTFUNCTIONOPT();

        stFunctionOpt.UserName = ("uname").ToCharArray();
        stFunctionOpt.Password = ("pword").ToCharArray();
        stFunctionOpt.ServerIP = ("192.168.1.1").ToCharArray();
        stFunctionOpt.ServerPort = 80;
        stFunctionOpt.Index = 255;
        stFunctionOpt.Timeout = 15000;
        stFunctionOpt.Filepath = ("c:\\temp\\test.jpg").ToCharArray();
        stFunctionOpt.isFirst = true;
        stFunctionOpt.SyncTime = 0;
    //stFunctionOpt.pOutBuf = new char*[10000];     // not sure how to do this yet
        stFunctionOpt.OutBufSize = 10000;

        // get result from DLL 
        return FunctionLib_Snapshot(stFunctionOpt);
    }

How do I properly pass the struct to this unmanaged DLL? The error seems like something simple but I haven't been able to narrow down the problem. Any help is appreciated!

Upvotes: 3

Views: 3772

Answers (2)

Hans Passant
Hans Passant

Reputation: 942119

Several problems:

    public char[] UserName;

That needs to be marshaled as an embedded string:

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string UserName;

Repeat that for the other fields that are declared like this.

    public char *pOutBuf;   // example C++ usage: myStruct.pOutBuf = (char*)malloc(1920 * 1080 * 3);

It is not clear whether the C++ function allocates that buffer or the client code is supposed to do that. If it is the former then you have a Big Problem, you cannot call the free() function to release the buffer again. Your only hope is that your C# code is supposed to allocate it, not unlikely. In which case it is:

    public byte[] pOutBuf;
    ...
    stFunctionOpt.pOutBuf = new byte[10000];
    stFunctionOpt.OutBufSize = stFunctionOpt.pOutBuf.Length;

The pinvoke declaration is wrong:

[DllImport(@"C:\Location\To\Project\bin\FunctionLibLib.dll", EntryPoint = "?FunctionLib_Snapshot@@YAHPAU_FunctionOpt@@@Z")]
public static extern int FunctionLib_Snapshot(PSTFUNCTIONOPT pstFunctionOpt);

The argument is ref PSTFUNCTIONOPT pstFunctionOpt. Do drop the P from the structure name, it is not a pointer type. Avoid hard-coding the path to the DLL, that isn't going to work on your user's machine. Just make sure that you have a copy of the DLL in your build output directory.

Upvotes: 4

Erko
Erko

Reputation: 391

I think you are missing a couple of things from your structure definition

  • The layout of struct in memory is different in c++ so you need to add [StructLayout(LayoutKind.Sequential)] attribute to your structure
  • In C++ you have fixed length char arrays, so you need to specify that also in c# using attribute [MarshalAs(UnmanagedType.ByValTStr, SizeConst = your_array_lenght)], moreover if you do that, the marshaler will handle conversions between string and char array.

Your struct definition should then look something like

[StructLayout(LayoutKind.Sequential)]
public struct PSTFUNCTIONOPT            
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string UserName;
    ...
    [MarshalAs(UnmanagedType.LPStr)]
    public string pOutBuf;  
}

For more info check out http://msdn.microsoft.com/en-us/library/s9ts558h.aspx

Upvotes: 1

Related Questions