[Mono-list] PInvoke Conventions

James Perry jeperry@uwaterloo.ca
Mon, 30 Jul 2001 21:59:38 +0100


Hi there,

this turned out longer than I wanted, but oh well. :)

>So, there are a couple of different approaches.
>One possibility is to access such routines by writing a wrapper, e.g. in C,
>that provides the same functionality without using types with a
system-dependent
>representation.  The wrapper can then be directly accessed from portable
>.NET code.  The .NET code remains both source- and binary-portable;
>the wrapper code is source-portable, but needs to be compiled
>seperately for each target platform.  The drawback of this approach is
>that you have to write a lot of cumbersome wrapper code.

>Another possibility is to extend the .NET VM with support for an
>additional custom attribute, e.g. "[PosixType]".  The VM would then
>represent types tagged with this attribute in the same way that the
>underlying system represents those types.  With this approach, no
>wrapper code would be needed.  A drawback of this approach is that it
>pushes quite a bit of complexity into the VM; the VM would have to know
>the native representation of all types annotated with this attribute.
>Another drawback is that code using this extension might not work on
>different VMs.

Microsoft certainly don't have to concern themselves with this - they aren't
aiming to make a portable runtime. Conversley, however, the documentation
leads me to believe that they did try to make things a little easier for the
rest of us. It looks like they've specified something of a cross between the
custom PosixTypeAttribute (which would obviously produce non-portable code)
and the platform dependent wrapper.

I'm looking at the System.Runtime.InteropServices namespace, and in
particular: StructLayoutAttribute, Marshall, MarshalAs and UnmanagedType.
The obvious implementation of PInvoke (at least, to me) would be to
implement it using Marshall and then define the actual values using
[StructLayout] and MarshalAs - a platform dependent wrapper might be
necessary, but at least using these _specified_ parts of the runtime and
class library, PInvoke and all of its platform dependent wrappers can be
written in C# without any needing any custom attributes (which would
restrict such code to Mono only). Looking at UnmanagedType and
StructLayoutAttribute, it seems to me that most POSIX platforms could be
swept up in a single implementation - it'd take a pretty broken
implementation of POSIX to fool this. Along the lines of having an external
wrapper, all the structs for external stuff could be located in a seperate
file, which could be replaced for the more esoteric platforms - and since
this is written for the CLI you don't have to have access to the platform
itself to write this, most of these esoteric platforms have similarly
esoteric compilers, IME :).

On another project, we had problems with platform independence, and
effectively implemented a solution of wrapping platform dependent sections
up and implementing different versions for different architectures; in
hindsight this was not an optimal solution (but seeing as how the project
was in a mix of C & C++, there weren't many better options available).
Wrapping the platform dependence into a single location (the runtime, which
has to know about these things anyway) is a much better and more elegant
solution.

The other suggested implementations of a PosixTypeAttribute class, or
external wrapper libraries are effectively the same thing, just in different
locations: move all information about platform dependencies and the logic of
how to deal with them into platform-dependent code (the runtime in the case
of PosixTypeAttribute, or a shared library for external wrappers); using the
runtime interop services is a bit different: you have the information about
platform dependencies outside and the logic of how to convert inside.

If you don't need convincing that these two methods aren't particularlly
good, you can skip the following section...

<reasons why the other two methods suck>
Implementing PInvoke using PosixType isn't particularly wonderfull for
reasons already mentioned, but here they are anyway: it's a lot of work to
be done by the runtime which, IMHO, should be kept as simple as possible
(given that it has to be portable, it'll be particularly sensitive to bugs
and incompatabilities, as well as being a single point of failure for every
application which has to run from Mono). Additionally, PosixType means
you're writing something which isn't portable to other .NET
implemenetations - something which really runs against one of the big points
for writing an application in a framework like .NET.

The other suggested implementation, having external wrappers in unmanaged
code is plagued with similar problems. From the mono-development side, it's
again a lot of time doing a relatively simple, repetitive task (but not so
simple or repetitive to make it very easily scriptable), this time would be
usefull on other parts of the project. Also from the mono-development side,
it makes porting mono to other platforms difficult since you need a platform
dependend implementation of the library; this is mitigated by the fact it
could probably be done once and then left to an autoconf script to
fine-tune, but that is also a lot of work which ultimately means the runtime
developers are now faced with a much larger base of code they have to worry
about portability issues for which, for reason explained in the previous
paragraph, I think is a bad idea.

When you start considering 3rd party developers (you know, the people who
would actually /use/ mono:) it's a major barrier for two reasons, one of
which hasn't been touched on yet. The first one means that if you want to
write something which talks to an external library you can either a) rewrite
the library in Mono (bad) or b) become responsible for writing a wrapper for
every platform mono supports (bad) or c) making a wrapper for a single
platform (defeats the point of using a platform independent architecture, so
bad). I can't see a good solution to this.

For the other negative point, consider this hypothetical situation: you have
a developer who has some experience in C but who is most used to C# and Java
writing an application which has to call an external bit of unmanaged code.
This person is now forced to write a wrapper for a library which he or she
might not entirely understand; this code, through his or her own error, or
due to a library issue which they didn't predict, might overflow a buffer,
or use sprintf badly or generate a signal and so open up a big security
hole. Seeing as how C# programmers aren't likely to all be competant at
writing secure applications for UNIX (the point of using something like C#
is to abstract yourself from the platform-dependency, isn't it?),  it's not
a question of if this will happen, it's a question of when and how often
vulnerabilities caused by stuff like this will happen.
</reasons why the other two methods suck>

Ultimately, even if the runtime interop stuff isn't sufficient for PInvoke,
it could be implemented in a platform dependent way that mitigates a lot of
the problems of the other two: have a shared library which describes the
platform dependencies in an abstract way (sizes of integers, sizes of chars,
etc...) and then have the logic to convert written in PInvoke based on this
information.

Someone tell me if I'm completely off-base here? :)

 - James Perry