分类: WINDOWS
2010-05-13 23:38:37
C++ At Work 专栏... 下载源代码: (273KB)
Ravi Singh 我正在用 Visual C++ 6.0 编写一个插件应用,它是一个 DLL,输出和接收纯虚拟接口指针。加载 DLL 后,EXE 便调用 DLL 中输出的 C 函数,该函数返回一个纯虚拟接口指针。然后 EXE 调用该接口上的方法,有时会传回另一个接口指针给 DLL 处理。目前有人要求必须用 C#,Visual Basic .NET 和其它语言编写插件。我没有什么基于 .NET 的编程经验,不懂托管和非托管代码之间的通讯问题,我找到许多有关这方面的信息,但是越看越糊涂。我如何才能让用户编写基于.NET 语言的插件? Daniel Godson 在 MSDN 杂志 2003 年 10 月刊中,有一篇 Jason Clark 写的一篇关于插件的文章,但我并不介意在此复习一下这个主题,尤其是因为插件本身就是 .NET 框架中举足轻重的部分(参见:)。毕竟,微软 .NET 框架的主要目的之一就是为编写可重用的软件组件提供一种语言无关的系统。从第一个 “Hello,world”程序到现在,这已经成为软件 开发至高无上的准则。可重用性从拷贝/粘贴到子例程,再到静态链接库,再到 DLLs 以及更专业的 VBX,OCX 和 COM。虽然最后三个东西属于不同的主题(它们都是 本机 DLLs),.NET 框架标志着一个真正的开端,因为所有代码都被编译成微软中间语言(MSIL)。互用性成为一种不可或缺的成分,因为在公共语言运行时层面,所有代码都一样。这就使得编写支持语言中立的插件体系结构 的程序变得尤其容易。那么在你的 C++ 程序中如何利用这个优势呢?Daniel 的虚拟函数指针系统就是一个手工自制的 COM。它就是 COM 对象本质之所在:纯虚拟函数指针。你可以为插件模型使用 COM ,开发人员可以用任何面向 .NET 的语言编写插件,因为这个框架让你创建和使用 COM 对象。但众所周知, COM 编码非常繁杂,因为它需要考虑的细节颇多,例如注册、引用计数,类型库等等——这些东西足以使你认为 COM 简直就是“Cumbersome Object Model”(麻烦对象模型)。如果你正在编写新代码并试图简化你的日常工作,那么就用 .NET 直接实现一个插件模型吧,我现在就是在讨论这个话题。 首先让我回答 Ray 的问题,即:在 .NET 中有没有类似 LoadLibrary 的东西,答案是:有,你可以用静态方法 System::Assembly::Load 加载任何框架程序集(就是一个包含 .NET 类的 DLL)。此外,.NET 支持反射机制。每个程序集都提供所有你需要的信息,如:该程序集有什么类,什么方法以及何种接口。不需要关心 GUIDs,注册,引用计数等诸如此类的事 情。 在我展示更一般的插件系统之前,我将从一个简单的例子开始,Figure 1 是一个 C# 类,它提供一个静态函数 SayHello。注意与 C/C++ 不同,在 .NET 中函数不单独输出;每个函数必须属于某个类,虽然这个类可以为静态的,也就是说它不需要实例化。为了将 MyLib.cs 编译成一个库,可以这样做: csc /target:library MyLib.cs 编译器将产生一个名为 MyLib.dll 的 .NET 程序集。为了通过托管扩展从 C++ 中调用 SayHello,你得这样写: #using 编译器链接到 MyLib.dll 并调用正确的入口点。这一切都简单明了,它属于 .NET 的基础。现在假设你不想在编译时链接 MyLib,而是想进行动态链接,就像在 C/C++ 用 LoadLibrary 那样。毕竟,插件无非是要在运行时链接,在你已经生成并交付的应用程序之后。Figure 2 所做的事情和前述代码段一样,只不过它是动态加载 MyLib 的。关键函数是 Assembly::Load。一旦你加载了该程序集,你便可以调用 Assembly::GetType 来获得有关类的 Type 信息(注意你必须提供全限定名字空间和类名),进而调用 Type::GetMethod 来获取有关方法的信息,甚至是调用它,就像这样: MethodInfo* m = ...; // get it String* args[] = {"Test2"}; m->Invoke(NULL, args); 第一个参数是对象实例(此例中为 NULL,因为 SayHello 是静态的),第二个参数是 Object (对象)数组,明白了吗? BOOL CMyApp::InitInstance() { ... m_plugins.LoadAll(__typeof(ITextPlugin)); } 此处 m_plug-ins 为 CPluginMgr 的一个实例。构造函数的参数为一个子目录名(默认值是 “Plugins”);LoadAll 搜索该文件夹查找程序集,在该程序集中包含的类实现了所请求的接口。当它找到这样一个程序集,CPluginMgr 便创建一个该类的实例并将它添加到一个列表中(STL vector)。下面是关键代码段: for (/* each type in assembly*/) { if (iface->IsAssignableFrom(type)) { {Object* obj = Activator::CreateInstance(type); m_objects.push_back(obj); count++; } } 换句话说,如果类型(type)可被赋值给 ITextPlugin,CPluginMgr 则创建一个实例并将其添加到数组。因为 CPluginMgr 是一个本机类,它无法直接保存托管对象,所以数组 m_objects 实际上是一个 gcroot PLUGINLIST& pl = theApp.m_plugins.m_objects; for (PLUGINLIST::iterator it = pl.begin(); it!=pl.end(); it++) { Object* obj = *it; ITextPlugin* plugin = dynamic_cast (PLUGINLIST 是一个 typedef,用于 vector void CMyView::OnPluginCmdUI(CCmdUI* pCmdUI) { CEdit& edit = GetEditCtrl(); int begin,end; edit.GetSel(begin,end); pCmdUI->Enable(begin!=end); } 我已展示了 PGEdit 是如何加载和存取插件的,但你要如何实现插件呢?那是很容易的事情。首先生成一个定义接口的程序集——本文的例子中就是 TextPlugin.dll。该程序集不实现任何代码或类,仅仅定义接口。记住,.NET 是语言中立的,所以没有源代码,与 C++ 头文件完全不同。相反,你生成定义接口的程序集并将它分发给编写插件的开发人员。插件与该程序集链接,于是他们从你提供的接口派生。例如,下面的 C# 代码: using TextPlugin; public class MyPlugin : ITextPlugin { ... // implement ITextPlugin } Figure 8 展示了用 C# 编写的 PluginCaps 插件。正像你所看到的,它十分简单。有关细节请参考本文的源代码。 |
作者简介 Paul DiLascia 是一名自由作家,软件咨询顾问以及大型 Web/UI 的设计师。他是《Writing Reusable Windows Code in C++》书(Addison-Wesley, 1992)的作者。业余时间他开发 PixeLib,这是一个 MFC 类库,从 Paul 的网站 可以获得这个类库。 |
本文出自 的 期刊,可通过当地报摊获得,或者最好是 |