├── 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 | }
--------------------------------------------------------------------------------