This time, I’ll write about some issues I faced during development of Mono’s ILAsm and how I got around them. I’ve had to make several changes that make Mono’s ILAsm stricter than MS.NET’s or outright incompatible.
First of all, we have generic parameters. In MS.NET’s ILAsm, !0 and !!0 are simply emitted as VAR 0 and MVAR 0 in the metadata, meaning that you could do something like:
.method public static !!0 Foo(!!1 bar)
{
ldarg bar
ret
}
Even though !!1 refers to a generic parameter that does not exist, MS.NET’s ILAsm will gladly emit it, and not even warn you. Due to an API “limitation” in Cecil, Mono’s ILAsm cannot do this. All GenericParameter objects must have an owner (i.e. the type or method definition/reference) and must be contained in that owner’s GenericParameters collection at the correct index. Therefore, Mono’s ILAsm would throw an error for the above code, since !!0 is out of bounds. This is a slightly annoying incompatibility, but there’s not much I can do about it other than patching Cecil. That’s only going to happen, though, if it won’t cause API breaks for version 1.0.
Second, properties like .file alignment, .imagebase, and .stackreserve aren’t fully supported. Cecil currently has no way of setting these on a module. ILAsm will check the values and error if they’re invalid, though.
Third, several native and variant types are not supported in marshal signatures. These include variant, void, syschar, decimal, date, objectref, nested struct and null, void, int64, uint64, unsigned int64, lpstr, lpwstr, safearray, hresult, carray, userdefined, record, filetime, blob, stream, storage streamed_object, stored_object, blob_object, cf, clsid, as well as pointers/references/vectors of these. The reason is that Cecil doesn’t expose any way to set these types. Either way, most of these are deprecated nowadays. All other native/variant types are supported.
Fourth, support for .data declarations is very limited (we actually only do a weak attempt at emulating them). Cecil has no way of emitting data constants, and they’re implementation-defined anyway. Currently, the only thing we do with them is copy them to the InitialValue property of field definitions where appropriate. This is far from correct, but it’s the best we can do. Either way, you should avoid using these declarations. They don’t make a whole lot of sense in managed land.
Fifth, we have no support for parsing System.Reflection-notation strings (yet). This means that things like custom marshalers aren’t supported (the syntax will simply be ignored). A type parsing API will supposedly be exposed in Cecil 1.0.
Sixth, support for declarative security syntax is limited. While we support the full syntax specified by the standard, we don’t support all of the syntax MS.NET’s ILAsm does (specifically, the syntax resembling verbal custom attribute initialization).
Seventh, exported types are unfinished because Cecil lacks a way to manipulate the File table directly, and because of the lack of a type parser.
Eighth, we can’t emit custom attributes on manifest resources and assembly references. This is a Cecil limitation which should be relatively easy to fix.
Lastly, the .vtable/.vtfixup/.vtentry directives are unsupported. Again, these will be implemented once Cecil has support for them.
There are some other incompatibilities, but they’re very subtle and you’re unlikely to ever encounter them. And even if you do, ILAsm will warn you.
Generally, the differences between the two implementations will only be encountered in obscure features that most users are highly unlikely to be using, or when attempting to use nonstandard syntax. Rule of thumb: Stay away from these things and you’re safe.