About an inherent limitation, and a possible solution 2
The linker is a static tool. It uses Mono.Cecil to analyze the assemblies, modify them, and save them back in a linked form.
That means that the linker can only know about the assemblies and the types that are statically referenced. You already see where I’m going to. Mono, just like .net, provides very interesting, and also widely used, ways to dynamically load assemblies and types. The common use case is a plug-in framework for instance, where assemblies are loaded when asked to.
As an example, let’s have a look to a piece of code I’ve faced when working on linking MoMa:
static Socket() {
Assembly ass;
try {
ass = Assembly.Load (Consts.AssemblyMono_Posix);
} catch (FileNotFoundException) {
return;
}
unixendpointtype=ass.GetType("Mono.Posix.UnixEndPoint");
Type[] arg_types=new Type[1];
arg_types[0]=typeof(string);
ConstructorInfo cons=unixendpointtype.GetConstructor(arg_types);
object[] args=new object[1];
args[0]="nothing";
unixendpoint=cons.Invoke(args);
}
From System.Net.Sockets.Socket in System.dllThis code is fairly simple to understand. The the Socket class constructor will be called, the assembly Mono.Posix will be loaded, and an instance of the type Mono.Posix.UnixEndPoint will be created, and its constructor will be invoked.
That’s the marvel of the reflection API. But as you can see, everything here is dynamic. The assembly System.dll contains no reference to the Mono.Posix.UnixEndPoint type, and no reference to the assembly Mono.Posix.
The linker can take two different kind of input. The first one is of course an assembly. It’s obvious what it does, the linker will link everything necessary for this particular assembly. The second one, is an xml file, that describes what types to save, and you can even choose in those types which fields or methods you want to link.
So, when linking your assemblies, you have to know if you’re doing some dynamic loading. If yes, you have to tell the linker using a xml descriptor which dynamic types you want to save, otherwise, they may very well be not present in the output assemblies.
That’s a little bit painful, and that forces you to maintain a list of such types/assemblies. To ease your pain, I wrote something that I find really useful, even if it’s not completely done yet.
Guys, that was the biggest introduction ever to talk about a little piece of code. This little guy is a profiler for Mono, that is simply triggered every time the JIT will leave a method.
It’s goal is to produce an xml file that the linker understand, from a running application. For instance, before linking MoMa, I used:
# mono --profile=link:moma.xml ./MoMa.exe
Then I’ve simply used MoMa like I would if I was simply analysing an application to submit a report, and at the end, I had an interesting moma.xml file. When opening it, I’ve seen:
<linker>
<!-- ... -->
<assembly fullname="Mono.Posix, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756">
<type fullname="Mono.Posix.UnixEndPoint">
<method signature="System.Void .ctor(System.String)" />
</type>
<!-- ... -->
</assembly>
<!-- ... -->
</linker>
As we can see, during the execution, we’ve been using the constructor of the type Mono.Posix.UnixEndPoint in the assembly Mono.Posix. That’s an information the linker would not have been able to know. By using the profiler, one can prepare the linker to do its job with more data that it would have been able to gather by itself.And that’s pretty good to be able to do it without requiring me to write xml descriptors for every possible type that is dynamically loaded.
See, I can even write a few lines of C, isn’t that amazing?
Bat Monobile 1
These last days, I’ve been working on a little framework based both on Boo and NUnit called Bat. Bat stands for Boo Assembly Tester, and is attended to simplify the creation of unit tests for everything Cecil based.
For instance, let say I want to test the way Cecil emits assemblies, with a very simple Hello World assembly, here is the declaration of the test:
[Test]
public void CreateHelloWorld ()
{
RunWriteAssemblyTestCase ("HelloWorld");
}
And here is the actual test:
""" Hello, world! """ worker = Main.CilWorker worker.Emit(OpCodes.Ldstr, "Hello, world!") worker.Emit(OpCodes.Call, ImportConsoleWriteLine()) worker.Emit(OpCodes.Ret)
The method RunWriteAssemblyTestCase will compile the Boo script, run it, and thus create a new assembly, run the assembly, and ensure that the output match the documentation of the Boo script.
The framework provides helper to test the ability to read assemblies, or to round trip them, and ensure that they still work as expected. The good thing is that it should be pretty easy to create it’s own helpers on top of that to write tests for assembly linking, merging, or for whatever tool that manipulates Cecil assemblies.
If you’re interested in writing unit tests for Cecil, you can checkout at the bat project and the updated Mono.Cecil.Tests project. People working with Cecil, don’t hesitate to submit new unit tests now that it’s both fun and easy!
This work is eavily influenced by what my friend Rodrigo and I have done on our different projects that always end up manipulating assemblies :) Thank you Rodrigo!
404
404 is my account id on ohloh. How amazing.
Stack Cecil!
Mono.Cecil Group
Following an excellent suggestion, I’ve created a Google Group for Mono.Cecil related stuff, as I ended up answering every question by mail.
You can access it here: Mono.Cecil group.
Please, dear all Cecil users, please post questions there instead of sending them to me by mail. Thanks!
And now for no reason but beauty, a picture of my trip in Brazil:
