Reputation: 175
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
}
Upvotes: 2
Views: 777
Reputation: 29547
You've incorrectly declared the members iidx.seg and ifil.ix1. 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?
Upvotes: 0
Reputation: 930
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.
Upvotes: 1