.NET Forum / .NET Framework / Interop / February 2008
marshalling a complex C function
|
|
Thread rating:  |
Christoph Rupp - 19 Feb 2008 21:03 GMT Hi,
i have the following function in my C library:
ham_status_t ham_cursor_move(ham_cursor_t *cursor, ham_key_t *key, ham_record_t *record, ham_u32_t flags);
it's possible to call the function in 4 several ways:
ham_key_t key; ham_record_t rec;
ham_cursor_move(cursor, NULL, NULL, flags); ham_cursor_move(cursor, &key, NULL, flags); ham_cursor_move(cursor, NULL, &record, flags); ham_cursor_move(cursor, &key, &record, flags);
if the second and/or third parameter is set, the structures key and record are filled with values; if they are NULL, they are ignored.
In C#, i have not yet found a way to get the same behaviour (i'm a C# noob, though). I defined KeyStruct and RecordStruct as public structs.
[DllImport("hamsterdb.dll", EntryPoint = "ham_cursor_move")] static private extern int CursorMoveLow(IntPtr handle, ref KeyStruct key, ref RecordStruct record, int flags);
However, when i want to pass null as second or third parameters, i get errors:
CursorMoveLow(handle, null, null, flags);
error CS1503: Argument '2': cannot convert from '<null>' to 'ref Hamster.LowLevel.KeyStruct' error CS1503: Argument '3': cannot convert from '<null>' to 'ref Hamster.LowLevel.RecordStruct'
I also tried the function declaration with "out" instead of "ref", but it didn't work, too. And i tried without "out" or "ref" - also no success.
How can i solve that problem? Every help is appreciated.
Thanks Chris
Jeroen Mostert - 19 Feb 2008 21:35 GMT > i have the following function in my C library: > [quoted text clipped - 22 lines] > ref KeyStruct key, ref RecordStruct record, int > flags); Unfortunately, the marshaller doesn't support this directly. In order to be able to pass a null pointer, you must marshal the types as IntPtr:
[DllImport("hamsterdb.dll", EntryPoint = "ham_cursor_move")] static private extern int CursorMoveLowExternal(IntPtr handle, IntPtr key, IntPtr record, int flags);
You then have to manually allocate memory and marshal the structs when necessary:
static private int CursorMoveLow(IntPtr handle, KeyStruct? key, RecordStruct? record, int flags) { IntPtr keyPtr = IntPtr.Zero; IntPtr recordPtr = IntPtr.Zero; try { if (key.HasValue) { keyPtr = Marshal.AllocHGlobal(Marshal.SizeOf(key.Value)); Marshal.StructureToPtr(key.Value, keyPtr, false); } if (record.HasValue) { recordPtr = Marshal.AllocHGlobal(Marshal.SizeOf(record.Value)); Marshal.StructureToPtr(record.Value, keyPtr, false); } return CursorMoveLowExternal(handle, keyPtr, recordPtr, flags); } finally { if (keyPtr != IntPtr.Zero) Marshal.FreeHGlobal(keyPtr); if (recordPtr != IntPtr.Zero) Marshal.FreeHGlobal(recordPtr); } }
It would be nice if the marshaller knew about nullable types to do this automatically, but it doesn't.
 Signature J.
Christoph Rupp - 22 Feb 2008 15:31 GMT After a lot of experimenting, i have still troubles with marshalling the structures. Precisely, Marshal.PtrToStructure throws an exception which i don't understand.
Here are the two structures:
[StructLayout(LayoutKind.Sequential)] struct RecordStruct { public Int32 size; public byte []data; public Int32 flags; public Int32 _flags; public Int64 _rid; }
[StructLayout(LayoutKind.Sequential)] struct KeyStruct { public Int16 size; public byte[] data; public Int32 flags; public Int32 _flags; }
And here's a function to insert a key/record pair - this works, both structures are not modified in the native library.
[DllImport("hamsterdb.dll", EntryPoint = "ham_insert", CallingConvention = CallingConvention.Cdecl)] static private extern int InsertLow(IntPtr handle, IntPtr reserved, ref KeyStruct key, ref RecordStruct record, int flags);
And here's the function to look up a value in the database. In that case, the "record" is actually modified in the native code. Therefore i had to create an IntPtr from it (passing a normal RecordStruct leads to a crash).
[DllImport("hamsterdb.dll", EntryPoint = "ham_find", CallingConvention = CallingConvention.Cdecl)] static private extern int FindLow(IntPtr handle, IntPtr reserved, ref KeyStruct key, IntPtr record, int flags);
Here is the code which causes the problem. I'm allocating memory and call FindLow(); if it returns successfully, i convert the memory back to the RecordStructure with Marshal.PtrToStructure. This throws an exception (see below).
static public unsafe byte[] Find(IntPtr handle, byte[] data, int flags) { IntPtr recordPtr = IntPtr.Zero; KeyStruct key = new KeyStruct(); key.flags = 0; // just to avoid a compiler warning key._flags = 0; // " key.size = (short)data.GetLength(0); key.data = data; try { RecordStruct record = new RecordStruct(); record._flags = 0; record.flags = 0; recordPtr = Marshal.AllocHGlobal(Marshal.SizeOf(record)); // initialize struct with zeroes - maybe not needed... Marshal.StructureToPtr(record, recordPtr, false); int st = FindLow(handle, IntPtr.Zero, ref key, recordPtr, flags); if (st == 0) { // success record = (RecordStruct)Marshal.PtrToStructure(recordPtr, record.GetType()); return record.data; } return null; } finally { if (recordPtr != IntPtr.Zero) Marshal.FreeHGlobal(recordPtr); } }
Here is the exception:
Unittests.DatabaseTest.FindKey : System.Runtime.InteropServices.SafeArrayRankMismatchException : SafeArray cannot be marshaled to this array type because it has either nonzero lower bounds or more than one dimension. at at System.Runtime.InteropServices.Marshal.PtrToStructureHelper(IntPtr ptr, Object structure, Boolean allowValueClasses) at at System.Runtime.InteropServices.Marshal.PtrToStructure(IntPtr ptr, Type structureType) at D:\hamster\hamsterdb-dotnet\trunk\hamsterdb-dotnet\LowLevel.cs(126)
I understand that the .NET runtime does not know the size of the RecordStruct.data array, my guess is that this leads to the problem. (The size is stored in RecordStruct.size). Any ideas how i can solve that problem?
Thanks Christoph
Stephen Martin - 20 Feb 2008 18:40 GMT You could change your KeyStruct and RecordStruct from structs to classes and the ref keyword to [In, Out]. This should cover all your scenarios.
If there is a reason that KeyStruct and RecordStruct must be structs then rather than doing manual marshalling you could declare four overloaded versions of your imported functions:
[DllImport("hamsterdb.dll", EntryPoint = "ham_cursor_move")] static private extern int CursorMoveLow(IntPtr handle, ref KeyStruct key, ref RecordStruct record, int flags);
[DllImport("hamsterdb.dll", EntryPoint = "ham_cursor_move")] static private extern int CursorMoveLow(IntPtr handle, IntPtr key, ref RecordStruct record, int flags);
[DllImport("hamsterdb.dll", EntryPoint = "ham_cursor_move")] static private extern int CursorMoveLow(IntPtr handle, ref KeyStruct key, IntPtr record, int flags);
[DllImport("hamsterdb.dll", EntryPoint = "ham_cursor_move")] static private extern int CursorMoveLow(IntPtr handle, IntPtr key, IntPtr record, int flags);
And call them like so:
ham_cursor_move(cursor, ref key, ref record, flags); ham_cursor_move(cursor, IntPtr.Zero, ref record, flags); ham_cursor_move(cursor, ref key, IntPtr.Zero, flags); ham_cursor_move(cursor, IntPtr.Zero, IntPtr.Zero, flags);
> Hi, > [quoted text clipped - 43 lines] > Thanks > Chris Christoph Rupp - 21 Feb 2008 08:52 GMT Stephen and Jeroen,
thanks to both of you for the help.
The idea with overloading is great and so simple, and it works fine. I will follow your suggestion.
There is only one reason that i use structs instead of classes - i need to use the StructLayout.Sequential attribute for them, and i don't know if it works for classes.
Regards Christoph
Stephen Martin - 21 Feb 2008 15:06 GMT The StructLayout attribute works the same way on both structs and classes.
> Stephen and Jeroen, > [quoted text clipped - 9 lines] > Regards > Christoph
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|