Dynamically Compiling C#Compiling C# from IronPython
![]()
IntroductionIronPython is a great way to use the .NET framework. It comes packed full of Python dynamic goodness. Unfortunately it isn't perfect. One noteworthy hole in the IronPython .NET integration is attributes. You can't use attributes in IronPython, which can sometime be a problem. The normal way round this problem is to create stub C# classes with methods that you can override in IronPython. This doesn't always work though; sometimes you want to dynamically specify the arguments to the attributes - which can only be done at compile time with C#. This article explores a way round the problem, with a solution that potentially has many other uses. It provides a way to dynamically compile C# source code into assemblies. These assemblies can be used in memory or saved to disk. Compiling C#This code uses the System.CodeDom.Compiler API, along with Microsoft.CSharp.CSharpCodeProvider: import clr from System.Environment import CurrentDirectory from System.IO import Path, Directory from System.CodeDom import Compiler from Microsoft.CSharp import CSharpCodeProvider def Generate(code, name, references=None, outputDirectory=None, inMemory=False): CompilerParams = Compiler.CompilerParameters() if outputDirectory is None: outputDirectory = Directory.GetCurrentDirectory() if not inMemory: CompilerParams.OutputAssembly = Path.Combine(outputDirectory, name + ".dll") CompilerParams.GenerateInMemory = False else: CompilerParams.GenerateInMemory = True CompilerParams.TreatWarningsAsErrors = False CompilerParams.GenerateExecutable = False CompilerParams.CompilerOptions = "/optimize" for reference in references or []: CompilerParams.ReferencedAssemblies.Add(reference) provider = CSharpCodeProvider() compile = provider.CompileAssemblyFromSource(CompilerParams, code) if compile.Errors.HasErrors: raise Exception("Compile error: %r" % list(compile.Errors.List)) if inMemory: return compile.CompiledAssembly return compile.PathToAssembly It exposes a single function called Generate. The arguments to the Generate function are as follows:
Using GenerateUsing Generate is very simple. I'll use as an example the Screenshot Code from my Windows Forms tutorial. This code uses the DllImport attribute to access unmanaged code from GDI32.dll and user32.dll. The following code shows the C# embedded in IronPython as a string. This is then compiled to an in memory assembly and imported into IronPython. The static methods are then called in exactly the same way as if the assemblies had been referenced from disk: import clr clr.AddReference('System.Drawing') from System.Drawing import Bitmap, Image from generate import Generate, LoadAssembly unmanaged_code = """ using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; namespace UnmanagedCode { public class GDI32 { [DllImport("GDI32.dll")] public static extern IntPtr CreateCompatibleDC(IntPtr hdc); [DllImport("GDI32.dll")] public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight); [DllImport("GDI32.dll")] public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); [DllImport("GDI32.dll")] public static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop); [DllImport("GDI32.dll")] public static extern bool DeleteDC(IntPtr hdc); [DllImport("GDI32.dll")] public static extern bool DeleteObject(IntPtr hObject); } public class User32 { [DllImport("user32.dll")] public static extern IntPtr GetDesktopWindow(); [DllImport("user32.dll")] public static extern IntPtr GetTopWindow(IntPtr hWnd); [DllImport("user32.dll")] public static extern IntPtr GetWindow(IntPtr hWnd, uint wCmd); [DllImport("User32.dll")] public static extern IntPtr GetWindowDC(IntPtr hWnd); [DllImport("User32.dll")] public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC); } } """ assembly = Generate(unmanaged_code, 'UnmanagedCode', inMemory=True) clr.AddReference(assembly) from UnmanagedCode import User32, GDI32 def ScreenCapture(x, y, width, height): hdcSrc = User32.GetWindowDC(User32.GetDesktopWindow()) hdcDest = GDI32.CreateCompatibleDC(hdcSrc) hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height) GDI32.SelectObject(hdcDest, hBitmap) # 0x00CC0020 is the magic number for a copy raster operation GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, 0x00CC0020) result = Bitmap(Image.FromHbitmap(hBitmap)) User32.ReleaseDC(User32.GetDesktopWindow(), hdcSrc) GDI32.DeleteDC(hdcDest) GDI32.DeleteObject(hBitmap) return result image = ScreenCapture(0, 0, 50, 400) for y in range(image.Height): row = [] for x in range(image.Width): color = image.GetPixel(x, y) value = color.R + color.G + color.B if value > 384: row.append(' ') else: row.append('X') print ''.join(row) Note that this code calls Generate to create the assembly, and then adds a reference to the assembly using clr.AddReference. The important lines are: assembly = Generate(unmanaged_code, 'UnamangedCode', inMemory=True) clr.AddReference(assembly) from UnmanagedCode import User32, GDI32 Having added a reference to the assembly, you can then import directly from the 'UnmanagedCode' namespace as usual. In practical use the C# source code could also be dynamically generated, making all sorts of things possible. If you run this script from the command line then, depending on what you have in the top left of your screen, you should see something a bit like: ![]() Subclassing in IronPythonThis is all very well, but it still requires writing classes and methods that need to be marked with attributes in C#. What if you really want to write your code in IronPython? Fortunately that is still easy - you can create a stub class that can be subclassed in IronPython. The following example C# creates a class with a method both marked with 'SomeAttribute'. 'method' takes two integers and returns a string. We make the method marked with an attribute call another method that is virtual, meaning that it can be overridden from IronPython.
using something;
namespace withattributes
{
[SomeAttribute]
class public Class
{
[SomeAttribute]
public string method(int arg1, int arg2)
{
return realmethod(arg1, arg2);
}
public virtual string realmethod(int arg1, int arg2)
{
return "";
}
}
}
When you compile this with Generate, you can then import Class from the withattributes namespace: from withattribtues import Class class SubClass(Class): def realmethod(self, arg1, arg2): return str(arg1) + str(arg2) SubClass still has the attribute on method, but your IronPython code is executed when it is called. Obviously you must observe the types of arguments and return values or .NET gets very unhappy. Now this isn't the most memory efficient way of doing things. Especially if you are generating a lot of small assemblies - we create an assembly and the type objects every time. Speed is not so much an issue though, this compilation executes much faster than you would expect. For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store. If you're looking for a new techie job, try the Voidspace Tech Job Board. This is part of the Hidden Network of technology and programming jobs.
Last edited Fri Feb 15 13:42:11 2008. Counter... |
|
|
Blogads
Follow me on: Tech Jobs |