Click here to Skip to main content
Click here to Skip to main content

Inline MSIL in C#/VB.NET and Generic Pointers

, 22 Aug 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
Discusses inline-IL in C#, benefits and uses, and how to achieve it.

Introduction

The following article discusses how to achieve inline CIL (MSIL/IL) in the C# language as well as VB.NET. Using inline IL allows us to achieve things that the compiler usually prevents us from doing due to type-safety and other safety-checks. An example of such is VB.NET lacks native pointer support and you cannot declare generic-type pointers in C# or VB.NET.

The majority of this tutorial is in C#, but the tool itself supports VB.NET as well. Given you are a proficient enough programmer (which you are assumed to be if you're interested in using inline IL in your code) it is quite simple to understand how all of this applies to VB.NET.

You can learn how to use this tool to export methods to native code here.

How it works

  • Post-build-command line is triggered
  • ILDASM is ran which disassembles your compiled assembly
  • C#/VB.NET Project is analyzed for IL code
  • IL code is appropriately inserted into the file (disasm.il) produced by ILDASM
  • ILASM is ran which reassembles your decompiled assembly and produces a new *.pdb

Implementation

Let's take a look at a simple method in C#:

public static int Add(int n, int n2)
{
    return n + n2;
}

Pulling out our handy tool ILSpy, we can disassemble this method in a compiled assemble which yields the following: 

.method public hidebysig static 
    int32 Add (
        int32 n,
        int32 n2
    ) cil managed 
{
    .maxstack 8
    .locals init (
        [0] int32 CS$1$0000
    )
    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldarg.1
    IL_0003: add
    IL_0004: stloc.0
    IL_0005: br.s IL_0007
    IL_0007: ldloc.0
    IL_0008: ret
}

Using ILSpy it's very easy to learn how C# or VB.NET does certain things at the IL level. Now, let's code an equivalent method in C# itself using the inline IL tool:  

public static int Add(int n, int n2)

{
#if IL
    ldarg n
    ldarg n2
    add
    ret
#endif
    return 0; // place holder so method compiles
}

As you can imagine, two integers which are arguments are loaded onto the pseudo stack, and then the instruction add pops both arguments off the pseudo stack and pushes the resulting value back onto the stack. You can learn more about operation codes using MSDN's articles for System.Reflection.Emit.OpCodes.

Using inline IL to achieve generic pointers

I've always thought C# lacked generic pointers. I think it's a tad silly you can apply the constraint class to a generic and assign null to a variable of T it but, if you applied the constraint struct you couldn't make a pointer variable of T. The reasoning is simple as structures can contain reference field types which you cannot (normally) create a pointer to, but there should still be a way to do this.  

Using the inline assembler you can achieve this, here is an example of such:

public static void UnsafeCopy<T, T2>(T[] src, T2[] dst, int length)
{
#if IL
    ldarg dst
    ldc.i4 0
    ldelema !!T2

    ldarg src
    ldc.i4 0
    ldelema !!T

    sizeof !!T
    ldarg length
    mul

    cpblk
#endif
}

cpblk is an instruction which C# does not have access to, it is very similar to memcpy() as it takes two pointers (dst and src) and a length parameter. ldelema loads a pointer of the given array onto the stack you specify two parameters which are index and type of the pointer. This instruction is used whenever you have a fixed scope in C# which uses an array.

Example of scenario usage:

public static void Sample()
{
    int[] numbers = new int[] { 1, 2, 3, 4 };
    byte[] bytes = new byte[sizeof(int) * numbers.Length];
    UnsafeCopy<int, byte>(numbers, bytes, 4);
    // ...
}

Here's another example with its pseudo-code equivalent:

Using Inline IL:

public unsafe static T ReadValue<T>(void* arg) where T : struct

{
#if IL
    ldarg arg
    ldobj !!T
    ret
#endif
    return default(T); // allow for compiliation
}

How it would look normally:

public unsafe static T ReadValue<T>(void* arg) where T : struct
{
    return *(T*)arg;
}

Visual Studio Integration

Integrating this into Visual Studio is quite easily, simply place InlineILCompiler.exe in a reliable directory and then go into Project Settings -> Build Events and then for Post-build event command line and paste/edit the following script to suit your needs:

set target=$(TargetPath)
set sdk=$(FrameworkSDKDir)
set project=$(ProjectPath)
set framework=$(MSBuildBinPath)
"C:\InlineILCompiler.exe"

If you're compiling a class library (*.dll) you need to add that argument to ILASM which you can do so like this:

set target=$(TargetPath)
set sdk=$(FrameworkSDKDir)
set project=$(ProjectPath)
set ilasm_args=/DLL
set framework=$(MSBuildBinPath)
"C:\InlineILCompiler.exe"

Limitations, bugs and notes 

I am well aware of the limitations of the current code as well as the bugs. Here are some of the things that I have noted:

  • If you compile invalid IL-syntax the compiler freezes for a short while before reporting back either a some-what useful error (but not fully detailed like you're use to having in C#) or complete garbage.
  • The IL-compiler does not report back an accurate line number for where the IL syntax-error occurred, however, it does give you the IL-region it occurred in as well as the error (which should be sufficient)
  • value is not a valid symbol in properties set methods. You must use ldarg.0 (or ldarg.1 if it is an instance method) to load value onto the stack or store it into a local variable and manipulate that.
  • Accessing instance-fields (belonging to the this object) are a pain in the ass. It's usually easier to assign them to local variables, and then use those and reassign back to the instance-fields.  The reason they're a pain is you need to fully qualify them. Take a look with ILSpy for an example.
  • It should be noted that for instance methods (and instance properties) ldarg.0 will load the this object onto the stack. The rest of your parameters then follow on the stack.

About removing .maxstack

So, recently someone directed me at this article (also by Mike Stall). Apparently simply removing the .maxstack directive doesn't cause ILASM to recompute the value instead ILASM uses a default value of 8 which I found to be a bit strange. Any ways, due to the impacts inline IL may have on the max stack size I have added support for the directive .maxstack (well actually, I should say I've "fixed" support since the parser would screw up if you used this directive in your inline IL code). The tool no longer removes .maxstack directives and only changes them if your inline IL code specifies this directive.

I suggest giving this small thread a read incase you run into any problems (typically InvalidProgramException) pertaining to the max stack value.

Closing Remarks

There are very interesting IL instructions like initblk which is similar to memset() and other features that exist in IL which don't get exist in C# (such as faults and filters). Sadly, I don't see Microsoft implementing support for generic pointers anytime soon as there is no demanding reason to implement them. It's still nice to know it can be achieved and I had lots of fun putting this project together. Shout out to Mike Stall who wrote the original tool which inspired this project. I tried to overcome it's limitations by integrating it into Visual Studio making it more practical to use. I'm aware the source code is appalling and has little to no object-orientation to it, however, my initial goal at the time wasn't to write elegant manageable code but rather simply working code to never be modified again.  

History

Saturday, August 11, 2012 

  • Initial release.

Sunday, August 12, 2012

  • Added support for VB.NET
  • Added support for IL debugging 
  • Added new IL directive .exportnative to easily export unmanaged method 

Monday, August 13 2012

  • Improved source legibility (added comments, so it's not one big Main() method anymore)
  • Removed _DEBUG statement (which was only ever for myself to use)
  • Fixed problem with parser when using lambdas (did not insert IL in the correct location) 

Wednesday, August 15 2012

  • Added support for a bug that occurs due to .maxstack (read About removing .maxstack)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

roylawliet
Student
Canada Canada
I was born March 15th 1994 and I have been programming for about 5 years now and working with C# for about 3 years. I am experienced in a variety of programming languages such as C/C++, Delphi, VB.NET, F#, and Python. I absolutely love learning new things about computer science, so I'm always doing research and often reinventing the wheel to get a better grasp on concepts.

Comments and Discussions

 
QuestionDebug and Release configuration PinmemberFlorian Rosmann9-May-14 22:01 
QuestionBest [modified] PinmemberMember 1040774317-Nov-13 20:35 
QuestionConsidered porting to Mono.Cecil? PinmemberDavid Jeske23-Aug-13 9:53 
GeneralMy vote of 5 PinmemberShahan Ayyub17-Mar-13 7:53 
QuestionAfter IL inserting in Release mode code is not optimized by JIT! Pinmemberbobeobi14-Dec-12 6:34 
AnswerRe: After IL inserting in Release mode code is not optimized by JIT! Pinmemberroylawliet17-Dec-12 10:39 
QuestionYor are best! PinmemberMember 160649026-Sep-12 11:02 
GeneralMy vote of 5 PinmemberMember 160649026-Sep-12 11:01 
GeneralMy vote of 5 PinmentorMd. Marufuzzaman14-Sep-12 1:59 
Questiongreat PinmemberAlbarhami13-Sep-12 4:09 
GeneralMy vote of 4 PinmemberAlbarhami13-Sep-12 4:09 
SuggestionIL Support PinmemberMember 76757965-Sep-12 10:53 
GeneralRe: IL Support Pinmemberroylawliet6-Sep-12 4:09 
GeneralRe: IL Support PinmemberMember 767579611-Sep-12 6:34 
GeneralRe: IL Support Pinmemberroylawliet13-Sep-12 1:27 
GeneralMy vote of 5 PinmemberPhilip Liebscher31-Aug-12 10:43 
GeneralVery Well Done! [modified] PinmemberPaul Conrad22-Aug-12 16:54 
Questionvery nice PinmemberCIDev16-Aug-12 4:16 
AnswerRe: very nice Pinmember_Amy22-Aug-12 21:09 
GeneralMy vote of 5 PinmemberAbdul Quader Mamun15-Aug-12 17:53 
GeneralRe: My vote of 5 PinmemberAbdul Quader Mamun15-Aug-12 17:54 
GeneralRe: My vote of 5 PinmemberAbdul Quader Mamun15-Aug-12 17:54 
GeneralMy vote of 5 Pinmembervbfengshui15-Aug-12 10:14 
GeneralRe: My vote of 5 Pinmemberroylawliet15-Aug-12 10:41 
GeneralRe: My vote of 5 Pinmembervbfengshui15-Aug-12 10:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141015.1 | Last Updated 22 Aug 2012
Article Copyright 2012 by roylawliet
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid