一、准备工作
具体说来,编程实现COM-->Assembly的功能,需要使用的以前几个类:
SystemRuntimeInteropServices
-TypeLibConverter提供一组服务,将托管程序集转换为 COM 类型库或进行反向转换。
-ITypeLibImporterNotifySink提供回调机制,以供类型库转换器向调用方通知转换的状态,并在转换过程本身之中涉及调用方。
SystemReflection
-StrongNameKeyPair(可选)封装对公钥或私钥对的访问,该公钥或私钥对用于为强名称程序集创建签名。
SystemReflectionEmit
-AssemblyBuilder 定义并表示动态程序集。
此外,还需要使用一个WinAPI,LoadTypeLibEx,具体定义如下:
[DllImport( "oleaut32dll", CharSet = CharSetUnicode, PreserveSig = false )]
private static extern void LoadTypeLibEx(String strTypeLibName, RegKind regKind, [MarshalAs(UnmanagedTypeInterface)] out Object typeLib );
为了让这个WinAPI function可以正常使用,我们还需要定义一个枚举,
private enum RegKind
{
RegKind_Default = 0,
RegKind_Register = 1,
RegKind_None = 2
}
注:上述类的说明来自MSDN。
大家都看到了,上述几个类中,仅有StrongNameKeyPair是可选的,这是因为如果我们不需要生成PIA,那么是不需要使用这个类的。同时,如果需要生成PIA,那么需要提供相应的密钥文件。在后面的描述中,我们将使用《走近COM Interop--浅谈PIA》中的例子做进一步的演示。
二、实战演练
在此,我们仍就由VB生成的PIADemodll展开演示。
1 载入一个COM组件
Object typeLib;
LoadTypeLibEx("PIADemodll", RegKindRegKind_None, out typeLib);
if(typeLib == null )
{
throw new Exception("载入失败!");
}
2 定义一个实现ITypeLibImporterNotifySink接口的类,基于提供回调机制,以供类型库转换器向调用方通知转换的状态,并在转换过程本身之中涉及调用方。
public class ConversionEventHandler: ITypeLibImporterNotifySink
{
public void ReportEvent(ImporterEventKind eventKind, int eventCode, string eventMsg )
{
// Do nothing
}
public Assembly ResolveRef(object typeLib)
{
// 此处直接返回null,避免把演示复杂化了
return null;
}
}
3 将COM类型库生成程序集
A 生成PIA Assembly
FileStream stream = new FileStream("commonsnk", FileModeOpen);
try
{
StrongNameKeyPair pair = new StrongNameKeyPair(stream);
TypeLibConverter converter = new TypeLibConverter();
ConversionEventHandler eventHandler = new ConversionEventHandler();
AssemblyBuilder ab = converterConvertTypeLibToAssembly(typeLib, "interopPIADemodll", TypeLibImporterFlagsPrimaryInteropAssembly, eventHandler, null, pair, null, null);
abSave("interopPIADemodll");
MessageBoxShow("Importing is ok");
Assembly asm = AssemblyLoadFile(ApplicationStartupPath + @"\interopPIADemodll");
Type t = asmGetType("interopPIADemoTestClass");
object obj = tInvokeMember(null, BindingFlagsDeclaredOnly | BindingFlagsPublic | BindingFlagsNonPublic | BindingFlagsInstance | BindingFlagsCreateInstance, null, null, null);
string ret = (string)tInvokeMember("Format", BindingFlagsDeclaredOnly | BindingFlagsPublic | BindingFlagsNonPublic |
BindingFlagsInstance | BindingFlagsInvokeMethod, null, obj, new object[]{"Go!"});
MessageBoxShow(ret);
}
catch(Exception ep)
{
if(stream != null)
{
streamClose();
}
MessageBoxShow(epMessage);
}
B 生成一般的Assembly
TypeLibConverter converter = new TypeLibConverter();
ConversionEventHandler eventHandler = new ConversionEventHandler();
AssemblyBuilder ab = converterConvertTypeLibToAssembly(typeLib, "interopPIADemodll", 0,
eventHandler, null, null, null, null);
abSave("interopPIADemodll");
MessageBoxShow("Importing is ok");
Assembly asm = AssemblyLoadFile(ApplicationStartupPath + @"\interopPIADemodll");
Type t = asmGetType("interopPIADemoTestClass");
object obj = tInvokeMember(null, BindingFlagsDeclaredOnly | BindingFlagsPublic | BindingFlagsNonPublic | BindingFlagsInstance | BindingFlagsCreateInstance, null, null, null);
string ret = (string)tInvokeMember("Format", BindingFlagsDeclaredOnly | BindingFlagsPublic | BindingFlagsNonPublic |
BindingFlagsInstance | BindingFlagsInvokeMethod, null, obj, new object[]{"Go!"});
MessageBoxShow(ret);
需要说明几点:
1 上述示例中使用的PIADemodll和Commonsnk都需要被copy至测试程序的bin目录中,否则,就需要指定可达到的文件路径。
2 AssemblyLoadFile的参数是要加载的文件的绝对路径,相对路径将会引发异常。
第二个过程演示如何发出方法体,以及如何使用泛型方法的类型参数创建泛型类型的实例以及调用其方法。 第三个过程演示如何调用泛型方法。 重要事项 不能仅因为某个方法属于泛型类型且使用泛型类型的类型参数,就称该方法是泛型方法。 只有当方法具有自己的类型参数列表时,才能称其为泛型方法。 泛型方法可以出现在非泛型类型上,在本例中就是如此。 有关泛型类型上的非泛型方法的示例,请参见 如何:用反射发出定义泛型类型。 定义泛型方法 首先,如果使用高级语言编写,则查看泛型方法的显示方式会十分有用。 下面的代码包含在此主题的代码示例中,用于调用泛型方法的代码同样也包含在其中。 该方法具有两个类型参数 TInput 和TOutput,后者必须为引用类型 (class),必须具有无参数构造函数 (new),并且必须实现 ICollection(Of TInput)(在 C# 中为 ICollection<TInput>)。 此接口约束确保 ICollectionTAdd 方法可用于将元素添加到该方法创建的 TOutput 集合中。 该方法具有一个形参 input,这是一个 TInput 的数组。 该方法创建类型 TOutput 的集合,并将 input 的元素复制到该集合中。 Public Shared Function Factory(Of TInput, _ TOutput As {ICollection(Of TInput), Class, New}) _ (ByVal input() As TInput) As TOutput Dim retval As New TOutput() Dim ic As ICollection(Of TInput) = retval For Each t As TInput In input icAdd(t) Next Return retval End Function public static TOutput Factory<TInput, TOutput>(TInput[] tarray) where TOutput : class, ICollection<TInput>, new() { TOutput ret = new TOutput(); ICollection<TInput> ic = ret; foreach (TInput t in tarray) { icAdd(t); } return ret; } 定义一个动态程序集和一个动态模块,以包含该泛型方法所属的类型。 在本例中,该程序集仅拥有一个模块(名为 DemoMethodBuilder1),模块名称即为程序集名称加上一个扩展名。 在此示例中,将程序集保存到磁盘中并执行,因此指定了 AssemblyBuilderAccessRunAndSave。 您可以使用 Ildasmexe(MSIL 反汇编程序) 检查DemoMethodBuilder1dll,并将其与步骤 1 中显示的方法的 Microsoft 中间语言 (MSIL) 进行比较。 Dim asmName As New AssemblyName("DemoMethodBuilder1") Dim domain As AppDomain = AppDomainCurrentDomain Dim demoAssembly As AssemblyBuilder = _ domainDefineDynamicAssembly(asmName, _ AssemblyBuilderAccessRunAndSave) ' Define the module that contains the code For an ' assembly with one module, the module name is the ' assembly name plus a file extension Dim demoModule As ModuleBuilder = _ demoAssemblyDefineDynamicModule( _ asmNameName, _ asmNameName & "dll") AssemblyName asmName = new AssemblyName("DemoMethodBuilder1"); AppDomain domain = AppDomainCurrentDomain; AssemblyBuilder demoAssembly = domainDefineDynamicAssembly(asmName, AssemblyBuilderAccessRunAndSave); // Define the module that contains the code For an // assembly with one module, the module name is the // assembly name plus a file extension ModuleBuilder demoModule = demoAssemblyDefineDynamicModule(asmNameName, asmNameName+"dll"); 定义该泛型方法所属的类型。 该类型不一定为泛型类型。 泛型方法可以属于泛型类型,也可以属于非泛型类型。 在此示例中,该类型为一个类,它不是泛型类型,名称为 DemoType。 Dim demoType As TypeBuilder = demoModuleDefineType( _ "DemoType", _ TypeAttributesPublic) TypeBuilder demoType = demoModuleDefineType("DemoType", TypeAttributesPublic); 定义泛型方法。 如果泛型方法的形参的类型由该泛型方法的泛型类型参数指定,请使用 DefineMethod(String, MethodAttributes) 方法重载定义该方法。 该方法的泛型类型参数尚未定义,因此不能在对 DefineMethod 的调用中指定该方法的形参的类型。 在此示例中,该方法名为 Factory。 该方法是公共方法,并且是 static(在 Visual Basic 中为 Shared)的。 Dim factory As MethodBuilder = _ demoTypeDefineMethod("Factory", _ MethodAttributesPublic Or MethodAttributesStatic) MethodBuilder factory = demoTypeDefineMethod("Factory", MethodAttributesPublic | MethodAttributesStatic); 通过将包含参数名称的字符串数组传递给 MethodBuilderDefineGenericParameters 方法来定义 DemoMethod 的泛型类型参数。 这使得该方法成为泛型方法。 下面的代码使得 Factory 成为具有类型参数 TInput 和TOutput 的泛型方法。 若要使代码更易于阅读,可创建具有这些名称的变量,以保存表示这两个类型参数的 GenericTypeParameterBuilder 对象。 Dim typeParameterNames() As String = {"TInput", "TOutput"} Dim typeParameters() As GenericTypeParameterBuilder = _ factoryDefineGenericParameters(typeParameterNames) Dim TInput As GenericTypeParameterBuilder = typeParameters(0) Dim TOutput As GenericTypeParameterBuilder = typeParameters(1) string[] typeParameterNames = {"TInput", "TOutput"}; GenericTypeParameterBuilder[] typeParameters = factoryDefineGenericParameters(typeParameterNames); GenericTypeParameterBuilder TInput = typeParameters[0]; GenericTypeParameterBuilder TOutput = typeParameters[1]; 可以选择为类型参数添加特殊约束。 特殊约束是通过使用 SetGenericParameterAttributes 方法添加的。 在此示例中,TOutput 被约束为引用类型,并且具有无参数构造函数。 TOutputSetGenericParameterAttributes( _ GenericParameterAttributesReferenceTypeConstraint Or _ GenericParameterAttributesDefaultConstructorConstraint) TOutputSetGenericParameterAttributes( GenericParameterAttributesReferenceTypeConstraint | GenericParameterAttributesDefaultConstructorConstraint); 可以选择为类型参数添加类约束和接口约束。 在此示例中,类型参数 TOutput 被约束为实现 ICollection(Of TInput)(在 C# 中为 ICollection<TInput>)接口的类型。 这确保 Add 方法可用于添加元素。 Dim icoll As Type = GetType(ICollection(Of )) Dim icollOfTInput As Type = icollMakeGenericType(TInput) Dim constraints() As Type = { icollOfTInput } TOutputSetInterfaceConstraints(constraints) Type icoll = typeof(ICollection<>); Type icollOfTInput = icollMakeGenericType(TInput); Type[] constraints = {icollOfTInput}; TOutputSetInterfaceConstraints(constraints); 使用 SetParameters 方法定义该方法的形参。 在此示例中,Factory 方法具有一个参数,即 TInput 的数组。 此类型是通过对表示 TInput 的 GenericTypeParameterBuilder 调用 MakeArrayType 方法创建的。 SetParameters 的参数是 Type 对象的数组。 Dim params() As Type = { TInputMakeArrayType() } factorySetParameters(params) Type[] parms = {TInputMakeArrayType()}; factorySetParameters(parms); 使用 SetReturnType 方法定义该方法的返回类型。 在此示例中,返回 TOutput 的一个实例。 factorySetReturnType(TOutput) factorySetReturnType(TOutput); 使用 ILGenerator 发出该方法体。 有关详细信息,请参见附带的过程发出方法体。 重要事项 发出对泛型类型的方法的调用,而且这些类型的类型变量为该泛型方法的类型参数时,您必须使用 TypeBuilder 类的static GetConstructor(Type, ConstructorInfo)、 GetMethod(Type, MethodInfo) 和 GetField(Type, FieldInfo) 方法重载,以获取这些方法的构造形式。 发出方法体的附带过程对此进行了演示。 完成包含该方法的类型并保存程序集。 附带的过程调用泛型方法演示了调用完整方法的两种方式。 ' Complete the type Dim dt As Type = demoTypeCreateType() ' Save the assembly, so it can be examined with Ildasmexe demoAssemblySave(asmNameName & "dll") // Complete the type Type dt = demoTypeCreateType(); // Save the assembly, so it can be examined with Ildasmexe demoAssemblySave(asmNameName+"dll"); 发出方法体 获取代码生成器并声明局部变量和标签。 DeclareLocal 方法用于声明局部变量。 Factory 方法具有四个局部变量:retVal 用于保存该方法返回的新 TOutput,ic 用于在 TOutput 转换为 ICollection(Of TInput)(在 C# 中为 ICollection<TInput>)时将其保存,input 用于保存 TInput 对象的输入数组,而 index 用于循环访问该数组。 该方法还具有使用 DefineLabel 方法定义的两个标签,一个用于进入循环 (enterLoop),另一个用于循环的顶部 (loopAgain)。 该方法首先使用 Ldarg_0 *** 作码加载其参数,然后使用 Stloc_S *** 作码将参数存储在局部变量 input 中。 Dim ilgen As ILGenerator = factoryGetILGenerator() Dim retVal As LocalBuilder = ilgenDeclareLocal(TOutput) Dim ic As LocalBuilder = ilgenDeclareLocal(icollOfTInput) Dim input As LocalBuilder = _ ilgenDeclareLocal(TInputMakeArrayType()) Dim index As LocalBuilder = _ ilgenDeclareLocal(GetType(Integer)) Dim enterLoop As Label = ilgenDefineLabel() Dim loopAgain As Label = ilgenDefineLabel() ilgenEmit(OpCodesLdarg_0) ilgenEmit(OpCodesStloc_S, input) ILGenerator ilgen = factoryGetILGenerator(); LocalBuilder retVal = ilgenDeclareLocal(TOutput); LocalBuilder ic = ilgenDeclareLocal(icollOfTInput); LocalBuilder input = ilgenDeclareLocal(TInputMakeArrayType()); LocalBuilder index = ilgenDeclareLocal(typeof(int)); Label enterLoop = ilgenDefineLabel(); Label loopAgain = ilgenDefineLabel(); ilgenEmit(OpCodesLdarg_0); ilgenEmit(OpCodesStloc_S, input); 使用 ActivatorCreateInstance 方法的泛型方法重载发出代码以创建 TOutput 的实例。 使用此重载需要指定类型具有无参数构造函数,这也是向 TOutput 添加该约束的原因。 通过将 TOutput 传递到 MakeGenericMethod 创建构造的泛型方法。 发出代码以调用该方法后,发出代码以使用 Stloc_S 将其存储在局部变量 retVal 中 Dim createInst As MethodInfo = _ GetType(Activator)GetMethod("CreateInstance", TypeEmptyTypes) Dim createInstOfTOutput As MethodInfo = _ createInstMakeGenericMethod(TOutput) ilgenEmit(OpCodesCall, createInstOfTOutput) ilgenEmit(OpCodesStloc_S, retVal) MethodInfo createInst = typeof(Activator)GetMethod("CreateInstance", TypeEmptyTypes); MethodInfo createInstOfTOutput = createInstMakeGenericMethod(TOutput); ilgenEmit(OpCodesCall, createInstOfTOutput); ilgenEmit(OpCodesStloc_S, retVal); 发出代码以将新的 TOutput 对象强制转换为 ICollection(Of TInput),并将其存储在局部变量 ic 中。 ilgenEmit(OpCodesLdloc_S, retVal) ilgenEmit(OpCodesBox, TOutput) ilgenEmit(OpCodesCastclass, icollOfTInput) ilgenEmit(OpCodesStloc_S, ic) ilgenEmit(OpCodesLdloc_S, retVal); ilgenEmit(OpCodesBox, TOutput); ilgenEmit(OpCodesCastclass, icollOfTInput); ilgenEmit(OpCodesStloc_S, ic); 获取表示 ICollectionTAdd 方法的 MethodInfo。 此方法对 ICollection(Of TInput)(在 C# 中为 ICollection<TInput>)进行 *** 作,因此有必要获取特定于该构造类型的 Add 方法。 不能使用 GetMethod 方法直接从 icollOfTInput 获取此 MethodInfo,因为 GetMethod 在已使用 GenericTypeParameterBuilder 构造的类型上不受支持。 而应该对包含 ICollectionT 泛型接口的泛型类型定义的 icoll 调用 GetMethod。 然后使用 GetMethod(Type, MethodInfo)static 方法生成构造类型的 MethodInfo。 下面的代码对此进行了演示。 Dim mAddPrep As MethodInfo = icollGetMethod("Add") Dim mAdd As MethodInfo = _ TypeBuilderGetMethod(icollOfTInput, mAddPrep) MethodInfo mAddPrep = icollGetMethod("Add"); MethodInfo mAdd = TypeBuilderGetMethod(icollOfTInput, mAddPrep); 发出代码以初始化 index 变量(通过加载 32 位整数 0 并将其存储在变量中)。 发出代码以分支到标签 enterLoop。 因为此标签位于循环内,所以尚未进行标记。 该循环的代码在下一步中发出。 ' Initialize the count and enter the loop ilgenEmit(OpCodesLdc_I4_0) ilgenEmit(OpCodesStloc_S, index) ilgenEmit(OpCodesBr_S, enterLoop) // Initialize the count and enter the loop ilgenEmit(OpCodesLdc_I4_0); ilgenEmit(OpCodesStloc_S, index); ilgenEmit(OpCodesBr_S, enterLoop); 发出该循环的代码。 第一步是标记循环的顶部,方法是用 loopAgain 标签调用 MarkLabel。 使用该标签的分支语句现在将分支到代码中的这个点。 下一步是将强制转换为 ICollection(Of TInput) 的TOutput 对象推入堆栈。 这不需要立即进行,但需要在调用 Add 方法之前完成。 接下来,输入数组被推入堆栈,然后是包含该数组的当前索引的 index 变量。 Ldelem *** 作码从堆栈中d出该索引和数组,然后将索引数组元素推入堆栈。 堆栈现在准备好调用 ICollectionTAdd 方法,该方法从堆栈中d出集合和新的元素,并将该元素添加到该集合中。 循环中的其他代码会使索引递增,并通过测试查看循环是否完成:将索引和 32 位整数 1 推入堆栈并相加,在堆栈上保留总和;总和存储在 index 中。 然后调用 MarkLabel 将该点设置为循环的入口点。 再次加载该索引。 将输入数组推入堆栈,然后发出 Ldlen 以获取其长度。 索引和长度现在位于堆栈中,并发出 Clt 对二者进行比较。 如果索引小于长度, Brtrue_S 会重新返回到循环的起始点。 ilgenMarkLabel(loopAgain) ilgenEmit(OpCodesLdloc_S, ic) ilgenEmit(OpCodesLdloc_S, input) ilgenEmit(OpCodesLdloc_S, index) ilgenEmit(OpCodesLdelem, TInput) ilgenEmit(OpCodesCallvirt, mAdd) ilgenEmit(OpCodesLdloc_S, index) ilgenEmit(OpCodesLdc_I4_1) ilgenEmit(OpCodesAdd) ilgenEmit(OpCodesStloc_S, index) ilgenMarkLabel(enterLoop) ilgenEmit(OpCodesLdloc_S, index) ilgenEmit(OpCodesLdloc_S, input) ilgenEmit(OpCodesLdlen) ilgenEmit(OpCodesConv_I4) ilgenEmit(OpCodesClt) ilgenEmit(OpCodesBrtrue_S, loopAgain) ilgenMarkLabel(loopAgain); ilgenEmit(OpCodesLdloc_S, ic); ilgenEmit(OpCodesLdloc_S, input); ilgenEmit(OpCodesLdloc_S, index); ilgenEmit(OpCodesLdelem, TInput); ilgenEmit(OpCodesCallvirt, mAdd); ilgenEmit(OpCodesLdloc_S, index); ilgenEmit(OpCodesLdc_I4_1); ilgenEmit(OpCodesAdd); ilgenEmit(OpCodesStloc_S, index); ilgenMarkLabel(enterLoop); ilgenEmit(OpCodesLdloc_S, index); ilgenEmit(OpCodesLdloc_S, input); ilgenEmit(OpCodesLdlen); ilgenEmit(OpCodesConv_I4); ilgenEmit(OpCodesClt); ilgenEmit(OpCodesBrtrue_S, loopAgain); 发出代码以将 TOutput 对象推入堆栈,并从该方法返回。 局部变量 retVal 和ic 均包含对新的 TOutput 的引用;ic 仅用于访问 ICollectionTAdd 方法。 ilgenEmit(OpCodesLdloc_S, retVal) ilgenEmit(OpCodesRet) ilgenEmit(OpCodesLdloc_S, retVal); ilgenEmit(OpCodesRet); 调用泛型方法Factory 为泛型方法定义。 若要调用泛型方法,您必须为泛型方法的泛型类型参数分配类型。 使用 MakeGenericMethod 方法可完成此 *** 作。 下面的代码通过为 TInput 指定 String 并为TOutput 指定List(Of String)(在 C# 中为 List<string>)创建一个构造的泛型方法,并显示该方法的字符串表示形式。 Dim m As MethodInfo = dtGetMethod("Factory") Dim bound As MethodInfo = mMakeGenericMethod( _ GetType(String), GetType(List(Of String))) ' Display a string representing the bound method ConsoleWriteLine(bound) MethodInfo m = dtGetMethod("Factory"); MethodInfo bound = mMakeGenericMethod(typeof(string), typeof(List<string>)); // Display a string representing the bound method ConsoleWriteLine(bound); 若要以后期绑定的形式调用该方法,请使用 Invoke 方法。 下面的代码创建 Object(包含的唯一元素为字符串数组)的数组,并将其作为泛型方法的参数列表传递。 Invoke 的第一个参数为空引用,因为该方法为 static 的。 返回值被强制转换为 List(Of String),并显示它的第一个元素。 Dim o As Object = boundInvoke(Nothing, New Object() { arr }) Dim list2 As List(Of String) = CType(o, List(Of String)) ConsoleWriteLine("The first element is: {0}", list2(0)) object o = boundInvoke(null, new object[]{arr}); List<string> list2 = (List<string>) o; ConsoleWriteLine("The first element is: {0}", list2[0]); 若要使用委托调用该方法,您必须具有与该构造的泛型方法的签名匹配的委托。 实现上述目标的一种简便方式是创建一个泛型委托。 下面的代码使用 DelegateCreateDelegate(Type, MethodInfo) 方法重载,创建在代码示例中定义的泛型委托 D 的一个实例,然后调用该委托。 委托的性能比后期绑定调用好。 Dim dType As Type = GetType(D(Of String, List(Of String))) Dim test As D(Of String, List(Of String)) test = CType( _ [Delegate]CreateDelegate(dType, bound), _ D(Of String, List(Of String))) Dim list3 As List(Of String) = test(arr) ConsoleWriteLine("The first element is: {0}", list3(0)) Type dType = typeof(D<string, List <string>>); D<string, List <string>> test; test = (D<string, List <string>>) DelegateCreateDelegate(dType, bound); List<string> list3 = test(arr); ConsoleWriteLine("The first element is: {0}", list3[0]); 发出的方法也可以从引用保存的程序集的程序调用。 示例 下面的代码示例使用泛型方法 Factory 创建一个非泛型类型 DemoType。 此方法具有两个泛型类型参数:TInput 用于指定输入类型,TOutput 用于指定输出类型。 TOutput 类型参数被限制为实现 ICollection<TInput>(在 Visual Basic 中为 ICollection(Of TInput)),限制为引用类型,并且限制为具有无参数构造函数。 该方法具有一个形参,这是一个 TInput 的数组。 该方法返回 TOutput 的实例,该实例包含输入数组中的所有元素。 TOutput 可以是实现 ICollectionT 泛型接口的任意泛型集合类型。 代码执行时,该动态程序集会保存为 DemoGenericMethod1dll,并可使用 Ildasmexe(MSIL 反汇编程序) 对其进行检查。 说明 学习如何发出代码的一种好方法是编写执行您要发出的任务的 Visual Basic、C# 或 Visual C++ 程序,然后使用反汇编程序检查编译器生成的 MSIL。 该代码示例包含等效于发出的方法的源代码。 发出的方法是以后期绑定的形式进行调用的,同时也使用了在该代码示例中声明的泛型委托。 Imports System Imports SystemCollectionsGeneric Imports SystemReflection Imports SystemReflectionEmit ' Declare a generic delegate that can be used to execute the ' finished method ' Delegate Function D(Of TIn, TOut)(ByVal input() As TIn) As TOut Class GenericMethodBuilder ' This method shows how to declare, in Visual Basic, the generic ' method this program emits The method has two type parameters, ' TInput and TOutput, the second of which must be a reference type ' (Class), must have a parameterless constructor (New), and must ' implement ICollection(Of TInput) This interface constraint ' ensures that ICollection(Of TInput)Add can be used to add ' elements to the TOutput object the method creates The method ' has one formal parameter, input, which is an array of TInput ' The elements of this array are copied to the new TOutput ' Public Shared Function Factory(Of TInput, _ TOutput As {ICollection(Of TInput), Class, New}) _ (ByVal input() As TInput) As TOutput Dim retval As New TOutput() Dim ic As ICollection(Of TInput) = retval For Each t As TInput In input icAdd(t) Next Return retval End Function Public Shared Sub Main() ' The following shows the usage syntax of the Visual Basic ' version of the generic method emitted by this program ' Note that the generic parameters must be specified ' explicitly, because the compiler does not have enough ' context to infer the type of TOutput In this case, TOutput ' is a generic List containing strings ' Dim arr() As String = {"a", "b", "c", "d", "e"} Dim list1 As List(Of String) = _ GenericMethodBuilderFactory(Of String, List(Of String))(arr) ConsoleWriteLine("The first element is: {0}", list1(0)) ' Creating a dynamic assembly requires an AssemblyName ' object, and the current application domain ' Dim asmName As New AssemblyName("DemoMethodBuilder1") Dim domain As AppDomain = AppDomainCurrentDomain Dim demoAssembly As AssemblyBuilder = _ domainDefineDynamicAssembly(asmName, _ AssemblyBuilderAccessRunAndSave) ' Define the module that contains the code For an ' assembly with one module, the module name is the ' assembly name plus a file extension Dim demoModule As ModuleBuilder = _ demoAssemblyDefineDynamicModule( _ asmNameName, _ asmNameName & "dll") ' Define a type to contain the method Dim demoType As TypeBuilder = demoModuleDefineType( _ "DemoType", _ TypeAttributesPublic) ' Define a Shared, Public method with standard calling ' conventions Do not specify the parameter types or the ' return type, because type parameters will be used for ' those types, and the type parameters have not been ' defined yet ' Dim factory As MethodBuilder = _ demoTypeDefineMethod("Factory", _ MethodAttributesPublic Or MethodAttributesStatic) ' Defining generic type parameters for the method makes it a ' generic method To make the code easier to read, each ' type parameter is copied to a variable of the same name ' Dim typeParameterNames() As String = {"TInput", "TOutput"} Dim typeParameters() As GenericTypeParameterBuilder = _ factoryDefineGenericParameters(typeParameterNames) Dim TInput As GenericTypeParameterBuilder = typeParameters(0) Dim TOutput As GenericTypeParameterBuilder = typeParameters(1) ' Add special constraints ' The type parameter TOutput is constrained to be a reference ' type, and to have a parameterless constructor This ensures ' that the Factory method can create the collection type ' TOutputSetGenericParameterAttributes( _ GenericParameterAttributesReferenceTypeConstraint Or _ GenericParameterAttributesDefaultConstructorConstraint) ' Add interface and base type constraints ' The type parameter TOutput is constrained to types that ' implement the ICollection(Of T) interface, to ensure that ' they have an Add method that can be used to add elements ' ' To create the constraint, first use MakeGenericType to bind ' the type parameter TInput to the ICollection(Of T) interface, ' returning the type ICollection(Of TInput), then pass ' the newly created type to the SetInterfaceConstraints ' method The constraints must be passed as an array, even if ' there is only one interface ' Dim icoll As Type = GetType(ICollection(Of )) Dim icollOfTInput As Type = icollMakeGenericType(TInput) Dim constraints()
近几年来 在TIOBE公司每个月发布的编程语言排行榜[ ]中 总是能挤进前 名 而在近 年的编程语言排行榜中 C#总体上呈现上升的趋势 C#能取得这样的成绩 有很多因素在起作用 其中 它在语言特性上的锐意进取让人印象深刻(图 )
图 C#各版本的创新点
年发布的C# 最大的创新点是拥有了动态编程语言的特性
动态编程语言的中兴
动态编程语言并非什么新鲜事物 早在面向对象编程语言成为主流之前 人们就已经使用动态编程语言来开发了 即使在 C# 等面向对象编程语言繁荣兴旺 大行于世的年代 动态编程语言也在 悄悄 地攻城掠地 占据了相当的开发领域 比如 业已成为客户端事实上的主流语言
最近这几年 动态编程语言变得日益流行 比如Python Ruby都非常活跃 使用者众多
这里有一个问题 为什么我们需要在开发中应用动态编程语言?与C#和Java这类已经非常成熟且功能强大的静态类型编程语言相比 动态编程语言有何优势?
简单地说 使用动态编程语言开发拥有以下的特性
( )支持REPL(Read evaluate print Loop 读入à执行à输出 循环迭代)的开发模式 整个过程简洁明了 直指问题的核心
举个简单的例子 图 所示为使用IronPython[ ]编程计算 + +……+ 的屏幕截图 我们可以快速地输入一段完成累加求和的代码 然后马上就可以看到结果
图 使用IronPython编程
如果使用开发就麻烦多了 您得先用Visual Studio创建一个项目 然后向其中添加一个类 在类中写一个方法完成求和的功能 再编写调用这一方法的代码 编译 排错 最后才能得到所需的结果……
很明显 对于那些短小的工作任务而言 动态编程语言所具备的这种REPL开发模式具有很大的吸引力
( )扩展方便 用户可以随时对代码进行调整 需要什么功能直接往动态对象上 加 就是了 不要时又可以移除它们 而且这种修改可以马上生效 并不需要像C#那样必须先修改类型的定义和声明 编译之后新方法才可用
换句话说 使用动态语言编程 不需要 重量级 的OOAD 整个开发过程迭代迅速而从不拖泥带水
( )动态编程语言的类型解析是在运行时完成的 可以省去许多不必要的类型转换代码 因此 与静态编程语相比 动态编程语言写的代码往往更紧凑 量更少
动态编程语言主要的弱点有两个
( )代码中的许多错误要等到运行时才能发现 而且需要特定的运行环境支持 对其进行测试不太方便 也不支持许多用于提升代码质量的各种工具 因此不太适合于开发规模较大的 包容复杂处理逻辑的应用系统
( )与静态编程语言相比 动态编程语言编写的程序性能较低 不过随着计算机软技术的不断进步 比如多核的广泛应用 动态编程语言引擎和运行环境不断地优化 动态编程语言编写的程序性能在不断地提升 在特定的应用场景下 甚至可以逼近静态语言编写的程序
拥抱 动态编程 特性的
为了让C# Visual Basic等编程语言能具备动态编程语言的特性 NET 引入了一个 DLR(Dynamic Language Runtime 动态语言运行时) (图 )
图 DLR 动态语言运行时
DLR运行于CLR之上 提供了一个动态语言的运行环境 从而允许Python Ruby等动态语言编写的程序在 NET平台上运行 同时 现有的 NET静态类型编程语言 比如C#和Visual Basic 也可以利用DLR而拥有一些动态编程语言的特性
( )使用C# 编写动态的代码
C# 新增了一个dynamic关键字 可以用它来编写 动态 的代码
例如 以下代码创建了一个ExpandoObject对象(注意必须定义为dynamic)
dynamic dynamicObj = new ExpandoObject();
这一对象的奇特之处在于 我们可以随时给它增加新成员
dynamicObj Value = ; //添加字段dynamicObj Increment = new Action(() => dynamicObj Value++); //添加方法
这些动态添加的成员与普通的类成员用法一样
for (int i = ; i < ; i++)dynamicObj Increment();//调用方法Console WriteLine( dynamicObj Value={ } dynamicObj Value);//访问字段
ExpandoObject对象实现了IDictionary<string object>接口 可看成是一个字典对象 所有动态添加的成员都是这个字典对象中的元素 这意味我们不仅可以添加新成员 还可以随时移除不再需要的成员
//移除Increment方法(dynamicObj as IDictionary<stringobject>) Remove( Increment );
方法移除之后 再尝试访问此方法将引发RuntimeBinderException异常
( )使用dynamic关键字简化与组件交互的代码
要在这个 托管世界 里调用 非托管世界 中的组件 我们必须通过 互 *** 作程序集(Interop Assembly) 作为桥梁 互 *** 作程序集 定义了CLR类型与类型之间的对应关系
只要给 NET项目添加对 互 *** 作程序集 的引用 就可以在 NET应用程序中创建这一程序集所包容的各种类型的实例(即包装器对象) 对这些对象的方法调用(或对其属性的存取)将会被转发给组件
以调用Word为例 在 之前您可能经常需要编写这样的代码
Object wordapp = new Word Application(); //创建Word对象Object fileName = MyDoc docx;//指定Word文档Object argu = System Reflection Missing Value;Word Document doc = wordapp Documents Open(ref fileNameref arguref arguref arguref arguref arguref arguref argu ref arguref arguref arguref arguref arguref arguref arguref argu);
上述对Open()方法的调用语句只能用 恐怖 一词来形容 其原因是Word组件中的Open()方法定义了太多的参数
使用dynamic关键字 配合从Visual Basic中学来的 命名参数与可选参数 这两个新语法特性 可以写出更简洁的代码
dynamic wordapp = new Word Application();dynamic doc = wordapp Documents Open(FileName: MyDoc docx );
上述代码中省去了用不着的参数 并且可以去掉参数前的ref关键字
当上述代码运行时 DLR会使用反射技术将dynamic表达式 绑定(bind) 到互 *** 作程序集中所包容的Word Application代理对象
( )C# 动态编程技术内幕
C# 中所定义的dynamic变量可以引用以下类型的对象
l 传统的 静态 的CLR对象
l 包装器对象 前面已经介绍了这方面的内容
l 实现了IDynamicMetaObjectProvider接口的 动态对象 ExpandoObject就是这种类型对象的实例
l 基于DLR实现的动态语言(比如IronRuby和IronPython)所创建的对象
从程序员角度来看 所有这四种对象都是一样的 都可用一个dynamic变量引用之 而DLR在程序运行时动态地将方法调用和字段存取请求 绑定 到真正的对象上
dynamic的功能是由DLR所支撑的 是C#编译器与DLR分工合作的成果
请看以下示例代码
dynamic d = ;d++;
C#编译器在处理上述代码时 它并不去检查变量d是否可以支持自增 *** 作 而是为其创建了一个CallSite<T>对象(<>p__Site )
private static class <Main>o__SiteContainer{public static CallSite<Func<CallSiteobjectobject>> <>p__Site ;}
中文MSDN将CallSite<T>译为 动态(调用)站点 它是DLR中的核心组件之一
动态站点对象通过CallSite<T> Create()方法创建 C#编译器会为其指定一个派生自CallSiteBinder的对象(称为 动态站点绑定对象 )作为其参数
动态站点绑定对象是与具体语言相关的 比如IronPython和C#都有各自的动态站点绑定对象
动态站点绑定对象的主要工作是将代码中的动态表达式(本例中为d++)转换为一棵 抽象语法树(AST Abstract Syntax Tree) 这棵语法树被称为 DLR Tree 是在 所引入的LINQ表达式树的基础上扩充而来的 因此 有时又称其为 表达式树(Expression Tree)
DLR在内部调用此表达式树的Compile()方法生成IL指令 得到一个可以被CLR所执行的委托(在本例中其类型就是Func<CallSite object object>)
动态调用站点对象(本例中为<>p__Site )有一个Target属性 它负责引用这一生成好的委托
委托生成之后 动态表达式的执行就体现为委托的执行 其实参由编译器直接 写死 在IL代码中
简化的代码示意如下(通过Reflector得到 为便于阅读 修改了变量名)
object d = ;object CS$ $= d;if (<>p__Site== null)<>p__Site= CallSite<Func<CallSiteobjectobject>> Create(……);d = <>p__Site Target(<>p__SiteCS$ $ );
上述类型推断 方法绑定及IL代码生成的工作都是在程序运行时完成的
( )动态代码很慢吗?
动态编程语言易学易用 代码紧凑 开发灵活 但性能则一直是它的 软肋 为了提升性能 DLR设计了一个三级缓存策略
动态站点绑定对象会为动态调用表达式转换而成的语法树加上相应的测试条件(称为 test ) 构成一个 规则(Rule) 这个规则可以用于判断某个语法树是否可用于特定的动态调用表达式
举个例子 请看以下这个动态表达式
d + d
如果在程序运行时d 和d 都是int类型的整数 则DLR生成的规则为
if( dis int && dis int) //测试条件return (int)d +(int)d ; //语法树
DLR通过检查规则中的 测试条件 就可以知道某个动态表达式是否可以使用此规则所包容的语法树
规则 是DLR缓存的主要对象
前面介绍过的动态站点对象Target属性所引用的委托是第一级缓存 它实现的处理逻辑是这样的
//当前处理规则 属于第 级缓存if( dis int && dis int) //测试条件return (int)d +(int)d ; //满足测试条件 直接返回一个表达式树//未命中 则在第 级 第 级缓存中查找 如果找到了 用找到的结果更新第 级缓存return site Update(site d d );
如果 级缓存中都没有命中的规则 则此动态站点所关联的调用站点绑定对象会尝试创建一个新的规则 如果创建新规则失败 则由当前编程语言(比如)所提供的默认调用站点绑定对象决定如何处理 通常的作法是抛出一个异常
当前版本的DLR第 级缓存了 条规则 第 级则缓存了 条规则
由于DLR自身设计了一个 规则 缓存系统 又充分利用了CLR所提供的JIT缓存(因为所有动态调用代码最终都会转换为CLR可以执行的IL指令 而CLR可以缓存这些代码) 使得动态代码仅仅在第一次执行时性能较差 后续的连续调用其性能可以逼近静态代码
C# 与动态语言的集成
由于几乎所有的编程语言都可以使用抽象语法树来表达 因此 在理论上DLR支持无限多种编程语言间的互 *** 作 在当前版本中 可以实现C#/Visual Basic与IronPython和IronRuby的互 *** 作 相信很快会出现其他动态编程语言的DLR实现
一个有趣的地方是当前基于DLR实现的动态编程语言都以 Iron 开头 比如IronRuby和IronPython IronPython的设计者 DLR的架构设计师Jim Hugunin曾经在微软PDC 大会上解释说主要是为了避免起一个 Python 或 Python for NET 之类 微软味十足 的名字 才有了 IronPython 他强调 Iron 系列动态语言将严格遵循动态语言自身的标准和规范 尊重这些动态语言已有的历史和积累 不会引入一些仅限于 NET平台的新语言特性 并且这些语言的 NET实现保持开源 与此同时 Jim Hugunin指出 Iron 系列语言能很好地与 NET现有类库 编程语言和工具集成 并且能 嵌入 到 NET宿主程序中
( )动态对象通讯协议
由于各种动态编程语言之间的特性相差极大 实现各语言间的互 *** 作是个难题 为此DLR采取了一个聪明的策略 它不去尝试设计一个 通用的类型系统 (CLR就是这么干的) 而是设计了一个 通用的对象通讯协议 规定所有需要互 *** 作的动态对象必须实现IDynamicMetaObjectProvider接口 此接口定义了一个GetMetaObject()方法 接收一个语法树对象作为参数 向外界返回一个 动态元数据(DynamicMetaObject) 对象
DynamicMetaObject GetMetaObject(Expression parameter);
DynamicMetaObject对象向外界提供了两个重要属性 Restrictions引用一组测试条件 Expression属性则引用一个语法树 这两个属性组合起来就是可供动态站点对象缓存的 规则(Rule)
DLR中的 动态站点绑定对象(CallSiteBinder) 获取了DynamicMetaObject对象之后 它调用此对象所提供的各个方法创建 规则 让 动态站点对象(CallSite<T>) 的Target属性引用它 完成动态绑定的工作
( )动态语言集成环境
为了方便地实现静态编程语言与各种动态编程语言间的相互集成 DLR提供了一整套称为 通用寄宿(Common Hosting) 的组件 其中包容ScriptRuntime ScriptScope等类型
下面我们以IronPython为例 介绍如何在 开发的程序中集成动态编程语言代码
首先需要创建一个ScriptRuntime对象 它是一个最顶层的对象 用于在一个应用程序域中 嵌入 一个特定动态语言的运行环境
ScriptRuntime pythonRuntime = Python CreateRuntime();
接着需要创建一个ScriptEngine对象 它是动态语言代码的执行引擎
ScriptEngine engine = pythonRuntime GetEngine( py );
ScriptScope对象类似于中的命名空间 其中可以通过定义一些变量向动态代码传入数据 比如下述代码将一个C# 创建的ExpandoObject对象传给Python代码
ScriptScope scope = pythonRuntime CreateScope();//C#创建动态对象 dynamic expando = new ExpandoObject();expando Name = JinXuLiang ; //动态添加一个字段 //让IronPython接收C#创建的Expando对象scope SetVariable( ExpandoObjectexpando);string pythonCode = print ExpandoObject Name ; //IronPython引擎执行Python语句engine CreateScriptSourceFromString(pythonCode) Execute(scope);
上述示例代码是直接执行Python代码 在实际开发中 更常见的是直接执行Python文件中的代码 假设有一个Calculator py文件 其中定义了一个Add函数
def Add(a b):return a+b
则以下C#代码可以直接执行之
ScriptRuntime pythonRuntime = Python CreateRuntime();dynamic pythonFile = pythonRuntime UseFile( Calculator py );Console WriteLine(pythonFile Add( ));
上述示例说明在DLR的支持之下 可以让静态编程语言使用动态语言所开发的库 反过来 基于DLR实现的动态编程语言也能使用为静态语言所设计的库 比如标准的基类库
这意味着两点
( )我们现在可以将 静态 和 动态 编程语言组合起来 开发出一些具有高度交互性的应用程序 使用静态编程语言搭建系统框架 使用动态编程语言实现交互性 这是一个很值得注意的应用领域
( )将来会出现一些 静态 动态 编程语言同时适用的库 向实现 无所不在的复用 目标又前进了一步
Visual Studio 为新的 NET编程语言F#提供了专门的项目模板 但没有为IronPython和IronRuby之类动态语言的开发提供支持 相信随着动态语言在 NET平台之上的应用日趋广泛 后继版本的Visual Studio会直接支持动态语言的开发
从C# ~ 所走过的路 可以很清晰地看到它的发展轨迹 得到这样的一个结论
未来的编程语言应该是多范式的 具有高度的可组合性 在一个项目或产品中组合多个编程语言 使用多种编程范式会变得越来越普遍
lishixinzhi/Article/program/ASP/201311/21813
建议使用数组和集合来代替递增的变量名。
public int a[] = new int[5];public SortedList<int, int> b = new SortedList<int, int>();
a[1] = 123;
bAdd(1, 123);
b[1] = 456;
如果非要实现以动态递增的形式创建指定变量名的对象,需要用到动态类型。
动态创建变量应该是下面两个类负责。
字段:SystemReflectionEmitFieldBuilder(定义并表示字段。无法继承此类)
局部变量:SystemReflectionEmitLocalBuilder(表示方法或构造函数内的局部变量)
下面是动态创建类Class的参考代码
public static Type DynamicCreateType() {//动态创建程序集
AssemblyName DemoName = new AssemblyName("DynamicAssembly");
AssemblyBuilder dynamicAssembly = AppDomainCurrentDomainDefineDynamicAssembly(DemoName, AssemblyBuilderAccessRunAndSave);
//动态创建模块
ModuleBuilder mb = dynamicAssemblyDefineDynamicModule(DemoNameName, DemoNameName + "dll");
//动态创建类MyClass
TypeBuilder tb = mbDefineType("MyClass", TypeAttributesPublic);
//动态创建字段
FieldBuilder fb = tbDefineField("myField", typeof(SystemString), FieldAttributesPrivate);
//动态创建构造函数
Type[] clorType = new Type[] { typeof(SystemString) };
ConstructorBuilder cb1 = tbDefineConstructor(MethodAttributesPublic, CallingConventionsStandard, clorType);
//生成指令
ILGenerator ilg = cb1GetILGenerator();//生成 Microsoft 中间语言 (MSIL) 指令
ilgEmit(OpCodesLdarg_0);
ilgEmit(OpCodesCall, typeof(object)GetConstructor(TypeEmptyTypes));
ilgEmit(OpCodesLdarg_0);
ilgEmit(OpCodesLdarg_1);
ilgEmit(OpCodesStfld, fb);
ilgEmit(OpCodesRet);
//动态创建属性
PropertyBuilder pb = tbDefineProperty("MyProperty", PropertyAttributesHasDefault, typeof(string), null);
//动态创建方法
MethodAttributes getSetAttr = MethodAttributesPublic | MethodAttributesSpecialName;
MethodBuilder myMethod = tbDefineMethod("get_Field", getSetAttr, typeof(string), TypeEmptyTypes);
//生成指令
ILGenerator numberGetIL = myMethodGetILGenerator();
numberGetILEmit(OpCodesLdarg_0);
numberGetILEmit(OpCodesLdfld, fb);
numberGetILEmit(OpCodesRet);
//使用动态类创建类型
Type classType = tbCreateType();
//保存动态创建的程序集 (程序集将保存在程序目录下调试时就在Debug下)
dynamicAssemblySave(DemoNameName + "dll");
//创建类
return classType;
}
以上就是关于如何用C#将COM组件转换成程序集全部的内容,包括:如何用C#将COM组件转换成程序集、如何:用反射发出定义泛型方法、C# 动态编程新特性与DLR剖析等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)