简体   繁体   中英

Marshalling Structs from C# to C++ DLL

I have a C DLL which accesses a proprietary database. I want to access this from a C# application which will be used to convert the data into a SQL database.

I am stuck at the moment with marshalling a particularly complex structure from C#.

My C structures are defined as following

typedef struct iseg {
    short   soffset;        /* segment offset   */
    short   slength;        /* segment length   */
    short   segmode;        /* segment mode     */
} ISEG, *LPISEG;


typedef struct iidx {
    short     ikeylen;        /* key length     */
    short     ikeytyp;        /* key type       */
    short     ikeydup;        /* duplicate flag */
    short     inumseg;        /* number of segments */
    LPISEG    seg;            /* segment information    */
    char      *ridxnam;       /* r-tree symbolic name   */
} IIDX, *LPIIDX;


typedef struct ifil {
    char        *pfilnam;       /* file name (w/o ext)  */
    char        *pfildes;       /* file description     */
    unsigned short  dreclen;        /* data record length   */
    unsigned short  dxtdsiz;        /* data file ext size   */
    short       dfilmod;        /* data file mode   */
    short       dnumidx;        /* number of indices    */
    unsigned short  ixtdsiz;        /* index file ext size  */
    short       ifilmod;        /* index file mode  */
    LPIIDX      ix;             /* index information    */
    unsigned short  rfstfld;        /* r-tree 1st fld name  */
    unsigned short  rlstfld;        /* r-tree last fld name */
    int     tfilno;         /* temporary file number*/
    char        datetime;       /* Update Date & Time Fields */ 
} IFIL, *LPIFIL;

I have tried a heap of different variants, but this is what my C# structures look like

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, Pack=1)]
public unsafe struct iseg
{
    public short soffset;
    public short slength;
    public short segmode;
}

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, Pack=1)]
public unsafe struct iidx
{
    public short ikeylen;
    public short ikeytyp;
    public short ikeydup;
    public short inumseg;

    [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, ArraySubType = System.Runtime.InteropServices.UnmanagedType.Struct)]
    public iseg[] seg;

    [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
    public string ridxnam;
}

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, Pack=1)]
public unsafe struct ifil
{
    [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
    public string pfilnam;

    [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
    public string pfildes;

    public ushort dreclen;
    public ushort dxtdsiz;
    public short dfilmod;
    public short dnumidx;
    public ushort ixtdsiz;
    public short ifilmod;

    [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, ArraySubType = System.Runtime.InteropServices.UnmanagedType.Struct)]
    public iidx[] ix;
    public ushort rfstfld;
    public ushort rlstfld;
    public int tfilno;
    public byte datetime;
}

I am getting the following exception

A first chance exception of type 'System.AccessViolationException' occurred in Conversion.dll An unhandled exception of type 'System.AccessViolationException' occurred in Conversion.dll

Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

I can't debug the C DLL, even though I selected the Debug Unmanaged Code option in the project. It maybe that the issue is occuring in the marshalling code?

My call to the c code is

public class NativeMethods
{
    /// Return Type: int
    ///lpIfil: LPIFIL->IFIL*
    [System.Runtime.InteropServices.DllImportAttribute(@"c:\db\debug\db.dll", EntryPoint = "OPNIFIL")]
    public static extern int OPNIFIL(ref ifil lpIfil);
}

if (NativeMethods.OPNIFIL(ref ifil) == 0)
{
    // No error occured
}

Sorry, I do not have enough rep to comment.

But:

Do you initialize this structure in C? As your strings are just pointers, check if your C code is allocating memory for the strings it wants to fill in. Maybe you are just checking in C that they are not NULL and trying to find the end of string... if so, initialize the struct before passing it to C code.

An effective approach to this kind of interop problems is to write a very simple C program or dll, that prints each field of the structure you are passing. When you figure out how the structure is arriving in C, you can replace the DLL with the real one.

Another thing to try is to get the sizeof your string in C and compare with the sizeof reported on C#. Even a one byte offset can cause many problems. Write a sanity function and export it on the DLL:

int sanity()
{
    return sizeof(IIDX);
}

Then, you can make a sanity check in C#, testing the value returned by sanity with the size of the structure calculated on C#. An alignment problem can be difficult to see and if the structures size change in the future, you can have a warning message.

Also, if the strings are allocated in C, think about how to free these strings later on.

Reference: http://msdn.microsoft.com/en-us/library/s9ts558h(v=vs.100).aspx#cpcondefaultmarshalingforstringsanchor2

You've incorrectly declared the members iidx.seg and ifil.ix 1 . You've declared them both as byval, or static, arrays. Because you haven't initialized SizeConst, I think the runtime marshals them as single-element arrays.

This means that the C# runtime thinks that you have a field 6 bytes wide for iidx.seg (the size of one iseg), and 18 or 22 bytes wide (depending on platform) for ifil.ix. But the size of both fields in your C structures is the size of a pointer (4 or 8 bytes, depending on platform).

By the way, you don't need to use the unsafe keyword. You only need that when using pointers, fixed , and sizeof . Marshaling, in general, keeps you from having to use unsafe code.

1 Have you considered using names that bear a stronger resemblance to actual words?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM