├── ProxyEmitter.Test ├── Dummy │ ├── IDummyService.cs │ └── DummyProxyBase.cs ├── EmitterTest.cs ├── Properties │ └── AssemblyInfo.cs └── ProxyEmitter.Test.csproj ├── .gitignore ├── ProxyEmitter ├── ProxyNamespaceAttribute.cs ├── ProxyBase.cs ├── Properties │ └── AssemblyInfo.cs ├── ProxyEmitter.csproj ├── Emitter.cs └── ProxyEmitter.cs ├── ProxyEmitter.sln └── README.md /ProxyEmitter.Test/Dummy/IDummyService.cs: -------------------------------------------------------------------------------- 1 | namespace ProxyEmitter.Test.Dummy 2 | { 3 | /// 4 | /// All kinds of interfaces 5 | /// 6 | [ProxyNamespace("CatX")] 7 | public interface IDummyService 8 | { 9 | void Fn1(); 10 | int Fn2(); 11 | void Fn3(int a, int b); 12 | int Fn4(int a, int b); 13 | int Fn5(int a, int b, int c, int d); 14 | } 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #ignore thumbnails created by windows 3 | Thumbs.db 4 | #Ignore files build by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | [Bb]in 23 | [Dd]ebug*/ 24 | *.lib 25 | *.sbr 26 | obj/ 27 | [Rr]elease*/ 28 | _ReSharper*/ 29 | [Tt]est[Rr]esult* 30 | *~ 31 | -------------------------------------------------------------------------------- /ProxyEmitter/ProxyNamespaceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ProxyEmitter 4 | { 5 | /// 6 | /// Specifies the namespace of Proxy methods 7 | /// 8 | [AttributeUsage(AttributeTargets.Interface)] 9 | public class ProxyNamespaceAttribute : Attribute 10 | { 11 | public string Namespace { get; private set; } 12 | 13 | public ProxyNamespaceAttribute(string @namespace) 14 | { 15 | Namespace = @namespace; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /ProxyEmitter.Test/EmitterTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using ProxyEmitter.Test.Dummy; 4 | 5 | namespace ProxyEmitter.Test 6 | { 7 | [TestClass] 8 | public class EmitterTest 9 | { 10 | public TestContext TestContext { get; set; } 11 | [TestMethod] 12 | public void TestEmit() 13 | { 14 | var service = ProxyEmitter.CreateProxy(TestContext); 15 | service.Fn1(); 16 | var @base = service as DummyProxyBase; 17 | Assert.AreEqual("CatX", @base.CurrentNameSpace); 18 | service.Fn3(0, 0); 19 | Assert.AreEqual("CatX", @base.CurrentNameSpace); 20 | Assert.AreEqual(0, service.Fn2()); 21 | Assert.AreEqual("CatX", @base.CurrentNameSpace); 22 | Assert.AreEqual(3, service.Fn4(1, 2)); 23 | Assert.AreEqual("CatX", @base.CurrentNameSpace); 24 | Assert.AreEqual(10, service.Fn5(1, 2, 3, 4)); 25 | Assert.AreEqual("CatX", @base.CurrentNameSpace); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ProxyEmitter/ProxyBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace ProxyEmitter 7 | { 8 | /// 9 | /// Base class for all Proxy class 10 | /// 11 | public abstract class ProxyBase 12 | { 13 | /// 14 | /// Invoke method 15 | /// 16 | /// Namespace of the method to be invoked 17 | /// Method to be invoked 18 | /// Argument list for the invoked method 19 | /// 20 | protected abstract object Invoke(string @namespace, string methodName, object[] arguments); 21 | 22 | /// 23 | /// Convert return value of method to a specific type 24 | /// 25 | /// Type of the return value 26 | /// Return value of method 27 | /// 28 | protected abstract TRet ConvertReturnValue(object returnValue); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ProxyEmitter.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProxyEmitter", "ProxyEmitter\ProxyEmitter.csproj", "{6B47DEA6-9B53-4113-866C-6F53A92D71E2}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProxyEmitter.Test", "ProxyEmitter.Test\ProxyEmitter.Test.csproj", "{7EBFCB34-694C-4935-A666-F0159A77C1FB}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6B47DEA6-9B53-4113-866C-6F53A92D71E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6B47DEA6-9B53-4113-866C-6F53A92D71E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6B47DEA6-9B53-4113-866C-6F53A92D71E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6B47DEA6-9B53-4113-866C-6F53A92D71E2}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {7EBFCB34-694C-4935-A666-F0159A77C1FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {7EBFCB34-694C-4935-A666-F0159A77C1FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {7EBFCB34-694C-4935-A666-F0159A77C1FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {7EBFCB34-694C-4935-A666-F0159A77C1FB}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /ProxyEmitter/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ProxyEmitter")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ProxyEmitter")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("83ebd3f3-64d6-4587-81c8-321f0da3bba0")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ProxyEmitter.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ProxyEmitter.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ProxyEmitter.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("73654584-9490-4614-ba52-97793025bef0")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ProxyEmitter.Test/Dummy/DummyProxyBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Runtime.Remoting.Contexts; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace ProxyEmitter.Test.Dummy 7 | { 8 | /// 9 | /// Dummy ProxyBase implementations for reference 10 | /// Nothing is implemented. 11 | /// 12 | public class DummyProxyBase : ProxyBase 13 | { 14 | #region ProxyBase 15 | 16 | protected override object Invoke(string @namespace, string methodName, object[] arguments) 17 | { 18 | CurrentNameSpace = @namespace; 19 | if (arguments != null) 20 | TestContext.WriteLine("{0}({1})", methodName, string.Join(", ", arguments)); 21 | else 22 | TestContext.WriteLine("{0}()", methodName); 23 | switch (methodName) 24 | { 25 | case "Fn1": 26 | case "Fn3": 27 | return null; 28 | } 29 | if (arguments == null) 30 | return 0; 31 | int sum = arguments.Cast().Sum(); 32 | return sum; 33 | } 34 | 35 | public string CurrentNameSpace { get; private set; } 36 | 37 | protected override TRet ConvertReturnValue(object returnValue) 38 | { 39 | if (typeof(TRet) != typeof(int)) 40 | return default(TRet); 41 | return (TRet)returnValue; 42 | } 43 | #endregion 44 | 45 | #region For Test 46 | 47 | public TestContext TestContext { get; set; } 48 | 49 | public DummyProxyBase(TestContext testContext) 50 | { 51 | TestContext = testContext; 52 | } 53 | #endregion 54 | } 55 | } -------------------------------------------------------------------------------- /ProxyEmitter/ProxyEmitter.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6B47DEA6-9B53-4113-866C-6F53A92D71E2} 8 | Library 9 | Properties 10 | ProxyEmitter 11 | ProxyEmitter 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Emit C# Proxy Class at Runtime with ILGenerator 2 | 3 | ## Problem Description 4 | 5 | In C#, we often run into objects or services that provide dynmaic method invocation by a single method like: 6 | 7 | ```cs 8 | public abstract class ProxyBase 9 | { 10 | protected abstract object Invoke(object someMethodRelatedInfo, object[] arguments); 11 | } 12 | ``` 13 | 14 | It is good practice to create a proxy class that wraps all remote methods and call the ```Invoke``` method internally to provide strong typed interface. 15 | 16 | Say we have a remote service. The first thing to do is declare its methods with interface: 17 | 18 | ```cs 19 | public interface IFooService 20 | { 21 | void MethodWithNoReturn(); 22 | int MethodTakeParameterAndReturn(int a, int b); 23 | } 24 | ``` 25 | 26 | Then we implement invocation method: 27 | 28 | ```cs 29 | public class FooProxyBase : ProxyBase 30 | { 31 | protected override object Invoke(object someMethodRelatedInfo, object[] arguments) 32 | { 33 | // Pack to JSON and send via http 34 | // Or adapte and call other classes 35 | // Or whatever 36 | } 37 | } 38 | ``` 39 | 40 | Finally we create a proxy class for IFooService: 41 | 42 | ```cs 43 | public class FooService : FooProxyBase, IFooService 44 | { 45 | #region Implement IFooService 46 | public void MethodWithNoReturn() 47 | { 48 | Invoke("MethodWithNoReturn", new object[0]); 49 | } 50 | 51 | public int MethodTakeParameterAndReturn(int a, int b) 52 | { 53 | return Invoke("MethodTakeParameterAndReturn", new object[] { a, b }); 54 | } 55 | #endregion 56 | } 57 | ``` 58 | 59 | As you can see, the implementation of proxy class is quite trival but will take many work if methods are too many. 60 | 61 | The point of interest here is to automatically generate ```FooService``` class at runtime. This goal can be achieved by various methods. This project in particular uses C#'s ```ILGenerator``` to emit IL code at runtime. 62 | 63 | Instead manually writing ```FooService```, all you need is one line of code: 64 | ```cs 65 | 66 | IFooService proxy = ProxyEmitter.CreateProxy(/*Constructor parameters are supported*/); 67 | 68 | ``` 69 | 70 | ## How To Use 71 | 72 | 0. Include project in your solution, or add reference to compiled ```ProxyEmitter.dll```. 73 | 1. Make an base class that derives from ```ProxyBase``` and implement all abstract methods (just slightly differenct from above). You can create whatever constructors you want. ```ProxyEmitter``` will make sure the generated class have the same ones. 74 | 2. Declare an service interface for the service. You can provide additional namespace information by tagging it with ```ProxyNamespace``` attribute. 75 | 3. Call ```ProxyEmitter.CreateProxy 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {7EBFCB34-694C-4935-A666-F0159A77C1FB} 7 | Library 8 | Properties 9 | ProxyEmitter.Test 10 | ProxyEmitter.Test 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {6b47dea6-9b53-4113-866c-6f53a92d71e2} 61 | ProxyEmitter 62 | 63 | 64 | 65 | 66 | 67 | 68 | False 69 | 70 | 71 | False 72 | 73 | 74 | False 75 | 76 | 77 | False 78 | 79 | 80 | 81 | 82 | 83 | 84 | 91 | -------------------------------------------------------------------------------- /ProxyEmitter/Emitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Reflection.Emit; 4 | using System.Threading; 5 | 6 | namespace ProxyEmitter 7 | { 8 | /// 9 | /// Helper class 10 | /// 11 | public static class Emitter 12 | { 13 | /// 14 | /// Gets the of current thread 15 | /// 16 | public static AppDomain CurrentDomain 17 | { 18 | get { return Thread.GetDomain(); } 19 | } 20 | 21 | # region Global Methods 22 | 23 | /// 24 | /// Creates an instance within 25 | /// 26 | /// 27 | /// 28 | /// 29 | public static AssemblyBuilder GetAssemblyBuilder(string name, AssemblyBuilderAccess access = AssemblyBuilderAccess.RunAndSave) 30 | { 31 | var aname = new AssemblyName(name); 32 | AppDomain currentDomain = AppDomain.CurrentDomain; 33 | AssemblyBuilder builder = currentDomain.DefineDynamicAssembly(aname, access); 34 | return builder; 35 | } 36 | 37 | /// 38 | /// Creates a instance within 39 | /// 40 | /// 41 | /// 42 | /// 43 | public static ModuleBuilder GetModule(AssemblyBuilder asmBuilder, String moduleName) 44 | { 45 | ModuleBuilder builder = asmBuilder.DefineDynamicModule(moduleName, true);//"EmitMethods", "EmitMethods.dll"); 46 | return builder; 47 | } 48 | 49 | /// 50 | /// Creates an instance within 51 | /// 52 | /// 53 | /// 54 | /// 55 | public static EnumBuilder GetEnum(ModuleBuilder modBuilder, string enumName) 56 | { 57 | EnumBuilder builder = modBuilder.DefineEnum(enumName, TypeAttributes.Public, typeof(Int32)); 58 | return builder; 59 | } 60 | 61 | /// 62 | /// Create a instance within 63 | /// 64 | /// 65 | /// 66 | /// 67 | /// 68 | /// 69 | public static TypeBuilder GetType(ModuleBuilder modBuilder, string className, Type parent = null, Type[] interfaces = null) 70 | { 71 | if (parent == null) 72 | parent = typeof(Object); 73 | TypeBuilder builder = modBuilder.DefineType(className, TypeAttributes.Public, parent, interfaces); 74 | return builder; 75 | } 76 | 77 | public static TypeBuilder GetType(ModuleBuilder modBuilder, string className, params string[] genericparameters) 78 | { 79 | TypeBuilder builder = modBuilder.DefineType(className, TypeAttributes.Public); 80 | GenericTypeParameterBuilder[] genBuilders = builder.DefineGenericParameters(genericparameters); 81 | 82 | foreach (GenericTypeParameterBuilder genBuilder in genBuilders) // We take each generic type T : class, new() 83 | { 84 | genBuilder.SetGenericParameterAttributes(GenericParameterAttributes.ReferenceTypeConstraint | GenericParameterAttributes.DefaultConstructorConstraint); 85 | //genBuilder.SetInterfaceConstraints(interfaces); 86 | } 87 | 88 | return builder; 89 | } 90 | 91 | public static MethodBuilder GetMethod(TypeBuilder typeBuilder, string methodName, MethodAttributes attributes) 92 | { 93 | MethodBuilder builder = typeBuilder.DefineMethod( 94 | methodName, 95 | attributes); 96 | return builder; 97 | } 98 | 99 | public static MethodBuilder GetMethod(TypeBuilder typeBuilder, string methodName, MethodAttributes attributes, Type returnType, params Type[] parameterTypes) 100 | { 101 | MethodBuilder builder = typeBuilder.DefineMethod( 102 | methodName, 103 | attributes, 104 | CallingConventions.HasThis, 105 | returnType, 106 | parameterTypes); 107 | return builder; 108 | } 109 | 110 | public static ConstructorBuilder GetConstructor(TypeBuilder typeBuilder, MethodAttributes attributes, params Type[] parameterTypes) 111 | { 112 | return typeBuilder.DefineConstructor(attributes, CallingConventions.HasThis, parameterTypes); 113 | } 114 | 115 | public static MethodBuilder GetMethod(TypeBuilder typeBuilder, string methodName, MethodAttributes attributes, Type returnType, string[] genericParameters, params Type[] parameterTypes) 116 | { 117 | MethodBuilder builder = typeBuilder.DefineMethod( 118 | methodName, 119 | attributes, 120 | CallingConventions.HasThis, 121 | returnType, parameterTypes); 122 | 123 | GenericTypeParameterBuilder[] genBuilders = builder.DefineGenericParameters(genericParameters); 124 | 125 | foreach (GenericTypeParameterBuilder genBuilder in genBuilders) // We take each generic type T : class, new() 126 | { 127 | genBuilder.SetGenericParameterAttributes(GenericParameterAttributes.ReferenceTypeConstraint | GenericParameterAttributes.DefaultConstructorConstraint); 128 | //genBuilder.SetInterfaceConstraints(interfaces); 129 | } 130 | return builder; 131 | } 132 | # endregion 133 | } 134 | } -------------------------------------------------------------------------------- /ProxyEmitter/ProxyEmitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Reflection.Emit; 7 | 8 | namespace ProxyEmitter 9 | { 10 | public static class ProxyEmitter 11 | { 12 | #region Type Caches 13 | 14 | /// 15 | /// Internal cache of generated Proxy classes. First mapped by ProxyBase type, then interface type 16 | /// 17 | internal static Dictionary> ProxyTypes = new Dictionary>(); 18 | 19 | /// 20 | /// Get cached Proxy class type 21 | /// 22 | /// Type of derived class 23 | /// Type of interface 24 | /// Null if the queried type is not present in cache 25 | internal static Type GetCachedType(Type baseType, Type interfaceType) 26 | { 27 | Dictionary interfaceTypes; 28 | if (ProxyTypes.TryGetValue(baseType, out interfaceTypes)) 29 | { 30 | if (interfaceTypes != null && interfaceTypes.ContainsKey(interfaceType)) 31 | return interfaceTypes[interfaceType]; 32 | } 33 | return null; 34 | } 35 | 36 | /// 37 | /// Get cached Proxy class type 38 | /// 39 | /// Type of derived class 40 | /// Type of interface 41 | /// Null if the queried type is not present in cache 42 | internal static Type GetCachedProxyType() 43 | { 44 | return GetCachedType(typeof(TBase), typeof(TInterface)); 45 | } 46 | 47 | /// 48 | /// Cache generated Proxy class 49 | /// 50 | /// Type of derived class 51 | /// Type of interface 52 | /// Generated Proxy class type 53 | internal static void CacheProxyType(Type baseType, Type interfaceType, Type proxyType) 54 | { 55 | if (!ProxyTypes.ContainsKey(baseType)) 56 | ProxyTypes.Add(baseType, new Dictionary()); 57 | ProxyTypes[baseType].Add(interfaceType, proxyType); 58 | } 59 | #endregion 60 | 61 | #region Public Methods 62 | /// 63 | /// Create Proxy class type 64 | /// 65 | /// Type of derived class 66 | /// Type of interface 67 | /// 68 | /// Thrown if is not derived from , 69 | /// nor is not an interface. 70 | /// 71 | /// Type of Proxy class that drived from both and 72 | public static Type CreateType(Type baseType, Type interfaceType) 73 | { 74 | // Validate 75 | if (!interfaceType.IsInterface) 76 | throw new ArgumentException("Invalid interface type"); 77 | if (!baseType.IsSubclassOf(typeof(ProxyBase))) 78 | throw new ArgumentException("Base type is not derived from ProxyBase"); 79 | var type = GetCachedType(baseType, interfaceType); 80 | // Look up cache first 81 | if (type != null) 82 | return type; 83 | // Not cached, create 84 | type = EmitProxyType(baseType, interfaceType); 85 | // Cache 86 | CacheProxyType(baseType, interfaceType, type); 87 | return type; 88 | } 89 | 90 | /// 91 | /// Create Proxy class type 92 | /// 93 | /// Type of derived class 94 | /// Type of interface 95 | /// 96 | /// Thrown if is not an interface. 97 | /// 98 | /// Type of Proxy class that drived from both and 99 | public static Type CreateType() 100 | where TBase : ProxyBase 101 | where TInterface : class 102 | { 103 | return CreateType(typeof(TBase), typeof(TInterface)); 104 | } 105 | 106 | /// 107 | /// Create Proxy class instance 108 | /// 109 | /// Type of derived class 110 | /// Type of interface 111 | /// An array of arguments that match in number, order, 112 | /// and type the parameters of the constructor to invoke. 113 | /// If args is an empty array or null, the constructor that takes no parameters (the default constructor) 114 | /// is invoked. 115 | /// 116 | /// Thrown if is not derived from , 117 | /// nor is not an interface. 118 | /// 119 | /// Instance of Proxy class that drived from both and 120 | public static object CreateProxy(Type baseType, Type interfaceType, params object[] args) 121 | { 122 | var type = CreateType(baseType, interfaceType); 123 | return Activator.CreateInstance(type, args); 124 | } 125 | 126 | /// 127 | /// Create Proxy class instance 128 | /// 129 | /// Type of derived class 130 | /// Type of interface 131 | /// An array of arguments that match in number, order, 132 | /// and type the parameters of the constructor to invoke. 133 | /// If args is an empty array or null, the constructor that takes no parameters (the default constructor) 134 | /// is invoked. 135 | /// 136 | /// Thrown if is not an interface. 137 | /// 138 | /// Instance of Proxy class that drived from both and 139 | public static TInterface CreateProxy(params object[] args) 140 | where TBase : ProxyBase 141 | where TInterface : class 142 | { 143 | return (TInterface)CreateProxy(typeof(TBase), typeof(TInterface), args); 144 | } 145 | 146 | #endregion 147 | 148 | #region Emitting 149 | 150 | private static Type EmitProxyType(Type baseType, Type interfaceType) 151 | { 152 | AssemblyBuilder asmBuilder = Emitter.GetAssemblyBuilder("DynamicProxyAssembly"); 153 | ModuleBuilder mBuilder = Emitter.GetModule(asmBuilder, "EmittedProxies"); 154 | TypeBuilder tBuilder = Emitter.GetType(mBuilder, String.Format("{0}{1}Proxy", baseType.FullName, interfaceType), baseType, new[] { interfaceType }); 155 | 156 | // Emit parametrized ctor 157 | var constructors = baseType.GetConstructors(); 158 | foreach (var ctor in constructors) 159 | { 160 | Debug.WriteLine(ctor.Name); 161 | EmitCtor(tBuilder, ctor); 162 | } 163 | 164 | // Get namespace information 165 | var namespaceAttr = interfaceType.GetCustomAttribute(); 166 | var ns = namespaceAttr == null ? "" : namespaceAttr.Namespace; 167 | 168 | // Create methods in interfaceType 169 | var superType = typeof(ProxyBase); 170 | var invokeMethod = superType.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.NonPublic); 171 | var convertMethod = superType.GetMethod("ConvertReturnValue", BindingFlags.Instance | BindingFlags.NonPublic); 172 | var methods = interfaceType.GetMethods(); 173 | foreach (var method in methods) 174 | { 175 | Debug.WriteLine(method.Name); 176 | EmitInterfaceMethod(tBuilder, ns, method, invokeMethod, convertMethod); 177 | } 178 | var proxyType = tBuilder.CreateType(); 179 | return proxyType; 180 | } 181 | 182 | private static void EmitCtor(TypeBuilder tBuilder, ConstructorInfo ctor) 183 | { 184 | var pTypes = ctor.GetParameters().Select(p => p.ParameterType).ToArray(); 185 | var builder = Emitter.GetConstructor( 186 | tBuilder, 187 | MethodAttributes.Public | 188 | MethodAttributes.HideBySig | 189 | MethodAttributes.SpecialName | 190 | MethodAttributes.RTSpecialName, 191 | pTypes 192 | ); 193 | var ilGen = builder.GetILGenerator(); 194 | 195 | // No locals 196 | 197 | // Load all args, note arg 0 is this pointer, so must emit one more 198 | for (int i = 0; i <= pTypes.Length; i++) 199 | { 200 | DoEmit(ilGen, OpCodes.Ldarg_S, i); 201 | } 202 | // Call base ctor 203 | DoEmit(ilGen, OpCodes.Call, ctor); 204 | 205 | // Return 206 | DoEmit(ilGen, OpCodes.Ret); 207 | } 208 | 209 | private static void EmitInterfaceMethod(TypeBuilder tBuilder, string ns, MethodInfo method, MethodInfo invokeMethod, MethodInfo convertMethod) 210 | { 211 | #region Emit Signatue 212 | // .method public hidebysig virtual instance void 213 | // MethodName(xxx) cil managed 214 | // { 215 | var pTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); 216 | MethodBuilder builder = Emitter.GetMethod( 217 | tBuilder, 218 | method.Name, 219 | MethodAttributes.Public | 220 | MethodAttributes.HideBySig | 221 | MethodAttributes.Virtual, 222 | method.ReturnType, 223 | pTypes); 224 | #endregion 225 | 226 | var ilGen = builder.GetILGenerator(); 227 | 228 | 229 | EmitLocals(ilGen, method, pTypes); 230 | 231 | EmitHead(ilGen, method); 232 | 233 | EmitInvokeArguments(ilGen, ns, method, pTypes); 234 | 235 | EmitInvoke(ilGen, method, invokeMethod); 236 | 237 | EmitReturn(ilGen, method, convertMethod); 238 | } 239 | 240 | private static void EmitLocals(ILGenerator ilGen, MethodInfo method, Type[] pTypes) 241 | { 242 | // Has return value 243 | if (method.ReturnType != typeof(void)) 244 | { 245 | ilGen.DeclareLocal(method.ReturnType).SetLocalSymInfo("RET"); 246 | } 247 | // Has parameters 248 | if (pTypes.Length > 0) 249 | ilGen.DeclareLocal(typeof(object[])).SetLocalSymInfo("Args"); 250 | 251 | } 252 | 253 | private static void EmitHead(ILGenerator ilGen, MethodInfo method) 254 | { 255 | // IL_0000: nop 256 | DoEmit(ilGen, OpCodes.Nop); 257 | // IL_0001: ldarg.0 258 | DoEmit(ilGen, OpCodes.Ldarg_0); 259 | // Twice if has return value 260 | // IL_0002: ldarg.0 261 | if (method.ReturnType != typeof(void)) 262 | DoEmit(ilGen, OpCodes.Ldarg_0); 263 | } 264 | 265 | private static void EmitInvokeArguments(ILGenerator ilGen, string ns, MethodInfo method, Type[] pTypes) 266 | { 267 | // Zero-th one, namespace 268 | DoEmit(ilGen, OpCodes.Ldstr, ns); 269 | // First one, method name 270 | // IL_0002: ldstr "Fn2" 271 | DoEmit(ilGen, OpCodes.Ldstr, method.Name); 272 | 273 | // Has arguments? 274 | if (pTypes.Length == 0) 275 | { 276 | // No? Easy 277 | // IL_0007: ldnull 278 | DoEmit(ilGen, OpCodes.Ldnull); 279 | } 280 | else 281 | { 282 | // Yes? So much work 283 | // Initialize array 284 | // IL_0006: ldc.i4.x 285 | DoEmit(ilGen, OpCodes.Ldc_I4_S, pTypes.Length); 286 | // IL_0007: newarr [mscorlib]System.Object 287 | DoEmit(ilGen, OpCodes.Newarr, typeof(Object)); 288 | 289 | // If has return value, array is local 1. Otherwise 0. 290 | var hasReturn = method.ReturnType != typeof(void); 291 | var set_op = hasReturn ? OpCodes.Stloc_1 : OpCodes.Stloc_0; 292 | var get_op = hasReturn ? OpCodes.Ldloc_1 : OpCodes.Ldloc_0; 293 | // IL_000c: stloc.1 294 | DoEmit(ilGen, set_op); 295 | 296 | // More work coming. 297 | // Now fill the array 298 | for (int i = 0; i < pTypes.Length; i++) 299 | { 300 | // Load the array first 301 | // IL_000X + 00: ldloc.1 302 | DoEmit(ilGen, get_op); 303 | 304 | // Push the index 305 | // IL_000X + 01: ldc_i4_x 306 | DoEmit(ilGen, OpCodes.Ldc_I4_S, i); 307 | // Load argument i + 1 (note that argument 0 is this pointer(?)) 308 | // IL_000X + 02: ldarg_X 309 | DoEmit(ilGen, OpCodes.Ldarg_S, i + 1); 310 | // Box value type 311 | if (pTypes[i].IsValueType) 312 | { 313 | // IL_000X + 03: box pTypes[i] 314 | DoEmit(ilGen, OpCodes.Box, pTypes[i]); 315 | } 316 | // Set arrary element 317 | // IL_00X + ??: stelem.ref 318 | DoEmit(ilGen, OpCodes.Stelem_Ref); 319 | } 320 | 321 | // Load array 322 | // IL_00XX: ldloc.1 323 | DoEmit(ilGen, get_op); 324 | } 325 | } 326 | 327 | private static void EmitInvoke(ILGenerator ilGen, MethodInfo method, MethodInfo invokeMethod) 328 | { 329 | // IL_0022: opCode instance object ProxyEmitter.ProxyBase::Invoke(string, object[]) 330 | DoEmit(ilGen, OpCodes.Callvirt, invokeMethod); 331 | } 332 | 333 | private static void EmitReturn(ILGenerator ilGen, MethodInfo method, MethodInfo convertMethod) 334 | { 335 | var hasReturn = method.ReturnType != typeof(void); 336 | if (hasReturn) 337 | { 338 | // IL_000e: opCode instance !!0 ProxyEmitter.ProxyBase::ConvertReturnValue(object) 339 | DoEmitCall(ilGen, OpCodes.Callvirt, convertMethod, new[] { method.ReturnType }); 340 | // IL_0013: stloc.0 341 | DoEmit(ilGen, OpCodes.Stloc_0); 342 | // IL_0016: ldloc.0 343 | DoEmit(ilGen, OpCodes.Ldloc_0); 344 | } 345 | else 346 | { 347 | // IL_000d: pop 348 | DoEmit(ilGen, OpCodes.Pop); 349 | } 350 | // IL_000e: ret 351 | DoEmit(ilGen, OpCodes.Ret); 352 | } 353 | 354 | #endregion 355 | 356 | #region Debug 357 | 358 | private static void DoEmitCall(ILGenerator ilGen, OpCode opCode, MethodInfo convertMethod, Type[] types) 359 | { 360 | Debug.WriteLine("0x{0:X4}: {1} {2} {3}", ilGen.ILOffset, opCode, convertMethod, string.Join(", ", types)); 361 | ilGen.EmitCall(opCode, convertMethod.MakeGenericMethod(types), types); 362 | } 363 | 364 | private static void DoEmit(ILGenerator ilGen, OpCode opCode) 365 | { 366 | Debug.WriteLine("0x{0:X4}: {1}", ilGen.ILOffset, opCode); 367 | ilGen.Emit(opCode); 368 | } 369 | 370 | private static void DoEmit(ILGenerator ilGen, OpCode opCode, ConstructorInfo parm) 371 | { 372 | Debug.WriteLine("0x{0:X4}: {1} {2}", ilGen.ILOffset, opCode, parm); 373 | ilGen.Emit(opCode, parm); 374 | } 375 | 376 | private static void DoEmit(ILGenerator ilGen, OpCode opCode, string parm) 377 | { 378 | Debug.WriteLine("0x{0:X4}: {1} {2}", ilGen.ILOffset, opCode, parm); 379 | ilGen.Emit(opCode, parm); 380 | } 381 | 382 | private static void DoEmit(ILGenerator ilGen, OpCode opCode, Type parm) 383 | { 384 | Debug.WriteLine("0x{0:X4}: {1} {2}", ilGen.ILOffset, opCode, parm); 385 | ilGen.Emit(opCode, parm); 386 | } 387 | 388 | private static void DoEmit(ILGenerator ilGen, OpCode opCode, MethodInfo parm) 389 | { 390 | Debug.WriteLine("0x{0:X4}: {1} {2}", ilGen.ILOffset, opCode, parm); 391 | ilGen.Emit(opCode, parm); 392 | } 393 | 394 | private static void DoEmit(ILGenerator ilGen, OpCode opCode, int parm) 395 | { 396 | Debug.WriteLine("0x{0:X4}: {1} {2}", ilGen.ILOffset, opCode, parm); 397 | ilGen.Emit(opCode, parm); 398 | } 399 | 400 | #endregion 401 | } 402 | } --------------------------------------------------------------------------------