Introduction
Many developers have found the magic of emitting code at runtime using Reflection.Emit. Also, many have not used it simply because of the complexity of the API. RunSharp (or Run# if you prefer), aims to bring the simplicity and readability of a high-level language such as C# to runtime code generation.
The IL is a great language. However, there are several problems with emitting IL instructions directly. One of them is that it's quite hard to see the actual structure of the generated code, by just looking at series of Emit() calls. Also, there are strict rules as to what sequences of IL you can emit, unless you want to receive the awful 'Common Language Runtime detected an invalid program' exception. Sometimes, it's hard to find out exactly what IL is needed to achieve some simple C# concept. And there are more. I've created RunSharp to avoid these problems, and I've had a lot of fun writing it. And I hope you will have a lot of fun using it.
Using the Code
There is very little you need to know before you start emitting your next great assembly. Here are the simple steps to your first Hello World generated at runtime:
1. Create an Assembly
AssemblyGen ag = new AssemblyGen("HelloWorld.exe");
This call simply creates an AssemblyBuilder and ModuleBuilder under the hood.
2. Define Your First Class
TypeGen MyClass = ag.Public.Class("MyClass");
{
}
As you can see, this is very similar to what you would write in C#, e.g.:
public class MyClass
{
}
You can also use other modifiers for the class, such as Private, Sealed, Abstract or NoBeforeFieldInit (the last allows the static constructor to be invoked sooner or later than any class member is accessed - see an excellent article on beforefieldinit written by Jon Skeet for some details).
3. Define the Main() Method
CodeGen g = MyClass.Public.Static.Method(
typeof(void), "Main", typeof(string[]));
{
Operand args = g.Arg(0, "args");
}
The above corresponds to the following in C#:
public static void Main(string[] args)
{
}
Note the definition of the Operand variable here. Operand is one of the core classes in RunSharp, representing a literal, argument, local variable, expression etc. It also overloads all the operators, which can be used to construct expressions much like you would in C#. The Operand in the snippet above will represent the single argument of the main method. If you don't need to define the name of the argument (note that names of arguments are not defined in the Method() call), you can skip this altogether and simply use g.Arg(0) to access the argument.
4. Write the Body of the Method
g.If(args.ArrayLength() > 0);
{
g.WriteLine("Hello " + args[0] + "!");
}
g.Else();
{
g.WriteLine("Hello World!");
}
g.End();
And compare this to the C# version:
if (args.Length > 0)
{
Console.WriteLine("Hello " + args[0] + "!");
}
else
{
Console.WriteLine("Hello World!");
}
As you can see, the code is quite similar. There are some things to note - the g.WriteLine(...) calls are a special case, and they are simply a shortcut to the longer
g.Invoke(typeof(Console), "WriteLine", ...)
you can use to call any method on any type/instance. Also, you can see the length of the arguments is retrieved by calling ArrayLength() on the args operand. This is similar to calling args.Property("Length"), although it yields a bit better performance, as it's translated to the ldlen opcode instead of accessing the property of the array object.
5. Using the Generated Code
Now that we have created a simple hello world application, you can, for example, choose to save it and run it:
ag.Save();
AppDomain.CurrentDomain.ExecuteAssembly(
"HelloWorld.exe", null, new string[] { "John" });
Or, you can just decide to complete the assembly by calling ag.Complete() and use the generated types in any way you like (the TypeGen class returned when defining the type is implicitly convertible to Type).
Other Mini-Samples
In the hello world sample, only the simplest concepts were shown. You can do, of course, much more with RunSharp. Here are some small examples of various concepts and how they are related to C#.
1. Local Variables
C#
int x = 3;
long y = x + 15;
string s;
RunSharp
Operand x = g.Local(3);
Operand y = g.Local(typeof(long), x + 15);
Operand z = g.Local(typeof(string));
Note that if the type of the variable is not specified, it automatically takes the type of the initialized expression. Also, an untyped and uninitialized variable can be created by calling g.Local() without any parameters. Such a variable will receive the type of the first expression assigned to it.
Note that it's possible to write code such as:
Operand x = g.Local(3);
Operand y = x + 40;
However, the y will not be a local in this case, but rather something like an 'alias' for the expression x + 40, meaning the expression will be evaluated every time y is used.
2. Statements
Statements are always generated using the members of the CodeGen class. Most C# keyword-based statements have their equivalent methods in RunSharp (i.e. Break() for break, Throw() for throw, Return() for return, etc.). Other than these, only assignment, method-invocation and increment/decrement statements are supported.
2.1 Assignment, Increment/Decrement
C#
a = 3;
b = a + 5;
c += 4;
b++;
RunSharp
g.Assign(a, 3);
g.Assign(b, a + 5);
g.AssignAdd(c, 4);
g.Increment(b);
Note that the assignment operator cannot be overridden in C#. That's why we need a dedicated method to perform the assignment. Also, there is a method for each compound assignment operator.
2.2. Method Invocation
C#
o.SomeMethod(arg1, arg2);
Console.WriteLine(arg1, arg2);
MyType.MyMethod(arg1, arg2);
d(arg1, arg2);
RunSharp
g.Invoke(o, "SomeMethod", arg1, arg2);
g.Invoke(typeof(Console), "WriteLine", arg1, arg2);
g.Invoke(MyType, "MyMethod", arg1, arg2);
g.InvokeDelegate(d, arg1, arg2);
Note that when invoking a method on a generated type, the typeof() is omitted - this is because the TypeGen converts implicitly to Type.
3. Expressions
As already mentioned, the Operand class allows you to construct expressions much like you would in C#. There are only a few cases when the intuitive syntax is not available.
3.1 Pre/Post-Fix Operators
C#
x = a++;
y = --b;
RunSharp
g.Assign(x, a.PostIncrement());
g.Assign(y, b.PreDecrement());
3.2 Conditional Operator (?:)
C#
x = a > 0 ? b : c;
RunSharp
g.Assign(x, (a > 0).Conditional(b, c));
You should never use the ?: operator directly with an Operand instance as the first operand - this will corrupt the internal state of the Operand, as the operators true and false are overloaded in a way that allows the && and || operators to be used intuitively. Instead of the ?:, you can use the op.Conditional(ifTrue, ifFalse) syntax. You will also corrupt the state if you use an Operand as a condition in if, while, for, etc., but this is generally less likely, as you generate code using g.If(), etc. instead.
3.3. Member Access
C#
x = o.MyMethod(arg1, arg2);
y = o.MyProperty;
o.MyProperty = z;
f = o.myField;
x = MyType.MyMethod(arg1, arg2);
y = Console.BackgroundColor;
RunSharp
g.Assign(x, o.Invoke("MyMethod", arg1, arg2));
g.Assign(y, o.Property("MyProperty"));
g.Assign(o.Property("MyProperty"), z);
g.Assign(f, o.Field("myField"));
g.Assign(x, Static.Invoke(MyType, "MyMethod", arg1, arg2);
g.Assign(y, Static.Property(typeof(Console), "BackgroundColor");
3.4. Object Creation
C#
MyType x = new MyType(arg1, arg2);
string x = new string[10, 20];
int[] x = { 1, 2, 3, 4 };
MyDelegate x = new MyDelegate(this.MyMethod);
RunSharp
Operand x = g.Local(Exp.New(MyType, arg1, arg2));
Operand x = g.Local(Exp.NewArray(typeof(string), 10, 20));
Operand x = g.Local(Exp.NewInitializedArray(typeof(int), 1, 2, 3, 4));
Operand x = g.Local(Exp.NewDelegate(MyDelegate, g.This(), "MyMethod"));
Contact Me
For any questions, ideas, suggestions, etc., don't hesitate to contact me, for example through the RunSharp project here.
Happy Runtime Code Generation!
History
- 2007-10-18: Original article
- 2009-08-07: Updated source code download
| You must Sign In to use this message board. |
|
|
 |
|
 |
Hi,
I am wondering if the Emit capability in reflection, or your library has the ability to output C# or VB source code instead of the compiled result.
Cheers
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi,
neither has - IL emitting is essentially what a .NET compiler backend does (RunSharp being kind of a runtime compiler), so it's quite a bit lower level than source code. Also, I see little value in adding such a feature to RunSharp (even if it would be technically possible). Have a look at the .NET CodeDOM (Document Object Model) in the MSDN for possibilities of emitting .NET source code at runtime (in various languages).
Regards, Stefan
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi, first of all - great work on this!
I had a problem trying to call GetHashCode() on an int, here's an example:
public delegate int Int32Delegate();
static void Main(string[] args) { DynamicMethodGen dmg = DynamicMethodGen.Static(typeof(Program)).Method(typeof(int)); CodeGen g = dmg.Code; { Operand value = g.Local(typeof(int), 0); g.Return(value.Invoke("GetHashCode")); }
DynamicMethod dm = dmg.GetCompletedDynamicMethod(true);
Int32Delegate myfunc = (Int32Delegate)dm.CreateDelegate(typeof(Int32Delegate)); Console.WriteLine(myfunc()); }
Unfortunately, when the delegate gets called the generated code throws and exception. If I output the same function to a dll and run it through peverify.exe I get the following:
[IL]: Error: [hello.dll : MyClass::DoIt][offset 0x00000004][found address of Int32][expected ref 'System.Object'] Unexpected type on the stack. [IL]: Error: [hello.dll : MyClass::DoIt][offset 0x00000004] Call to base type of valuetype.
Ok, the IL looks unhappy for some reason. Here's the line in question:
IL_0004: callvirt instance int32 [mscorlib]System.Object::GetHashCode()
After some digging around I find the following IL is ok:
IL_0004: call instance int32 [mscorlib]System.Int32::GetHashCode()
Ok, looks like the IL being generated via Invoke() doesn't seem to like calling virtual functions (like GetHashCode()) that are defined on value types. callvirt is expecting to see a System.Object but barfs as it's getting a System.Int32 instead.
If you need a workaround, you can cast the value type to an object (at least in this instance.) I've come up with a patch that generates IL that passes peverify, but I've no idea if it breaks anything else. Here it is:
--- dist/RunSharp/Operand.cs Mon Nov 05 22:00:36 2007 +++ dist/RunSharp/Operand.cs Fri Jan 04 15:12:50 2008 @@ -70,7 +70,7 @@ internal virtual bool TrivialAccess { get { return false; } } internal virtual bool IsStaticTarget { get { return false; } } - internal virtual bool SuppressVirtual { get { return false; } } + internal virtual bool SuppressVirtual { get { return Type != null && Type.IsValueType; } } internal virtual object ConstantValue { get { return null; } } internal virtual void AssignmentHint(Operand op) { } #endregion --- dist/RunSharp/TypeInfo.cs Mon Nov 05 22:00:36 2007 +++ dist/RunSharp/TypeInfo.cs Fri Jan 04 15:15:37 2008 @@ -345,7 +345,7 @@ { for (; t != null; t = t.BaseType) { - ApplicableFunction af = OverloadResolver.Resolve(Filter(GetMethods(t), name, false, @static, false), args); + ApplicableFunction af = OverloadResolver.Resolve(Filter(GetMethods(t), name, false, @static, !@static), args); if (af != null) return af;
This can be applied the the 0.1.1 pre-alpha version that sits on SourceForge at the moment.
cheers, ninj
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi ninj,
thank you very much for your bug report and detailed analysis.
I've already reproduced the problem, and am working on a fix - updates will be posted on the SF.net tracker.
Regards, Stefan
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
 |
|
 |
Very impressive 2 different questions... Is there (simple) support for adding attribute instances, i.e. to properties? It would be handy to (in SimpleProperty, for example) be able to decorate the property with attributes. Did I miss this?
Also - for dynamic Type generation, it would be handy if TypeGen.Complete() returned the new type; I have tested this with a tweaked copy and it works great:
(reasons why)[^]
Marc
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Hi Marc,
1 - Unfortunately, there is absolutely no support for attributes in this version. However, attributes are one of the features I plan to add soon. 2 - The TypeGen to Type conversion is something for what I haven't decided yet, how the interface will exactly look like - I understand that the current implementation is not very nice. However, I'd like to keep it clean, so I'm not sure if having a Complete() method, that can be called again and again and will in fact do what it's name says (Complete) the type only the first time, and it's real function would be something like 'GetConstructedTypeAndCompleteIfNeeded' Currently, TypeGen is also implicitly convertible to Type, however this conversion doesn't complete the type - it returns the underlying TypeBuilder if the type hasn't been completed explicitly. Also, having an implicit conversion as the only means of retrieving the constructed type is not something I'd like to see in the 'final' version. Most likely, I'll add a new method, something like 'GetConstructedType' or 'GetSystemType' or something, maybe with an optional 'completeIfNeeded' argument.
Thanks for your feedback, Stefan
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
re attributes - fair enough  I hacked it for a quick test by exposing the builder directly - but it isn't very tidy
For TypeGen ->Type - minor aside, but you might want to avoid the term "constructed", since this has a defined (and different) meaning in terms of generics.
A clever piece of work, though.
Cheers,
Marc
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I am very interested in this kind of coding patterns that make life easier when using Reflection.Emit.... but, I am always, and still, frustrated to see no initiative to create a Reflection.Emit library or alike that could be used with Compact Framework!...
It would be VERY interested to have a version of your work that could interface with the PERWAPI ou Cecil libraries, that are the only initiative that can be used with the Compact Framework.
Regis
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
This is very, very slick. Great work!
However in order to be of any use to me I'll have to re-implement it. GPL'd code is of no use to me.
Thanks for the pattern, though!
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
|
 |
|
 |
Hi Dah,
You shouldn't have problems using instances of generic types, such as List inside the generated code. However, emitting generic code is not supported yet, and is not planned in the close future (i.e. until the current feature set is stabilized). In my optinion, generics are a bit less useful in runtime generated code that it is in normal compiled code, as runtime code generation is often used to solve similar problems that can be solved by using generics.
For the sake completeness, generic code generation will likely be added in a future release (maybe as a conditional compilation option), but at this point I am not able to tell exactly when.
Thanks for understanding, Stefan
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Stefan,
Thanks for reply, I just want to generate types derived from a generic base type which I have defined outside of runtime, I think it is useful to support emit generic code, since it provides more flexibility for manipulate the object.
I know the project has its complexity, adding such an big feature may be tough, anyway, RunSharp gives us an innovated idea to do code emitting.;P
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I was just telling someone the other day that .net is really easy to target as a compiler.. but this would have made winning the argument that much easier. I just about fell off my chair thinking, what a great idea! The concept itself is simple enough, but you've done a bunch of the tedious boring stuff for us... nice work! Anyone who's ever written a .net compiler will be able to tell you they've either done something like this, or wish they had something like this
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
xacc.ideThe rule of three: "The first time you notice something that might repeat, don't generalize it. The second time the situation occurs, develop in a similar fashion -- possibly even copy/paste -- but don't generalize yet. On the third time, look to generalize the approach." |
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Hi James,
I'm not familiar with the dotNetZen library, however, I've looked at it, and if I understood it correctly, it is providing helpers for generating standard patterns using CodeDOM. RunSharp doesn't use CodeDOM at all, it emits the code directly using Reflection.Emit (much faster, smaller footprint - CodeDOM invokes the full C#/VB/whatever compiler in the background). However, it shouldn't be a problem to write something similar like dotNetZen for RunSharp (i.e. allow patterns to be emitted without writting the exact logic). Btw., there are already hints of this inside the library - for example, the TypeGen.SimpleProperty() automatically creates a property with trivial getter/setter.
I'll definitely look at the possibility of adding something like this in future vesrions.
Regards, Stefan
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Is it possible to use RunSharp with DynamicMethod (Emit code and and method provided for an already existing type)?
If yes, could you provide a short sample to show usage?
Thanks and gratulation for your excellent work.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Leeloo,
technically, there is no problem in generating code for a DynamicMethod, and I must confess that this is something that I forgot to write before releasing the code . If you look at the CodeGen class, there is a commented CreateDynamicMethod (containing absolutely devious code), which is a leftover from early development. However, it has to be rewritten to provide an ICodeGenContext describing the DynamicMethod (which should be pretty easy).
I promise I'll rectify this in the next release.
Regards, Stefan
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Great work. I tried to download the source from sourceforge but I get: cvs [login aborted]: authorization failed: server runsharp.cvs.sourceforge.net rejected access to /cvsroot/runsharp for user anonymous Bye.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Hi Ricci,
the version on SF.net is currently exactly the same as on CodeProject, in the form of a file release. There is nothing in the CVS at the moment.
The reason I've included the SF link in this CodeProject article is, that I plan to release minor updates only to SF.net (i.e. 0.1.x will be likely SF.net-only updates, and with 0.2 I'll update the article as well)
Thanks, Stefan
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|