首页 > 代码库 > 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接口,能被非MFC或MFC编写的应用程序所调用;
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 导出函数名
(2)DLL调用方式
方法(一)显式调用:隐式链接虽然实现较简单,但除了必须的*.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的时候,需要一个入口函数,就如同EXE的main一样,否则系统无法引用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调用DLL的DllMain函数。
2.DLL_PROCESS_DETACH:
当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的ul_reason_for_call值是DLL_PROCESS_DETACH。
★如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。
3.DLL_THREAD_ATTACH:
当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,
并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。
新创建的线程负责执行这次的DLL的DllMain函数,
只有当所有的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)语句。这个语句的含义是声明函数add为DLL的导出函数。
6.MFC-regular dll
(1)内部使用MFC,与应用程序接口不能使用MFC。
(2)不能导出MFC类
MFC-regular dll分为两类:
(1) 静态链接到MFC-regular dll
静态链接到MFC的规则DLL与MFC库(包括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编程总结