Generated Type Library
生成的类型库
如前面已讨论的,用regevcs.exe建立和注册程序集不仅配置了服务组件,也会生成一个类型库。现在让我们来研究一下类型库。你可以使用一个工具来读取这个类型库里
的信息:Visual Studio→工具→OLE/COM对象查看器。这个OLE/COM对象查看器允许你深入COM对象,甚至启动它们。你可以用这个工具读取类型库信息:File →View TypeLib。
图4-3展示了打开了这个组件后OLE/COM对象查看器所显示的信息。在左边,你可以看到该程序集内所有类型的coclass和interface的定义。

图4-3 OleView打开了生成的类型库
细致地看一下生成的COM接口的详细定义。这份代码中定义的每个类型都有一个coclass入口被生成。一个coclass标记了一个COM对象,还列出了被这个组件实现了的那些COM接口。
对应于服务组件类CourseManagement的输出在下面列了出来。在其头部,UUID标记了此组件的唯一标识符,在COM组件中这也被称为CLSID。这个自定义的值为0F21F359-AB84-41E8-9A78-36D110E6D2F9的UUID特性仅在.NET客户端使用这个元数
据时有用。这个UUID标记了一个COM互操作特性。(也就是说,当系统从这个类型库类生成一个运行时调用包装(RCW) .NET类时,它将使用跟这个UUID有关联的名字来命名包装类。)
[
uuid(4535EEFE-94D3-35FA-943F-057FB4116ED8),
version(1.0),
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9,
Demos.COMInterop.CourseManagement)
]
coclass CourseManagement {
[default] interface _CourseManagement;
interface _Object;
interface IRemoteDispatch;
interface IDisposable;
interface IManagedObject;
interface IServicedComponentInfo;
interface ICourseManagement;
};
服务组件实现的很多接口都仅在内部使用,所以这章只会简要地描述IRemoteDispatch、IManagedObject和IServicedComponentInfo。
对于像在第2章中讨论过的声明了[AutoComplete]特性的方法,内部必须实现IRemoteDispath接口。对于.NET组件,接口并不是必须的,但是方法所需的元数据只能通过使用接口来写入COM+目录。IRemoteDispatch这个接口,是CLR运行时从程序集元数据中读取[AutoComplete]信息后实现的。同时,对于所有配置为服务组件的托管对象,运行时为它们实现了IManagedObject这个接口。
_Object这个接口定义了基类Object的所有方法,这样就允许客户端来调用托管对象的基类方法。
默认的接口_CourseManagement可以被用于调用此类型的公有方法,也能通过ICourseManagement这个接口来调用这些方法。因为CourseManagement类实现了ICourseManagement,所以这个接口也在这里被列了出来。
类CourseInfo也包含在服务组件程序集中,它的类型信息也在类型库中被记载。然而,因为它并不是从ServicedComponent类继承的类型,这里的类型信息看上去就不太一样了。在这个coclass的IDL头部分,你可以看到一个名称为noncreatable的属性,这样就不允许在客户端实例化这个类型的对象。你可以看到的另一个不同之处在于,这个组件只有两个接口,_CourseInfo和_Object。
[
uuid(BF26C367-4B4F-3017-A5F2-CAF972D2C66F),
version(1.0),
noncreatable,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9,
Demos.COMInterop.CourseInfo)
]
coclass CourseInfo {
[default] interface _CourseInfo;
interface _Object;
};
例子中另外的Customer和CustomerControl类,看上去和那两个已经讨论过的coclass很类似。让我们看一下接口的定义。
这个服务组件中定义的仅有的接口是ICourseManagement。你已经在CourseManagement的coclass节看到过这个接口。在这里列出的UUID定义了这个接口的唯一标识符,它也被称为接口ID(IID)。在默认情形下,生成的接口类型是一个双重接口,允许这个组件被脚本客户端调用。双重接口源于COM接口IDispatch,如这里所示。此外,你可以看到所有定义在这个接口中的方法,但是其方法签名已被改变,返回值总是一个HRESULT,这是COM接口的方法所期望的。
[
odl,
uuid(11BC9B79-C02C-39D6-A70F-32E83B18DD0E),
version(1.0),
dual,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9,
Demos.COMInterop.ICourseManagement)
]
interface ICourseManagement : IDispatch {
[id(0x60020000)]
HRESULT GetCourse([out, retval] _CourseInfo** pRetVal);
[id(0x60020001)]
HRESULT SetCourse([in] _CourseInfo* course);
[id(0x60020002)]
HRESULT GetCustomerControl([out, retval]
_CustomerControl** pRetVal);
};
那些类型专用的接口并没有在C# 源代码中定义,但是已经在coclass节中被列出,出现在生成的IDL定义中。然而,这些接口没有列出任何方法。其原因是这些方法只能在运行时通过反射来决定。
很多COM服务器会把它们的方法名和调度ID写入其类型库中。这使得创建语言相关的类型库成为可能(比如,方法名可以有多个语种的名字,如英语、德语和法语),这样只要替换类型库就可以在编写代码时使用另外的语言。然而这个概念并不成功,因为这带来了更多的问题(比如,运行中的系统可能并不带有正确语种的类型库)。
[
uuid(9DF3A585-8F0A-3648-B291-0C4832871C24),
hidden,
dual,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9,
Demos.COMInterop.CourseInfo)
]
dispinterface _CourseInfo {
properties:
methods:
};
至此,你了解到类型库在没有特别定义COM特性的情况下是如何被生成的。默认情况下总是生成双重接口,但通常这并不是最佳选择,接下来你将看到这一点。
什么是.NET反射?
每个.NET程序集除了程序代码外都额外包含了元数据。元数据包含了程序集本身的信息,比如版本号,引用了什么程序集等。元数据也包含了程序集内所有类型的信息,包括其方法、属性和字段。使用.NET反射,你可以在运行时读取这些信息,并且可以动态地调用方法。
让我们来看这样一个例子,它通过使用.NET反射读取和动态调用了一个方法。在这个示例中有一个Demo类,它带有方法Message以被动态调用。这个Demo类在程序集ReflectionLib.dll中。
namespace Samples.Reflection
{
public class Demo
{
public void Message(string s)
{
System.Console.WriteLine(s);
}
}
}
我们有一个简单的控制台程序,其中有一个Test类,动态地调用了Demo类的Message方法。首先,含有Demo类的程序集通过Assembly类的LoadFrom静态方法来加载入内存,这个程序集的文件名来自于命令行参数,是在启动这个应用程序时传入的。
通过Assembly.LoadFrom方法返回的assembly对象,可以读取其中的元数据。方法GetType会返回一个用于表示Samples.Reflection.Demo的Type对象。如果你想读取此程序集的所有的类型,方法GetTypes会返回一个Type对象的数组。
调用方法t.GetMethod会返回方法自身的信息,表现(represented)于MethodInfo类中。通过MethodInfo对象,你可以读取方法的名称,得到关于参数的信息。通过MethodInfo的Invoke方法,你可以动态地调用此方法。因为实例方法需要在调用时有一个实例存在,所以用Activator.CreateInstance根据从程序集中获得的类型(前面得到的Type对象)创建了一个对象。Invoke方法允许你通过一个对象数组传递任意数量的参数到目标方法。Message方法只需要一个参数,所以传入了一个只包含一个对象的数组到Invoke方法中。
结果是,Message方法在控制台输出了字符串“Test”。
using System;
using System.Reflection;
class Test
{
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("filename needed");
return;
}
string filename = args[0];
Assembly assembly = Assembly.LoadFrom(filename);
Type t = assembly.GetType("Samples.Reflection.Demo");
MethodInfo mi = t.GetMethod("Message");
object o = Activator.CreateInstance(t);
object[] parameters = new object[] {"Test"};
mi.Invoke(o, parameters);
}
}






