(1)DLL的构成

      每个应用程序都有一个入口函数WinMain,而每个DLL也有一个入口函数DllMain。DLL跟应用程序一样都含有资源、数据段和代码段。DLL跟应用程序的差别主要是DLL有符号输入表和符号输出表,以方便应用程序调用DLL中的函数。

      因为DLL是由应用程序加载的,所以它本身没有虚拟的内存地址空间,它使用的是加载它的应用程序的地址空间。这种加载分为隐式和显式两种方式。将在后面叙述。

(2)创建MFC DLL

      A,VC6新建一个工程MFCDLL,类型为MFC  AppWizard(dll),具体选项为Regular DLL using shared MFC DLL。我们在MFCDLL.cpp的末尾添加实现代码如下:

 int sum(int a,int b)
{
        return a+b;
}
int sub(int a,int b)
{
        return a-b;
}

      B,使用时,动态链接库的函数申明是写在def文件中,编译器根据这个文件的函数申明来生成lib文件和dll文件。我们只需要在MFCDLL.def文件的末尾添加代码如下:

sum  @1;
sub   @2;   //后面表示函数的序号

      以上两步完成后,即可编译生成DLL了,总共生成MFCDLL.dll和MFCDLL.lib两个文件。DLL本身不能运行,所以还需要一个测试程序。

      C,隐式加载MFC DLL并测试

      用VC创建一个对话框工程testDlg,加上“测试加法”“测试减法”两个按钮。隐式加载需要在testDlg头文件头部添加代码申明:

#pragma comment(lib,"MFCDLL")
int sum(int a,int b);
int sub(int a,int b);

编译时,需要将MFCDLL.lib拷贝到test工程目录下(dll可有可没有),否则会有错误提示:cannot open file "MFCDLL.lib"。在保证编译成功后,直接点击运行EXE也是无法成功的,会提示无法找到MFCDLL.dll文件。这个时候需要将MFCDLL.dll拷贝到跟EXE同层的目录。

      给对话框按钮添加的代码如下:

void CDLLtestDlg::OnSum()
{
      int value = sum(10,4);
      CString string;
      string.Format("sum value is %d",value);
      MessageBox(string);
}

void CDLLtestDlg::OnSub()
{
      int value = sub(10,4);
      CString string;
      string.Format("sub value is %d",value);
      MessageBox(string);
}

      D,显式加载MFC DLL并测试

      用VC创建一个对话框,放置如上的“测试加法”和“测试减法”的按钮。采用显式加载DLL法,需要添加一个句柄来存储加载DLL的返回值,还要定义带参数的指针类型存储获得的两个函数地址typedef int (*PFUNC)(int,int);,在对话框头文件的类中如下:

public:
 HMODULE hDllLib;
 PFUNC m_pSum;
 PFUNC m_pSub;

       在对话框的初始化中添加DLL加载过程:

 hDllLib = LoadLibrary("MFCDLL.dll");    
 if(hDllLib == NULL)
 {
       MessageBox("dll load error");
       return FALSE;
 }

 m_pSum = (PFUNC)GetProcAddress(hDllLib,"sum"); //强制类型转换一定要加,GetProcAddress默认返回不带参数的函数指针
 m_pSub = (PFUNC)GetProcAddress(hDllLib,"sub");

     为了完整性,在对话框的WM_DESTORY消息的响应函数OnDestroy()中添加:

FreeLibrary(hDllLib);

     这样,两个按钮的操作如下:

void CMFCDLLtest2Dlg::OnSum()
{

      int value = m_pSum(10,4);
      CString string;
      string.Format("sum value is %d",value);
      MessageBox(string);
}

void CMFCDLLtest2Dlg::OnSub()
{
      int value = m_pSub(10,4);
      CString string;
      string.Format("sub value is %d",value);
      MessageBox(string);
}

动态加载不需要LIB文件,只要把DLL文件放在EXE同目录下即可运行。

(3)WIN32 DLL说明

      WIN32 DLL与MFC DLL的差别在于它的DllMain不是打包的,是可见的。它同样其中定义函数实体,不过在头文件中都需要申明(申明语句是特定的,如extern "C" __declspec( dllexport ) )。缺省的入口函数如下:

BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
      )
{
       switch (ul_reason_for_call)
       {
               case DLL_PROCESS_ATTACH:    //进程被创建
               case DLL_THREAD_ATTACH:      //线程被创建
               case DLL_THREAD_DETACH:      //线程被终止
               case DLL_PROCESS_DETACH:   //进程被终止
               break;
       }
       return TRUE;
}

DllMain负责初始化和结束工作。

hModule是动态库被调用时所传递过来的一个指向自己的句柄;

ul_reason_for_call是一个说明动态库被调用原因的标志。当进程或者线程装载、卸载动态链接库的时候,操作系统便调用入口函数。DLL_PROCESS_ATTACH:当一个DLL被首次载入进程地址空间时,系统会调用该DLL的DLLMain函数,传递的参数fdwReason为DLL_PROCESS_ATTACH。这种情况只有在首次映射DLL时才发生。

DLL_PROCESS_DETACH:当DLL从进程的地址空间解除映射时,参数fdwReason被传递的值为DLL_PROCESS_DETACH。当DLL处理DLL_PROCESS_DETACH时,DLL应该处理与进程相关的清理操作。

lpReserved是一个被系统保留的参数

      理解以上之后,就需要考虑输出函数的实现方法。

(4)创建WIN32 DLL

      A,用VC6创建一个WIN32  DLL工程WIN32DLL,可以看出它默认用WIN32DLL_API这个符号输出一个变量nWIN32DLL 和一个函数fnWIN32DLL,而WIN32DLL_API相当于__declspec(dllexport)。类似,在对话框头文件中添加输出申明,同时需要特别关注的是在申明和定义前都要加extern "C"关键字,如果不加在隐式调用和显式调用时会出现异常状况

extern "C" WIN32DLL_API int sum(int a,int b);
extern "C" WIN32DLL_API int sub(int a,int b);

      在对话框C文件完成函数体:

extern "C" WIN32DLL_API int sum(int a,int b)
{
      return a+b;
}

extern "C" WIN32DLL_API int sub(int a,int b)
{
      return a-b;
}

编译就生成对应的LIB和DLL。

      B,隐式加载WIN32 DLL并测试

利用第(2)步同样的隐式调用测试程序,就可以测试这个DLL。有点差别,就是头文件前也要加extern "C",否则编译错误。如下:

#pragma comment(lib,"win32")
extern "C" int sum(int a,int b);
extern "C" int sub(int a,int b);

过程是讲LIB拷贝至测试程序工程目录下,将DLL拷贝至EXE同层目录下。

      C,显式加载WIN32 DLL并测试

利用第(2)步同样的显式调用程序即可。如果win32 dll中没有加extern "C" 关键字,则取函数地址时异常。