在实现了IUnknown之后,组件和客户之间只是一种非常松散的连接,这使用组件和客户各自可以发生变化而不会对对方造成什么影响。
下面讨论如何将组件放入到动态链接库(dll)中。
关于DLL更多内容可以参考我的其它文章:
组件的创建
在客户可以获取某个组件接口指针之前,它必须先将相应
的DLL装载到其进程空间中并创建此组件。这里创建组件的方法是临时性的,后面我还有其它方法(它客户和组件进一步分隔开来)。
之前说到QueryInterface函数的时候给出了CreateInstance可以建立一个组件的实例并给客户返回一个IUnknown接口指针(见:QueryInterface函数)这个函数是DLL中唯一需要显式链接的函数。客户其它所需要的所有函数都可以通过接口指针而访问到。
从DLL中输出CreateInstance函数
用extern "C"进行标记要输出的函数。
例如:
extern "C" IUnknown *CreateInstance()
{
IUnknow *pI = (IUnknown
*)(void *)new CA;
pI->AddRef();
Return pI;
}
在函数的定义前加上可防止C++ 编译器在函数名称上加上类型信息。例如在不加extern "C"时,Microsoft Visual C++ 将把CreateInstance变成:
?CreateInstance@@YAPAUUnknown@@XZ
其它的编译器可能会使用另外某种名称修改方法。
还需要建立一个的DEF文件。
LIBRARY
Search.dll
EXPORTS
导出函数名
DLL的装载
CreateInstance
装载DLL并调用其中所输出的函数
为装载指定的DLL,CallCreateInstance调用了Win32的LoadLibray函数:
GetProcAddress使用LoadLibray返回的句柄和待调用的函数名称,返回一个指向此函数的指针
FARPROC GetProcAddress(
HMODULE hModule,
LPCWSTR lpProcName
);
typedef IUnknown* (*CREATEFUNCPTR)();
IUnknown* CallCreateInstance(char* name)
{
// Load dynamic link library into process.
HINSTANCE hComponent = ::LoadLibraryA(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 << "CallCreateInstance:\tError: "
<< "Cannot find CreateInstance function."
<< endl;
return NULL;
}
return CreateInstance();
}
使用DLL的原因
DLL可以共享它们所链和的应用程序的地址空间。
客户和组件是通过接口进行交互的。一个接口实际上是一个指向函数的指针列表(vtbl)。组件为vtbl分配内存并用每个函数的地址来初始化此表格。
为了使用vtbl客户应该能
够访问组件 其vtbl民分配的内存,它还必须能够理解组件放入到vtbl中的各个地址。
进程外服务器具有与客户不同的地址空间,但我们将仍然使用Dll来帮助实现进程外服务器同客户之间的交流。如下图显示了DLL如何映射到其客户的地址空间。
此外的关键之外在于,当得到组件的一个接口指针时,连接客户和组件的唯一中介是接口的二进制结构。当客户查询组件的某个接口时,它所请求的实际上是具有特定格式的一块内存。当组件返回一个接口指针时,它告诉的实际上是些块内存的地址。
由于接口是在客户和组件都能够访问的内存中,因此这种情况实际上与当然和组件在同一个EXE文件中是相同的。
(vs2008)代码下载:http://www.box.net/shared/zhktgzojlj