[Mono-list] need some help with PInvoke..
Jonathan Pryor
jonpryor@vt.edu
09 Jul 2003 14:15:09 -0400
I suspect that what you want to do isn't possible.
You state that you're using opaque data types. So, to paraphrase a lot,
you have an API like:
/* C language */
typedef void* HDATA;
char* GetDescription (HDATA data);
void CreateData (HDATA* to_init);
/* ... */
This doesn't have an exact "mesh" with P/Invoke. There are two ways to
wrap something like the above.
The first way is similar to what's done in Gtk# -- use System.IntPtr
instead of "void*" and use the IntPtr exclusively as a pointer into
managed memory:
// C#
public class Data {
[DllImport ("mylib")]
private static extern string GetDescription(IntPtr d);
[DllImport ("mylib")]
private static extern void CreateData (out IntPtr init);
private IntPtr handle;
public Data () {
CreateData (out handle);
}
public string Description {
get {return GetDescription (handle);}
}
}
This works, but has IntPtr's everywhere.
The second method would be to just use unsafe code everywhere, and use
void* as the data member instead of a IntPtr. Otherwise, it looks the
same.
However, what you posted doesn't exactly match the above. You have a
"mostly opaque" data structure (given that your C code directly
references the `desc' member of the NOERR class).
There's a problem with this. The .NET/mono runtime systems make two
assumptions: structs are located on the stack, and classes are allocated
in garbage-collected, .NET-controlled, memory. Non-stack, non-garbage
collected memory doesn't enter the picture AT ALL. This runs directly
contrary to your example code, where `hdf_init' is allocating
(unmanaged) memory and storing a pointer to it in *hdf. You're trying
to mix the two memory heaps (managed vs. unmanaged), and the runtime
won't like that.
For example, if we assume the C code:
void hdf_init (HDF** hdf) {
*hdf = malloc (sizeof(HDF));
}
and the C# code:
class Hdf {
int foo;
}
class User {
[DllImport("")]
private static extern void hdf_init (ref Hdf);
public static void Main () {
Hdf h = null;
hdf_init (ref h);
}
}
Recall that .NET class variables are actually pointers, so the argument
for `User.hdf_init' is correct -- a pointer to a pointer is actually
passed. The problem is the interaction between the C code, and how it
breaks the .NET memory model assumptions. After the call to hdf_init,
`h' will actually refer to unmanaged, non-GCed memory. FURTHERMORE,
what the memory is actually pointing to is no longer a .NET `Hdf' type.
.NET class types contain extra object header information, for use with
run-time type checking and the virtual function mechanism. So if you
attempt to use this, you can either expect an error on the first use of
`h', or an error/segfault on the next garbage collection.
In short, you can't do that. Mixing managed and unmanaged memory can
only really be done in Managed C++, and you have to be *very* careful
when you do so (paying careful attention to the issues outlined above).
So, what's the solution? Expose your HDF type as a *fully* opaque data
type, providing C functions to access whatever data is within it,
instead of using "normal" member access. Use the accessor functions
from C#. and make sure you keep the managed and unmanaged memory
separate.
See the generated Gtk# sources for an example of this. C code can
directly access GObject members, but (1) that's not good style (you're
not supposed to do that), and (2) it's impossible to do that from C#.
- Jon
P.S. Sorry for the long email/rant. Also, don't try actually compiling
the code printed above -- I didn't try compiling it, it's for
demonstration/example purposes only (to help make my point).
On Wed, 2003-07-09 at 03:37, David Jeske wrote:
> I'm trying to write a PInvoke wrapper and I'm having trouble doing
> something pretty basic. Perhaps someone can help me.
>
> The C-function I'm trying to call has this prototype:
>
> NEOERR* hdf_init (HDF **hdf);
>
> As far as C# is concerned, HDF and NEOERR are just opaque
> data-structures.
>
> When hdf_init runs, it allocates some datastructures, and passes back
> the pointer through the externally allocated hdf pointer. I just want
> to print the address of this structure so I can see that something is
> working. Here is a C-program that does what I want..
>
> #include <ClearSilver.h>
>
> int main() {
> HDF *hdf;
> NEOERR *err;
>
> err = hdf_init(&hdf);
>
> if (err) {
> printf("error: %s\n", err->desc);
> return 1;
> }
>
> printf("success: 0x%X\n", hdf);
> }
>
> I can't seem to get this to work through C# and PInvoke with
> mono.. (All of this is with Linux. I can't try with csc right now
> because I don't have the dll built for windows) My attempt is here:
>
> using System;
> using System.Runtime.InteropServices;
>
> [StructLayout(LayoutKind.Sequential)] public class HDF {}
> [StructLayout(LayoutKind.Sequential)] public class LPHDF {
> //[MarshalAs(UnmanagedType.LPStruct)]
> //public HDF hdf;
> public int hdf;
> }
>
> public class Hdf {
> // NEOERR* hdf_init (_HDF **hdf);
>
> [DllImport("libneo.so", EntryPoint="hdf_init")]
> [return: MarshalAs(UnmanagedType.LPStruct)]
> private static extern void hdf_init(
> [ MarshalAs(UnmanagedType.LPStruct)]
> LPHDF hdf);
>
> public static int Main(string[] argv) {
> Console.WriteLine("start test");
>
> LPHDF lphdf = new LPHDF();
> hdf_init(lphdf);
> Console.WriteLine(lphdf.hdf);
>
>
> return 0;
> }
>
> };
>
> Whenever I run this, it just prints out "0" as the alleged value of
> lphdf.hdf. This is no good. Any suggestions?
>
> If you want to poke at the real stuff, you can download libneo.so here:
>
> http://mozart.chat.net/~jeske/_drop/pinvoke/
>
> libneo is built out of the clearsilver package from here:
>
> http://www.clearsilver.net/