首页 > 代码库 > VC6之dll编程总结

VC6之dll编程总结

转载请注明作者:小刘




           VC动态链接库

1. 什么是链接库

链接库分为动态链接库DLL和静态链接库LIB,两者都是基于共享代码的思想。

两者的区别在于:

.应用程序在编译时直接将静态库中的代码加入应用程序,在运行时则不再需要库,因而在运行时不能随意的卸载库。这不仅增加了代码量,还增加了系统内存开销。而另一方面,应用程序在运行时才将动态库载入内存,在运行期间库都存在,可以按照应用程序功能的需要随时载入库和卸载库。

.如果同时又多个应用程序运行时用到相同的链接库,那么静态链接库会多次载入内存,而动态链接库仅仅载入一次。

.在静态链接库中不能再包含其他动态链接库或静态链接库,动态链接库则相反

因而动态链接库的使用更加方便,更加广泛

静态链接库的一个例子:

进入VC---->新建--->工程--->win32 static library--->工程名字

“staticlibTest”--->在工程中新建lib.h

//lib.h 代码如下:

#ifndef  LIB_H

#define LIB_H

extern  “C”  int add(int x,int y);

#endif

--->在工程中新建lib.cpp

//lib.cpp代码如下:

#include “lib.h”

int add(int x,int y)

{

return x+y;

}

--->点击编译,则生成.lib静态链接库文件。至此创建完成。

新建应用程序对静态链接库进行调用:在之前的工作空间添加win32 console application “libCall”--->新建main.cpp

//main.cpp代码如下:

#include “stdio.h”

#include  “..//lib.h”

#pragma comment(lib, “staticlibTest.lib) //该语句把目标文件和静态库文件链接,若不用此语言,则--->工具--->选项--->目录--->library file--->加入静态库的路径即可

int main(int argc,char  *argv[])

{

printf(“2+3=%d\n”,add(2,3));

return 0;

}

编译运行即可。

关于dll调试:

方法一:因为库不能单独执行,当我们执行库的时候出现如图所示:

 

在对话框内输入应用程序路径就可以进行调试了。

方法二:将应用程序和库放在同一工作区,然后再应用程序调用库的位置设置断点,并将应用程序设置为活动工程即可进行调试。

2.什么是动态链接库

动态链接库,即dll后缀文件。可以理解为编程中所使用的一类“仓库”。

它提供给程序员可使用的变量,函数,类。例如windows提供的系统dll(其中包括windows API函数,例如MessageBox()).

Dll文件是已经编译好的代码,但是它不能独立运行,需要应用程序的调用,显然既然应用程序需要调用dll文件,所以在DLL文件中必须将在应用程序中用到的函数,变量,类进行导出。另一方面在调用时,应用程序必须要对该DLL提供的函数,变量,类进行导入才能实现这种调用。

2. 动态链接库的分类

动态链接库分为nonMFC dll, MFCreguler dll, MFCextension dll.

Non-MFC dll: 不采用MFC类库结构,直接用C写的DLL,其导出函数为标准的C接口,能被非MFCMFC编写的应用程序所调用;

MFC-regular dll:用MFC类库编写 ,包含一个继承自CWinApp的类,但其无消息循环,可以导出函数,变量,但不能导出类。其细分一般分为静态连接到MFC和动态连接到MFC

MFC-extension dll: 采用MFC的动态链接版本创建,可以导出从MFC继承的类,它只能被用MFC类库所编写的应用程序所调用。

3. dll导出函数的声明方式,以及dll导出函数的调用方式。

(1) dll导出函数的声明方式

方法(一):在dll头文件中,在导出函数声明类型和导出函数名之间加上关键字”_declspec(dllexport)”。

方法(二):采用模块定义文件(.def)声明,需要在dll工程中添加模块文件,其格式如下:

LIBRARY 库工程名称

EXPORTS 导出函数名

2DLL调用方式

方法(一)显式调用:隐式链接虽然实现较简单,但除了必须的*.dll文件外还需要DLL的*.h文件和*.lib文件,在那些只提供*.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,其能更加有效地使用内存,在编写大型应用程序时往往采用此方式。这种方法编程具体实现步骤如下:

使用Windows API函数Load Library或者MFC提供的

①AfxLoadLibrary将DLL模块映像到进程的内存空间,对DLL模块进行动态加载。

②使用GetProcAddress函数得到要调用DLL中的函数的指针。

③不用DLL时,用Free L 

方法(二)隐式调用:1.把你的youApp.DLL拷到你目标工程(需调用youApp.DLL的工程)的Debug目录下; 
2.把你的youApp.lib拷到你目标工程(需调用youApp.DLL的工程)目录下; 
3.把你的youApp.h(包含输出函数的定义)拷到你目标工程(需调用youApp.DLL的工程)目 
录下; 
4.打开你的目标工程选中工程,选择Visual   C++的Project主菜单的Settings菜单; 
5.执行第4步后,VC将会弹出一个对话框,在对话框的多页显示控件中选择Link页。然 
后在Object/library   modules输入框中输入:youApp.lib 
6.选择你的目标工程Head   Files加入:youApp.h文件; 
7.最后在你目标工程(*.cpp,需要调用DLL中的函数)中包含你的:#include   "youApp.h " 注:youApp是你DLL的工程名。

4. 关于DllMain

正如C程序一样,每个DLL必须有一个DllMain. 虽然DLL不能自己运行,可是Windows在加载DLL的时候,需要一个入口函数,就如同EXEmain一样,否则系统无法引用DLL。所以根据编写规范,Windows必须查找并执行DLL里的一个函数DllMain作为加载DLL的依据,这个函数不作为API导出,而是内部函数。DllMain函数使DLL得以保留在内存里,有的DLL里面没有DllMain函数,可是依然能使用,这是因为Windows在找不到DllMain的时候,会从其它运行库中找一个不做任何操作的缺省DllMain函数启动这个DLL使它能被载入,并不是说DLL可以放弃DllMain函数

 

参数意义:

① hModule参数:指向DLL本身的实例句柄;

ul_reason_for_call参数:指明了DLL被调用的原因,可以有以下4个取值:

1. DLL_PROCESS_ATTACH
DLL被进程 <<第一次>> 调用时,导致DllMain函数被调用,

同时ul_reason_for_call的值为DLL_PROCESS_ATTACH

如果同一个进程后来再次调用此DLL时,操作系统只会增加DLL的使用次数,

不会再用DLL_PROCESS_ATTACH调用DLLDllMain函数。

2.DLL_PROCESS_DETACH
DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的ul_reason_for_call值是DLL_PROCESS_DETACH
如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLLDllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。

3.DLL_THREAD_ATTACH
当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,

并用值DLL_THREAD_ATTACH调用DLLDllMain函数。 

新创建的线程负责执行这次的DLLDllMain函数,

只有当所有的DLL都处理完这一通知后,系统才允许线程开始执行它的线程函数。

4.DLL_THREAD_DETACH
如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。

 

5. non-MFC dll

将之前用静态链接库实现的例子用动态链接库实现如下:

新建--->Win32 Dynamic-Link Library--->名字 dynamicdlltest--->添加头文件lib.h

//代码如下:

#ifndef  LIB_H

#define LIB_H

Extern  “C” _declspec(dllexport) int add(int x,int y);

#endif

新建lib.cpp

//代码如下

#include “lib.h”

add(int x,int y)

{

Return x+y;

}

编译生成.dll文件

在工作空间新建应用程序win32 console application 名字为 lib.Call,在该工程内添加cpp文件libCall.cpp

(本例使用显示调用)

//libCall.cpp代码如下

#include “stdio.h”

Typedef int(*AddFun)(int x,int y);

Int main(int argc,char *argv[])

{

HINSTANCE hDLL;

AddFun Add;

hDLL=LoadLibrary(”dynamicdlltest.dll”);

if(hDLL!=0)

{

Add=(AddFun)GetProcAddress(hDLL,”add”);

If(Add!=0)

Printf(“2+3=%d\n”,Add(2+3));

}

FreeLibrary(hDLL);

Return 0;

}

Return 0;

}

分析上述代码,dllTest工程中的lib.cpp文件与第2节静态链接库版本完全相同,不同在于lib.h对函数add的声明前面添加了__declspec(dllexport)语句。这个语句的含义是声明函数addDLL的导出函数。

6.MFC-regular dll

(1)内部使用MFC,与应用程序接口不能使用MFC

(2)不能导出MFC

      MFC-regular dll分为两类:

(1) 静态链接到MFC-regular dll

静态链接到MFC的规则DLLMFC库(包括MFC扩展DLL)静态链接,将MFC库的代码直接生成在.dll文件中。在调用这种DLL的接口时,MFC使用DLL的资源。因此,在静态链接到MFC的规则DLL中不需要进行模块状态的切换。 使用这种方法生成的规则DLL其程序较大,也可能包含重复的代码。

(2)动态链接到MFC的规则DLL 

动态链接到MFC的规则DLL可以和使用它的可执行文件同时动态链接到MFCDLL和任何MFC扩展DLL。在使用了MFC共享库的时候,默认情况下,MFC使用主应用程序的资源句柄来加载资源模板。这样,当DLL和应用程序中存在相同ID的资源时(即所谓的资源重复问题),系统可能不能获得正确的资源。因此,对于共享MFCDLL的规则DLL,我们必须进行模块切换以使得MFC能够找到正确的资源模板。

      

一个例子:

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

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

//client1.cpp

#include<stdio.h>

#include<iostream>

extern "C" _declspec(dllexport)double Ex21cSquareRoot(double d)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

TRACE("Enter EX21cSquareRoot/n");

if(d>=0.0)

{

return sqrt(d);

}

AfxMessageBox("Can‘t take sqare root of a negative number.");

return 0.0;

}

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

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

//client1.cpp

#include<stdio.h>

#include<iostream>

 

extern "C" _declspec(dllimport) double Ex21cSquareRoot(double d);

int main()

{

printf("please input a number:");

double dInput,dOutput;

scanf("%lf",&dInput);

dOutput=Ex21cSquareRoot(dInput);

printf("%lf",dOutput);

system("pause");

return 0;

}

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

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

7.MFC-extension dll

一个例子说明MFC-extension dll:

a) 首先新建--->mfc dll--->mfc extension dll--->在工程中添加类的头文件和cpp文件,内容如下:

//MyClass.h

Class _declspec(dllexport) CMyClass

{

public:

    CMyClass();

    void SetValue(int n);

    void GetValue();

private:

    int _a;

};

/MyClass.cpp

#include "StdAfx.h"

#include "MyClass.h"

CMyClass::CMyClass()

{

      printf("CMyClass::CMyClass()/n");

}

 

void CMyClass::SetValue(int n)

{

      printf("SetValue(int n)/n");

      -a=n;

}

void CMyClass::GetValue()

{

      printf("GetValue()/n");

      printf("_a=%d/n",_a);

}

b)新建测试程序client(win32 console application) ,添加代码:

include <stdio.h>

#include <iostream>

$include "MyClass.h"

int main()

{

     printf("main()/n");

     int nVal=100;

 

      CMyClass mc;

      mc.SetValue(nVal);

      mc.GetValue();

 

      system("pause");

      return 0;

}

d)将dll文件拷贝到测试程序debug目录下,将lib文件和类头文件拷贝到测试程序的目录下。

e)在工程---.>setting--->Link--->module/library中添加.Lib文件。

编译运行即可成功。

8.总结

在大多数情况下我们使用mfc-regular dll创建dll,导出函数使用关键字_declspec(dllexport),而应用程序使用动态调用方式。




转载请注明作者:小刘

VC6之dll编程总结