[Mono-list] need some help with PInvoke..

David Jeske jeske@chat.net
Thu, 10 Jul 2003 10:13:45 -0700


Thanks for the help Jonathan, it's just what I needed!

On Thu, Jul 10, 2003 at 10:58:16AM -0400, Jonathan Pryor wrote:
> First of all, IntPtrs, shouldn't be exposed to client code, if at all
> possible.  Granted, this isn't always possible (S.W.F exposes them
> everywhere so you can manually call Win32 functions, and the Gtk#
> wrapper also exposes them), but ideally you could provide a complete
> wrapper around a type, and not need to expose an IntPtr.

We can all see that the reality is that they need to be exposed in
many places. 

IMHO, DllImport should always be "unsafe", and HWND handles should be
unsafe struct pointers. That way any code that wanted to load some new
function and call it directly would have had to be marked unsafe to
use both the struct pointer and DllImport. That seems to mirror the
real world since that code will in fact be pretty unsafe.

I'm not sure what benefit we get by letting "safe" code mess around
with IntPtr, or call DllImported functions with "allegedly correct"
marshaling options.

It seems like currently the unsafe definition means "may violate the
type system", which makes it pretty odd that IntPtrs can be touched by
"safe" code. If I had my way (fat chance), I would change that
definition to "safe code should never cause a segfault". Anywhere that
DllImport is being used can easily cause a segfault, and anywhere
IntPtrs are passed to the wrong place can also cause a segfault
(although it will occur elsewhere), thus they are pretty "unsafe" in
my book. :)

However, we're not redesigning .NET here, so none of that matters too
much. Back to the regularly scheduled programming...

> Alternatively, creating a new struct that just has an IntPtr member
> should be an equivalent, which would allow some type safety.  I'm
> surprised I don't see this more often.

That's what I tried to do initially in my code, but since all the
marshal examples I had used classes, I was making the mistake of using
classes also. My take away is this:

 - If I want to copy the data into managed memory by marshaling, I use
   a class.

 - If I want to reference the data in-place in unmanaged memory, I use
   an unsafe struct and a struct pointer.

 - Since an IntPtr is basically a void*, I don't see why I would ever
   use it, unless the external call actually takes a void*.

> The `char' type is an unsigned 16-bit type.  Your other functions
> specify that string marshaling should be done as LPStrs (an 8 bit
> type).  Which means there's a mismatch between your structure and method
> signatures.

Actually, I used:

  Marshal.PtrToStringAnsi((IntPtr)p->name);

Which did exactly the right thing even though you are correct about my
mis-use of char *. I'll change it to "byte*". 

> You can convert it into a System.String by using the
> System.String.String(sbyte*) constructor

Oohh! That's exactly what I was looking for. My strings are actually
in UTF, so I can do:

  string name = new String.String(p->name,0,strlen(p->name),UTF8Encoding);

I was worried that I was going to have to marshal the byte* into a
managed byte[], use convert to go from UTF8 to UCS2 in a byte[], and
then convert to a String. Too many copies. Using String.String() is
much better, thanks! (although the underlying implemenation might
still do the copies, it theoretically can be optimized someday)

> Going from System.String to a sbyte* would likely require that you
> P/Invoke to malloc/free (or whatever memory management functions your C
> code uses), allocate unmanaged memory, and do the copy yourself (or use
> System.Runtime.InteropServices.Marshal.Copy(byte[], int, IntPtr, int)). 
> You'll have to convert the System.String to a byte[] first, though,
> which will likely require using the System.Text.Encoding class (making
> sure that you use the same encoding as your C code does).

It looks like I can write a Custom Marshaler which handles UTF8:

 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemruntimeinteropservicesicustommarshalerclasstopic.asp

However, based on what I can find in Convert, it looks like I'll have
to do the copying I talked about above (in reverse). My strings are
pretty small, so this shouldn't be too big a deal. However, I'm going
to be pushing strings out alot more often than I'm pulling strings
back in, so this is unfortunate.

If anyone knows of a way to marshal a .NET string into a UTF8 encoded
sbyte* in a single copy, speak up. :)


Anyhow, thanks for the help, my code seems to be doing what I need it
to do now. Go Mono!

-- 
David Jeske (N9LCA) + http://www.chat.net/~jeske/ + jeske@chat.net