《COM技术内幕》 §5 —— 动态链接
关于如何从DLL中输出函数;如何装载DLL;客户与组件的划分。
《COM技术内幕》 §5 —— 动态链接(重新学习)
回顾:
1) 将组件分成多个接口只是将应用程序分割成多个部分的第一步。
2) 然后我们使用QueryInterface将组件抽象成由多个相互独立的接口构成的集合。
3) 下面通过对接口的引用计数实现对组件生命期的管理。
4) 在实现了IUnknown之后,组件和客户之间只是一种非常松散的连接,这种松散的连接关系使得组件和客户各自可以发生变化而不会对对方造成什么影响。
5) 这一章将讨论如何将组件放入到DLL中。
NOTE HERE:并不是要把一个组件变成为一个DDL。一个组件实际上并不是一个DLL,将组件当成DLL来看待是非常肤浅的。DLL只是一个组件服务器,或者说是一种发行组件的方式。组件实际上应看成是在DLL中所实现的接口集。DLL只是一种形式,而组件才是实质。
在客户可以获取某个组件接口指针之前,它必须先将相应的DLL装载到其进程空间中并创建此组件。第3章中给出的CreateInstance可以建立一个组件的实例并给客户返回一个IUnknown接口指针。这是DLL中唯一需要客户显式链接的函数。由于组件中客户所需的所有函数都可以通过一个接口指针而访问到,因此需要在DLL中输出CreateInstance函数以便客户可以调用它。
(1)从DLL中输出函数
DEF文件建立起来也是比较麻烦的。为此可以在某个例子中复制一个DEF文件,然后修改其中的某些行。
CMPNT1.DEF
;
;Compnt1 module-definition file.
;
LIBRARY Compnt1.dll
DESCRIPTION '(c)1996-1997 Dale E.Rogerson'
EXPORTS
CreateInstance @1 PRIVATE
对上述文件所需要做的全部工作就是在EXPORTS段中列出待从DLL中输出的函数的名称。对每个名称,还可以加上一个序号。在LIBRARY行上必须加上DLL的实际名称。
(2)DLL的装载
其中详细讲了为什么要用DLL,有点长,不想看了。
//
// Create.cpp
//
#include <iostream.h>
#include <unknwn.h> // Declare IUknown
#include "Create.h"
typedef IUnknown* ( *CREATEFUNCPTR)();
IUnknown* CallCreateInstance(char* name)
{
// Load dll int process
HINSTANCE hComponent = ::LoadLibrary(name);
if(hComponent == NULL)
{
cout << "CallCreateInstance:/tError:Cannot load component." << endl;
return NULL;
}
// Get address for CreateInstance function
CREATEFUNCPTR CreateInstance
= (CREATEFUNCPTR)::GetProcAddress(hComponent, "CreateInstance");
if(CreateInstance == NULL)
{
cout << "Call CreateInstance:/tError:"
<< "Cannot find CreateInstance function."
<< endl ;
return NULL;
}
return CreateInstance();
}
在此例中,并没有给出Create.h的代码,我就只写了一个IUnknown* CallCreateInstance(char* name);的声明,我想应该没有关系的,即使是空的估计也没什么(不过这点没实验J)。
(3)客户和组件的划分
这里,客户将在Client1.cpp中实现,客户文件还包括Create.h并将同Create.cpp一块链接起来。在2个文件将把创建包含在DLL中组件的过程封装起来。组件在此是在一个名为Cmpnt1.裁判判的文件中实现的。由于动态链接需要一个列出从DLL中输出的函数名称的模块定义文件,因此在组件文件中还包括一个名为Cmpnt1.def的文件。组件和客户共享了2个文件。其中之一Iface.和包含Cmpnt1支持的所有接口的声明,其中还包含这些接口的接口ID声明。关于这些接口ID的定义则在文件GUIDs.cpp中。(但是我实际上是把对接口的定义之间放在了相应的cpp文件中了,不知道为什么都认不出GUIDs.cpp中定义L)
使用下面的命令,可以生成客户程序Client1.exe和DLL文件Cmpnt1.dll。
path/ >cl Client1.cpp Create.cpp GUIDs.cpp UUID.lib // 建立客户程序
path/ >cl /LD Cmpnt1.cpp GUIDs.cpp UUID.lib Cmpnt1.def // 生成DLL
(4)回顾一下整个历程:
ÿ DLL的DEF文件和DLL装载的程序(在后面的章节里面,关于DLL的转载和注册将由专门的函数来完成);
ÿ 客户程序Client的编写,主要是 #include “” 文件和main( )函数;
ÿ 组件类继承IUnknown接口和其他接口的函数定义,构造析够函数等等;
ÿ 所有接口的头文件声明;
ÿ 对于IID的定义;
ÿ 利用上面的command line来建立客户程序,生成DLL。
今天最大的收获就是实现了Client1.exe和Cmpnt1.dll,并且调用成功了。J