Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / .NET Framework / Interop / February 2008

Tip: Looking for answers? Try searching our database.

marshalling a complex C function

Thread view: 
Enable EMail Alerts  Start New Thread
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 Magazines

Get 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 ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.