// Copyright 2007 Alp Toker // This software is made available under the MIT License // See COPYING for details using System; using System.Reflection; using System.Reflection.Emit; using System.Collections.Generic; namespace NDesk.DBus { static class TypeImplementer { static AssemblyBuilder asmB; static ModuleBuilder modB; static void InitHack () { if (asmB != null) return; asmB = AppDomain.CurrentDomain.DefineDynamicAssembly (new AssemblyName ("NDesk.DBus.Proxies"), AssemblyBuilderAccess.Run); modB = asmB.DefineDynamicModule ("ProxyModule"); } static Dictionary map = new Dictionary (); public static Type GetImplementation (Type declType) { Type retT; if (map.TryGetValue (declType, out retT)) return retT; InitHack (); TypeBuilder typeB = modB.DefineType (declType.Name + "Proxy", TypeAttributes.Class | TypeAttributes.Public, typeof (BusObject)); Implement (typeB, declType); foreach (Type iface in declType.GetInterfaces ()) Implement (typeB, iface); retT = typeB.CreateType (); map[declType] = retT; return retT; } public static void Implement (TypeBuilder typeB, Type iface) { typeB.AddInterfaceImplementation (iface); foreach (MethodInfo declMethod in iface.GetMethods ()) { ParameterInfo[] parms = declMethod.GetParameters (); Type[] parmTypes = new Type[parms.Length]; for (int i = 0 ; i < parms.Length ; i++) parmTypes[i] = parms[i].ParameterType; MethodAttributes attrs = declMethod.Attributes ^ MethodAttributes.Abstract; MethodBuilder method_builder = typeB.DefineMethod (declMethod.Name, attrs, declMethod.ReturnType, parmTypes); typeB.DefineMethodOverride (method_builder, declMethod); //define in/out/ref/name for each of the parameters for (int i = 0; i < parms.Length ; i++) method_builder.DefineParameter (i, parms[i].Attributes, parms[i].Name); ILGenerator ilg = method_builder.GetILGenerator (); GenHookupMethod (ilg, declMethod, sendMethodCallMethod, Mapper.GetInterfaceName (iface), declMethod.Name); } } static MethodInfo sendMethodCallMethod = typeof (BusObject).GetMethod ("SendMethodCall"); static MethodInfo sendSignalMethod = typeof (BusObject).GetMethod ("SendSignal"); static MethodInfo toggleSignalMethod = typeof (BusObject).GetMethod ("ToggleSignal"); static Dictionary hookup_methods = new Dictionary (); public static DynamicMethod GetHookupMethod (EventInfo ei) { DynamicMethod hookupMethod; if (hookup_methods.TryGetValue (ei, out hookupMethod)) return hookupMethod; if (ei.EventHandlerType.IsAssignableFrom (typeof (System.EventHandler))) Console.Error.WriteLine ("Warning: Cannot yet fully expose EventHandler and its subclasses: " + ei.EventHandlerType); MethodInfo declMethod = ei.EventHandlerType.GetMethod ("Invoke"); hookupMethod = GetHookupMethod (declMethod, sendSignalMethod, Mapper.GetInterfaceName (ei), ei.Name); hookup_methods[ei] = hookupMethod; return hookupMethod; } public static DynamicMethod GetHookupMethod (MethodInfo declMethod, MethodInfo invokeMethod, string @interface, string member) { ParameterInfo[] delegateParms = declMethod.GetParameters (); Type[] hookupParms = new Type[delegateParms.Length+1]; hookupParms[0] = typeof (BusObject); for (int i = 0; i < delegateParms.Length ; i++) hookupParms[i+1] = delegateParms[i].ParameterType; DynamicMethod hookupMethod = new DynamicMethod ("Handle" + member, declMethod.ReturnType, hookupParms, typeof (MessageWriter)); ILGenerator ilg = hookupMethod.GetILGenerator (); GenHookupMethod (ilg, declMethod, invokeMethod, @interface, member); return hookupMethod; } //static MethodInfo getMethodFromHandleMethod = typeof (MethodBase).GetMethod ("GetMethodFromHandle", new Type[] {typeof (RuntimeMethodHandle)}); static MethodInfo getTypeFromHandleMethod = typeof (Type).GetMethod ("GetTypeFromHandle", new Type[] {typeof (RuntimeTypeHandle)}); static ConstructorInfo argumentNullExceptionConstructor = typeof (ArgumentNullException).GetConstructor (new Type[] {typeof (string)}); static ConstructorInfo messageWriterConstructor = typeof (MessageWriter).GetConstructor (Type.EmptyTypes); static MethodInfo messageWriterWriteMethod = typeof (MessageWriter).GetMethod ("WriteComplex", new Type[] {typeof (object), typeof (Type)}); static MethodInfo messageWriterWritePad = typeof (MessageWriter).GetMethod ("WritePad", new Type[] {typeof (int)}); static Dictionary writeMethods = new Dictionary (); public static MethodInfo GetWriteMethod (Type t) { MethodInfo meth; if (writeMethods.TryGetValue (t, out meth)) return meth; /* Type tUnder = t; if (t.IsEnum) tUnder = Enum.GetUnderlyingType (t); meth = typeof (MessageWriter).GetMethod ("Write", BindingFlags.ExactBinding | BindingFlags.Instance | BindingFlags.Public, null, new Type[] {tUnder}, null); if (meth != null) { writeMethods[t] = meth; return meth; } */ DynamicMethod method_builder = new DynamicMethod ("Write" + t.Name, typeof (void), new Type[] {typeof (MessageWriter), t}, typeof (MessageWriter)); ILGenerator ilg = method_builder.GetILGenerator (); ilg.Emit (OpCodes.Ldarg_0); ilg.Emit (OpCodes.Ldarg_1); GenMarshalWrite (ilg, t); ilg.Emit (OpCodes.Ret); meth = method_builder; writeMethods[t] = meth; return meth; } //takes the Writer instance and the value of Type t off the stack, writes it public static void GenWriter (ILGenerator ilg, Type t) { Type tUnder = t; //bool imprecise = false; if (t.IsEnum) { tUnder = Enum.GetUnderlyingType (t); //imprecise = true; } //MethodInfo exactWriteMethod = typeof (MessageWriter).GetMethod ("Write", new Type[] {tUnder}); MethodInfo exactWriteMethod = typeof (MessageWriter).GetMethod ("Write", BindingFlags.ExactBinding | BindingFlags.Instance | BindingFlags.Public, null, new Type[] {tUnder}, null); //ExactBinding InvokeMethod if (exactWriteMethod != null) { //if (imprecise) // ilg.Emit (OpCodes.Castclass, tUnder); ilg.Emit (exactWriteMethod.IsFinal ? OpCodes.Call : OpCodes.Callvirt, exactWriteMethod); } else { //..boxed if necessary if (t.IsValueType) ilg.Emit (OpCodes.Box, t); //the Type parameter ilg.Emit (OpCodes.Ldtoken, t); ilg.Emit (OpCodes.Call, getTypeFromHandleMethod); ilg.Emit (messageWriterWriteMethod.IsFinal ? OpCodes.Call : OpCodes.Callvirt, messageWriterWriteMethod); } } //takes a writer and a reference to an object off the stack public static void GenMarshalWrite (ILGenerator ilg, Type type) { LocalBuilder val = ilg.DeclareLocal (type); ilg.Emit (OpCodes.Stloc, val); LocalBuilder writer = ilg.DeclareLocal (typeof (MessageWriter)); ilg.Emit (OpCodes.Stloc, writer); FieldInfo[] fis = type.GetFields (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); //align to 8 for structs ilg.Emit (OpCodes.Ldloc, writer); ilg.Emit (OpCodes.Ldc_I4, 8); ilg.Emit (messageWriterWritePad.IsFinal ? OpCodes.Call : OpCodes.Callvirt, messageWriterWritePad); foreach (FieldInfo fi in fis) { Type t = fi.FieldType; //the Writer to write to ilg.Emit (OpCodes.Ldloc, writer); //the object parameter ilg.Emit (OpCodes.Ldloc, val); ilg.Emit (OpCodes.Ldfld, fi); GenWriter (ilg, t); } } public static void GenHookupMethod (ILGenerator ilg, MethodInfo declMethod, MethodInfo invokeMethod, string @interface, string member) { ParameterInfo[] parms = declMethod.GetParameters (); Type retType = declMethod.ReturnType; //the BusObject instance ilg.Emit (OpCodes.Ldarg_0); //MethodInfo /* ilg.Emit (OpCodes.Ldtoken, declMethod); ilg.Emit (OpCodes.Call, getMethodFromHandleMethod); */ //interface ilg.Emit (OpCodes.Ldstr, @interface); //special case event add/remove methods if (declMethod.IsSpecialName && (declMethod.Name.StartsWith ("add_") || declMethod.Name.StartsWith ("remove_"))) { string[] parts = declMethod.Name.Split (new char[]{'_'}, 2); string ename = parts[1]; //Delegate dlg = (Delegate)inArgs[0]; bool adding = parts[0] == "add"; ilg.Emit (OpCodes.Ldstr, ename); ilg.Emit (OpCodes.Ldarg_1); ilg.Emit (OpCodes.Ldc_I4, adding ? 1 : 0); ilg.Emit (OpCodes.Tailcall); ilg.Emit (toggleSignalMethod.IsFinal ? OpCodes.Call : OpCodes.Callvirt, toggleSignalMethod); ilg.Emit (OpCodes.Ret); return; } //property accessor mapping if (declMethod.IsSpecialName) { if (member.StartsWith ("get_")) member = "Get" + member.Substring (4); else if (member.StartsWith ("set_")) member = "Set" + member.Substring (4); } //member ilg.Emit (OpCodes.Ldstr, member); //signature Signature inSig = Signature.Empty; Signature outSig = Signature.Empty; if (!declMethod.IsSpecialName) foreach (ParameterInfo parm in parms) { if (parm.IsOut) outSig += Signature.GetSig (parm.ParameterType.GetElementType ()); else inSig += Signature.GetSig (parm.ParameterType); } ilg.Emit (OpCodes.Ldstr, inSig.Value); LocalBuilder writer = ilg.DeclareLocal (typeof (MessageWriter)); ilg.Emit (OpCodes.Newobj, messageWriterConstructor); ilg.Emit (OpCodes.Stloc, writer); foreach (ParameterInfo parm in parms) { if (parm.IsOut) continue; Type t = parm.ParameterType; //offset by one to account for "this" int i = parm.Position + 1; //null checking of parameters (but not their recursive contents) if (!t.IsValueType) { Label notNull = ilg.DefineLabel (); //if the value is null... ilg.Emit (OpCodes.Ldarg, i); ilg.Emit (OpCodes.Brtrue_S, notNull); //...throw Exception string paramName = parm.Name; ilg.Emit (OpCodes.Ldstr, paramName); ilg.Emit (OpCodes.Newobj, argumentNullExceptionConstructor); ilg.Emit (OpCodes.Throw); //was not null, so all is well ilg.MarkLabel (notNull); } ilg.Emit (OpCodes.Ldloc, writer); //the parameter ilg.Emit (OpCodes.Ldarg, i); GenWriter (ilg, t); } ilg.Emit (OpCodes.Ldloc, writer); //the expected return Type ilg.Emit (OpCodes.Ldtoken, retType); ilg.Emit (OpCodes.Call, getTypeFromHandleMethod); LocalBuilder exc = ilg.DeclareLocal (typeof (Exception)); ilg.Emit (OpCodes.Ldloca_S, exc); //make the call ilg.Emit (invokeMethod.IsFinal ? OpCodes.Call : OpCodes.Callvirt, invokeMethod); //define a label we'll use to deal with a non-null Exception Label noErr = ilg.DefineLabel (); //if the out Exception is not null... ilg.Emit (OpCodes.Ldloc, exc); ilg.Emit (OpCodes.Brfalse_S, noErr); //...throw it. ilg.Emit (OpCodes.Ldloc, exc); ilg.Emit (OpCodes.Throw); //Exception was null, so all is well ilg.MarkLabel (noErr); if (retType == typeof (void)) { //we aren't expecting a return value, so throw away the (hopefully) null return if (invokeMethod.ReturnType != typeof (void)) ilg.Emit (OpCodes.Pop); } else { if (retType.IsValueType) ilg.Emit (OpCodes.Unbox_Any, retType); else ilg.Emit (OpCodes.Castclass, retType); } ilg.Emit (OpCodes.Ret); } } }