[Mono-devel-list] Overcoming PInvoke limitations in e.g. Mono.Posix

Jonathan Pryor jonpryor at vt.edu
Thu Oct 7 21:16:36 EDT 2004


On Thu, 2004-10-07 at 14:45, Alan Jenkins wrote:
> PInvoke is convenient, but it relies on binary compatability.  

It should be noted that *any* interop layer will rely on binary
compatibility, *unless* you distribute only as source code (and require
that your users have a compiler available at install- or run-time for source
compatibility).

> E.g. much  
> Posix specifications define symbolic constants for C programs which do not 
> have a fixed value across all platforms.

Mono.Posix has a code generator to handle this.  See make-map.cs.  It
generates C code which maps between the managed value and the unmanaged
value.

> The layout of structures also 
> varies between platforms.  PInvoke is sufficient for the microsoft world, but 
> Mono really needs more.  As far as I know the current Mono.Posix 
> implementation doesn't have a complete solution - although I don't know 
> anything about the SDL or GTK bindings.

Define "complete solution."  It has *a* solution, which offers a
framework for future expansion.  Granted, *all* of POSIX isn't wrapped
yet, but it's fairly straightforward to add more support.

> I propose a Mono.InteropServices namespace, with tools to write structs and 
> enums in C# from annotated (with attributes) C# code, with C code as an 
> intermediate step.

Eh?  So...  You write C# code, which contains attributes, which you run
through a code generator, which generates more C# code?  Have I got that
right?

Actually, Mono.Posix OEE's make-map.cs does that, mostly because I was
too lazy to keep the C# and C enum mapping declarations in sync.  Then I
broke this support by adding non-enum methods to PosixConvert (for
DateTime conversions).  Partial classes would be good for this, though.

> The C code would be relatively portable, but the generated 
> C# code would be platform specific.

BAD idea.  Horribly bad.  Platform-specific C# code requires compiling
it for all target platforms, which implies that you need a C# compiler
to be present on those platforms.  This can make bootstrapping
difficult.

Worse is coping with standardization, or lack thereof.  Consider the
dirent structure (see readdir(3)).  The man page says that only d_name
can be assumed, and d_ino is an XSI extension.  Meanwhile linux also has
members d_off, d_reclen, and d_type.  AIX has others.

Even worse, is that there's *actually* a dirent and a dirent64
structure.  The primary difference being the types of the d_ino and
d_off members (32 vs 64 bits).  Ditto for the stat and flock
structures.  I'm sure there are others (I just haven't hit them yet).

What this *really* means is that, for platform-specific C# code, on some
platforms your structure members will be of type `long', and on others
it will be `int'.  How you can have platform-independent code which uses
this platform specific code is beyond me.

I'm sure I'm biased, but the Mono.Posix approach is much more sane. 
Keep the C# side fixed, and vary the C side to map arguments to the
corresponding C# structures.

<snip/>

> Stop me if this is already being worked on - I know its obvious, it's just 
> nobody seems to have got around to it yet!  Unless I've missed something.

You're missing the details. :-)

Which I've been hitting.  I had been thinking that there has to be a
better way with Mono.Posix, but I was convincing myself that it was a
terribly difficult problem.  Because it is.

However, your discussion has brought up some ideas for future
implementation.  Basically:

  - Keep all declarations in C#
  - Tag everything that needs to have mapping C code generated
    (similar to the [Map] attribute used by make-map.cs)
  - Tag methods that have 64-bit "overloads" as well.

A possible example:

	[Map, Native ("stat"), Has64 ("stat64")]
	public struct Stat {
		[Native ("dev_t")] public ulong st_dev;
		[Native ("ino_t")] public ulong st_ino;
		[Native ("mode_t")] public uint st_mode;
		// ...
	}

	public static class Syscall {
		[Map, Has64 ("stat64"),
		DllImport ("HelperLib", SetLastError=true,
			EntryPoint="my_stat")]
		public static extern int stat (string file_name, 
			out Stat buf);
		// ...
	}

Your code generator could then pick out all the elements marked with
[Map], generate appropriate C intermediate structures, and generate
appropriate function definitions for the intermediate layers.

The Has64 attribute is needed so that the code generator knows what the
appropriate 64-bit function to call, if necessary.  Alas, you can't just
append "64" to the function name, as readdir_r(P) is readdir64_r(). 
Sigh.

Of course, that's a lot of attributes to keep track of and ensure is
consistent.  Better the code generator than me, I guess... 

It won't be easy.  I wish you luck. :-)

 - Jon





More information about the Mono-devel-list mailing list