minvoke - forcing portability
Portability to Mono is pretty easy when writing applications entirely in C#. Winforms works well enough, just about anything console-based will work, and as long as you avoid some obviously broken areas of the API (System.Management, I’m looking at you), you’re usually ok.
We’ve done much in the area of migration tools, such as providing the excellent MoMA, which offers you a easy way to both get information about your problem areas (if you’re calling into unimplemented apis, making pinvokes, etc), and inform the mono project (anonymously, if you desire) so that we can use the information to prioritize missing features.
But we can only do so much. Some win32 hackers remain tied to user32.dll, kernel32.dll, and other platform dlls. It’s rather a shame that a winforms app loses out on mono portability simply because they’re pinvoking SendMessage or something. If the developer cares about portability you can sometimes motivate them to correct the problem, but sometimes you’re faced with an old, unmaintained app, or a cantankerous developer, or something provided by a faceless corporation with nobody to complain *to*.
This is where minvoke comes in. Simply put, it uses the insanely awesome Cecil to rewrite pinvokes into managed method calls.
Take the following trivially broken (on linux/osx) code:
using System;
using System.Runtime.InteropServices;
class MyBrokenExecutable {
[DllImport("kernel32.dll", SetLastError=true)]
static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
public static void Main (string[] args)
{
long perfCount;
if (QueryPerformanceCounter (out perfCount)) {
Console.WriteLine ("QueryPerformanceCounter returned {0}", perfCount);
}
}
}
if you compile and run this on linux or your mac you’ll see:
$ mono BrokenTest.exe
Unhandled Exception: System.EntryPointNotFoundException: QueryPerformanceCounter
at (wrapper managed-to-native) MyBrokenExecutable:QueryPerformanceCounter (long&)
at MyBrokenExecutable.Main (System.String[] args) [0x00000]
In mono svn, mono-tools/minvoke you’ll see MapAssembly.cs, which contains this:
public class Kernel32 {
...
[MapDllImport ("kernel32.dll")]
public static bool QueryPerformanceCounter (out long performanceCount)
{
Console.WriteLine ("Kernel32:QueryPerformanceCounter called");
performanceCount = 0;
return true;
}
...
}
Note the MapDllImport attribute. minvoke uses that to match up the entry point and dllname with pinvokes, and rewrites the call to invoke this static managed method instead.
QueryPerformanceCounter’s implementation is not extremely useful at the moment. In fact, everything in MapAssembly.cs is just stubbed. But all the same, if you run minvoke:
$ minvoke MapAssembly.dll BrokenTest.exe FixedTest.exe
building list of MapDllImports from map assembly MapAssembly.dll
building list of DllImports in input assembly BrokenTest.exe
retargeting assembly BrokenTest.exe -> FixedTest.exe
retargeting reference to method kernel32.dll/QueryPerformanceCounter
and then run FixedTest.exe, you’ll see:
$ mono FixedTest.exe
Kernel32:QueryPerformanceCounter called
QueryPerformanceCounter returned 0
Keep in mind, this is a proof of concept. It does no type checking of pinvoke signatures, doesn’t handle all the DllImport attributes, and pretty much nothing has been implemented.
My plan is to split up MapAssembly.cs into MapUser32.cs, MapKernel32.cs, etc, and have them implement things in terms of mono’s assemblies (i.e. the SendMessage implementation will call into our managed System.Windows.Forms’s XplatUI layer) wherever possible, and when it’s not possible to completely implement something in managed code, support both linux and osx setups using pinvokes.
I’d love help on it, as otherwise it’ll only get attention when I’m suffering moonlight burnout or have a spare evening (not very often). I’m definitely accepting patches
To take a look at the code, check out the mono-tools module from mono SVN, or grovel around here. Enjoy