MFC Regular DLL and Usage

MFC 正规DLL


2.5  MFC 正规DLL ——CWinApp 派生类

当用 AppWizard 产生正规 DLL 时, DllMain 函数将出现在框架内,并且我们会得到一个 CWinApp 的派生类 ( 和一个该类的全局对象 ) ,就像 EXE 程序的情形一样。我们可以通过重载 CWinApp::InitInstance 和 CWinApp::ExitInstance 函数获得控制。然而大多数情况下,我们不用重载这两函数。我们只要编写 C 函数,然后用 __declspec(dllexport) 修饰符导出这些函数 ( 或者在工程的 DEF 文件里加入函数入口 ) 即可。


2.6  使用AFX_MANAGE_STATE 宏

当 mfc42.dll 作为进程的一部分被装入时,它把数据存放在一些可靠的全局变量里。如果我们从一个 MFC 程序或扩展 DLL 中调用 MFC 函数,则 mfc42.dll 会知道如何代表调用进程去设置这些全局变量。然而,如果我们从一个正规 MFC DLL 中调用进入 mfc42.dll ,则全局变量并不同步,其后果不可预知。为了解决这个问题,请在正规 DLL 所有导出函数的开始处,插入下面的代码行:

AFX_MANAGE_STATE(AfxGetStaticModuleState());

如果 MFC 代码被静态连接,则该宏不会有任何影响。


2.7   如何使用MFC 正规DLL

u      创建 MFC 正规 DLL

a)         运行 AppWizard ,选择 MFC AppWizard(dll)->Regular DLL Using Shared MFC DLL ,工程名为 ex21c 。

b)         在 ex21c.cpp 文件中加入导入的函数代码:

+ expand sourceview plaincopy to clipboardprint?
// The one and only CEx21cApp object  
CEx21cApp theApp;  
extern "C" __declspec(dllexport) double Ex21cSquareRoot(double d)  
{  
    AFX_MANAGE_STATE(AfxGetStaticModuleState()); // Note!  
    TRACE("Entering EX21cSquareRoot/n");  
    if (d>=0.0)  
    {  
        return sqrt(d); // 添加math.h头文件  
    }  
    AfxMessageBox("Can't take square root of a negative number.");  
    return 0.0;  

// The one and only CEx21cApp object
CEx21cApp theApp;
extern "C" __declspec(dllexport) double Ex21cSquareRoot(double d)
{
 AFX_MANAGE_STATE(AfxGetStaticModuleState()); // Note!
 TRACE("Entering EX21cSquareRoot/n");
 if (d>=0.0)
 {
  return sqrt(d); // 添加math.h头文件
 }
 AfxMessageBox("Can't take square root of a negative number.");
 return 0.0;
}

c)         编译工程,得到 ex21c.dll 和 ex21c.lib 两个文件。

u      测试 DLL 的客户程序

a)         创建一个空白的 Win32 控制台程序,工程名为 client ,添加如下测试代码:

+ expand sourceview plaincopy to clipboardprint?
#include   
#include   
//声明Ex21cSquareRoot为一个导入函数  
extern "C" __declspec(dllimport) double Ex21cSquareRoot(double d);  
int main()  
{  
    printf("input a number:");  
    double dInput,dOutput;  
    scanf("%lf",&dInput);  
    //测试使用导出的函数  
    dOutput=Ex21cSquareRoot(dInput);  
    printf("sqrt(%lf)=%lf/n",dInput,dOutput);  
      
    system("pause");  
    return 0;  

#include
#include
//声明Ex21cSquareRoot为一个导入函数
extern "C" __declspec(dllimport) double Ex21cSquareRoot(double d);
int main()
{
 printf("input a number:");
 double dInput,dOutput;
 scanf("%lf",&dInput);
 //测试使用导出的函数
 dOutput=Ex21cSquareRoot(dInput);
 printf("sqrt(%lf)=%lf/n",dInput,dOutput);
 
 system("pause");
 return 0;
}

b)         将 ex21c.dll 和 ex21c.lib 这两个文件拷贝到 client 工程目录中,并且在 Project->Settings->Link ,在 Object/library modules 中添加 ex21c.lib( 多个 lib 用空格分开 ) 。

c)         编译并测试,输入 2 ,将输出如下结果,即可以成功地调用正规 DLL 导出的函数。

input a number:2

sqrt(2.000000)=1.414214

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/delphiwcdj/archive/2010/01/06/5144940.aspx

 

使用 MFC 可以生成两类 DLL : MFC 扩展 DLL 和常规 DLL 。常规 DLL 又分为两类:动态链接 (dynamically linked) 和静态链接 (statically linked) 。关于这两种 DLL 的区别和简单的用法已经在前面的博文中总结过,本文主要针对以下三个方面进一步地讨论关于这两类 DLL 的用法和注意事项。

构建 DLL

在客户程序中使用 DLL

DLL 与 MFC 和编译器的兼容相关的一些常见问题

 

From: CodeGuru Visual C++ 编程精粹(wcdj借于图书馆发现此书翻译的不是很好,将主要精髓整理如下)

 

--------------------------------------------------------------------------------
 

n        构建 DLL

通过 AppWizard 可以生成一个不完成任何实质性任务的 DLL 。新的 DLL 能编译,但由于它没有导出任何类或函数,所以仍然不能发挥作用。所以我们的任务就是:

²          给 DLL 添加功能,使之发挥作用;

²          修改客户应用程序来运用这个 DLL ;

我们可以完成下面的工作:

1)          导出类

一旦结束了 AppWizard ,然后就可以从另一个项目添加 .cpp 和 .h ,来给 DLL 添加类,或者在当前项目中从头开始创建它们。为了导出类,在类声明中添加“ __declspec(dllexport) ”,如下所示:

view plaincopy to clipboardprint?
class __declspec(dllexport) CMyClass  
{     
    // 在此放置类声明    
}; 
class __declspec(dllexport) CMyClass

 // 在此放置类声明 
};

如果创建的是 MFC 扩展 DLL ,可使用宏 AFX_EXT_CLASS :

view plaincopy to clipboardprint?
class AFX_EXT_CLASS CMyClass  
{     
    // 在此放置类声明    
};     
class AFX_EXT_CLASS CMyClass

 // 在此放置类声明 
};    

还有其他途径来导出类,但以上办法最容易。

2)          导出变量、导出常量和导出对象

如果不导出整个类,也可以让 DLL 导出一个变量、常量或对象。导出一个变量或常量,按如下方式声明:

 

view plaincopy to clipboardprint?
__declspec(dllexport) int MyInt;  
__declspec(dllexport) extern const COLORREF MyColor=RGB(50,50,50); 
__declspec(dllexport) int MyInt;
__declspec(dllexport) extern const COLORREF MyColor=RGB(50,50,50);

需要导出一个常量时,必须使用定义符 extern 。否则,会得到链接错误。

可以按相同的方式声明和导出一个类对象:

view plaincopy to clipboardprint?
__declspec(dllexport) CRect MyRect(30,30,300,300); 
__declspec(dllexport) CRect MyRect(30,30,300,300);

注意: 如果客户程序识别这个类而且有自己的头文件,则只能导出一个类对象。如果在 DLL 中创建一个新类,客户程序不借助头文件,就不能识别它。

当你导出一个变量或对象时,载入此 DLL 的每个客户程序都将获得自己的拷贝。于是,如果两个不同的应用程序使用同一个 DLL ,一个应用程序所做的修改不会影响另一个应用程序。

务必记住: 只能导出处于 DLL 中具有全局作用域的对象和变量。当局部对象和变量越出作用域时,它们就会消亡。因此,如果 DLL 包含如下代码,就不会正常工作:

view plaincopy to clipboardprint?
MyFunction()  
{     
    __declspec(dllexport) CSomeClass SomeObject;      
    __declspec(dllexport) int SomeInt;    

MyFunction()

 __declspec(dllexport) CSomeClass SomeObject; 
 __declspec(dllexport) int SomeInt; 
}

一旦对象和变量越出作用域,它们就不复存在了。

3)          导出函数

导出函数与导出对象或变量相似。只要将“ __declspec(dllexport) ”放到函数原型开头:

view plaincopy to clipboardprint?
__declspec(dllexport) int SomeFunction(int); 
__declspec(dllexport) int SomeFunction(int);

如果创建的是常规 DLL ,它将提供 C 写成的客户程序使用,则函数声明应如下所示:

view plaincopy to clipboardprint?
extern "C" __declspec(dllexport) int SomeFunction(int); 
extern "C" __declspec(dllexport) int SomeFunction(int);

注意: 如果创建的是一个动态链接到 MFC 代码库 DLL 的常规 DLL ,则必须插入宏 AFX_MANAGE_STATE ,作为导出函数的首行。因此,函数定义应如下:

view plaincopy to clipboardprint?
extern "C" __declspec(dllexport) int AddFive(int x)  
{     
    AFX_MANAGE_STATE(AfxGetStaticModuleState());      
    return x+5;   

extern "C" __declspec(dllexport) int AddFive(int x)

 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 
 return x+5; 
}
 

 

 

说明: 在每个常规 DLL 中,这样做没有危害。如果将你的 DLL 切换到静态链接,其中的宏只是无效而已。

注意: 只有 MFC 扩展 DLL 导出的函数才可以让参数和返回值使用 MFC 数据类型。

4)          导出指针

导出指针与导出一个变量或对象的方式相同,比如:

view plaincopy to clipboardprint?
// 导出未初始化的指针  
__declspec(dllexport) int* SomeInt; 
// 导出未初始化的指针
__declspec(dllexport) int* SomeInt;

view plaincopy to clipboardprint?
// 导出一个初始化的指针  
__declspec(dllexport) CSomeClass* SomePointer=new CSomeClass; 
// 导出一个初始化的指针
__declspec(dllexport) CSomeClass* SomePointer=new CSomeClass;

当然,如果声明和初始化指针,需要找到删除它的地方。

u      在扩展 DLL 中,有一个 DllMain() 函数。当客户程序附加到 DLL 上,而且再次分离时,会调用这个函数。因此,这是一种在扩展 DLL 中处理指针的可能方式:

+ expand sourceview plaincopy to clipboardprint?
#include "SomeClass.h"  
__declspec(dllexport) CSomeClass* SomePointer=new CSomeClass;  
DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)  
{     
    if (dwReason==DLL_PROCESS_ATTACH)         
    {         
        // 附加时        
    }     
    else if (dwReason==DLL_PROCESS_DETACH)        
    {         
        // 分离时        
        delete SomePointer;       
    }     

#include "SomeClass.h"
__declspec(dllexport) CSomeClass* SomePointer=new CSomeClass;
DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)

 if (dwReason==DLL_PROCESS_ATTACH)  
 {  
  // 附加时  
 } 
 else if (dwReason==DLL_PROCESS_DETACH)  
 {  
  // 分离时  
  delete SomePointer;  
 } 
}

u      在常规 DLL 中,看起来和普通 MFC 可执行文件更像。它有一个从 CwinApp 派生的对象来处理 DLL 的开关。可以使用 Class Wizard 添加 InitInstance() 和 ExitInstance() 函数。

+ expand sourceview plaincopy to clipboardprint?
int CMyDllApp::ExitInstance()  
{     
    delete SomePointer;   
    return CWinApp::ExitInstance();   

int CMyDllApp::ExitInstance()

 delete SomePointer; 
 return CWinApp::ExitInstance(); 
}
 

 

 

 在客户程序中使用 DLL

DLL 不能在自身上运行。它需要有一个客户应用程序载入它,并使用它的接口。

编译 DLL 时,编译器创建两个重要文件: .dll 文件和 .lib 文件。客户应用程序需要这两个文件。必须将它们复制到客户应用程序的项目文件夹中。

注意: 在 debug 模式下通过生成创建的 .dll 文件和 .lib 文件与在 release 模式下创建的文件是不同的。在 debug 模式下生成的客户程序时,需要 .dll 和 .lib 的 debug 版本;而在 release 模式下生成的客户程序时,则需要 .dll 和 .lib 的 release 版本。

除了 .dll 和 .lib 文件外,客户程序还需要针对导出内容 ( 导出类、函数、对象和变量 ) 的头文件。导出时,在声明中添加“ __declspec(dllexport) ”,而在导入时,需要添加“ __declspec(dllimport) ”。

记住: 如果在 DLL 中使用定义符 extern “C” ,也必须在客户程序中使用它。

为了导入整个类,必须复制整个 .h 头文件到客户程序。 DLL 和客户程序于是具有导出类的相同的头文件,不同的是,在 DLL 中是“ class __declspec(dllexport) CMyClass ”,在客户程序文件中是“ class __declspec(dllimport) CMyClass ”。如果创建的是 MFC 扩展 DLL ,在两个位置均为“ class AFX_EXT_CLASS CMyClass ”。

说明: 一旦完成了客户程序的构建,就准备将它移交给实际用户,给予他们的应该是发行版本的可执行文件以及发行版本的 DLL ,不必给予用户 .lib 文件。 .dll 文件和 .exe 文件放在同一个目录中,或者将 .dll 放在 Windows 的 System 目录下。

警告: 由于 DLL 存在一些严重的缺陷,因此 COM 和 ATL 才应运而生。 DLL 存在的主要问题有两个: (1) 通过某种版本编译器构建的 DLL 可能与另外的编译器构建的客户程序不兼容。 (2) 修改 DLL 时,可能必须重新编译客户程序,即使没有更改客户程序中的代码时也是如此。可能仍然要拷入新的 .dll 和 .lib 文件,再进行重新的编译。

在某些情况下,可以通过一些途径来避免这个问题。下文将介绍两种方法。

 

n          DLL 与 MFC 和编译器的兼容相关的一些常见问题

DLL 是任何 MFC 程序员有用的工具,但是它们存在许多重要的限制。

Ø           MFC 问题 。 DLL 必须具备正确版本的 MFC 代码库。

Ø           编译器不兼容性问题 。用一种版本的编译器来构建 DLL ,但用另一种版本的编译器构建的 App 来调用它,这时就有可能会出现问题。

Ø           重新编译问题 。比如现在构建的 DLL 导出一个名叫 CMyClass 的类,为 CMyClass 提供一个头文件的拷贝,供客户应用程序使用,并假设 CMyClass 对象的大小为 30 字节。这时,假如修改 DLL 来更改 CMyClass ,为其增加一个 int 型的私有成员变量,因此,当创建 CMyClass 类型的对象时,大小变为 34 个字节。将这个新的 DLL 发送到用户,并通知他们替换掉旧的 DLL ,于是就产生了一个问题:客户 App 期望的是 30 字节大小的对象,但是新的 DLL 创建的是 34 个字节大小的对象,这时客户 App 就会出错。

解决方案是什么?

如果上述这些问题存在完美的解决方案的话,这也许就是 COM 。但是掌握 COM 或 ATL 需要花费大量的时间和精力,运用 DLL 相对容易得多。下面介绍两种通过修改 DLL 来解决上述问题的办法:

1)          使用接口类

接口类的目标是分离需要导出的类与该类的接口。完成的途径是创建第二个类,它将作为需要导出的类的接口。于是,即使在导出类改变时,也不必重新编译客户的 App ,因为接口类保持不变。

创建一个单独的接口类,只要接口类 ( 大小 ) 不改变,就不必重新编译。但是采用这种方法仍然存在两个相对较小的问题。 (1) 首先,对于 CMyClass 中的每个公有函数和成员变量,必须在 CMyInterface 中创建一个对应的函数或变量。如果 CMyClass 中有上百个函数和变量,创建过程就会非常冗长且很容易出错。 (2) 这样做导致必须完成的过程数量增加了,客户的 App 不再直接调用 CMyClass ,相反,它调用一个调用 CMyClass 的 CMyInterface 函数,如果这是一个被客户 App 经常调用的函数,那么额外的处理时间会激增。

2)          使用创建和销毁导出类的静态函数

    避免必须重新编译的另一条途径是使用静态函数来创建和销毁导出类。在创建导出类时,添加两个公有静态函数 CreateMe() 和 DestoryMe() ,使用这种技术也可以修改 CMyClass 的大小而不必重新编译客户的 App 。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/delphiwcdj/archive/2010/01/12/5182966.aspx

你可能感兴趣的:(VC技巧)