windows中动态链接库的创建

来源:互联网 发布:mac电脑输入法中英切换 编辑:程序博客网 时间:2024/05/16 03:15

windows中动态链接库的创建

创建动态链接库工程:我使用的vs2010

1. 创建win32项目,(我们命名为DllDemo)

2.在应用程序类型中选择DLL

动态链接库的入口点DllMain (在dllmain.cpp文件中)DllMain函数。库的入口函数仅供操作系统使用,Windows在库装载、卸载、进程中线程创建和结束时调用入口函数,以便动态链接库可以采取相应的动作。DllMain由工具自动生成,我们不用去修改它。

动态链接库中的函数:DLL能够定义两种函数,导出函数和内部函数。导出函数可以被其他模块调用,也可以被定义这个函数的模块调用,而内部函数只能被定义这个函数的模块调用。

动态链接库的主要功能就是向外导出函数,供进程中其他模块使用

在DllMain.cpp中定义导出函数:我们创建一个函数 void ExportFunc(LPCTSTR pszContent)

注意:函数定义完后只能在本工程中使用,要想将函数导出供其他模块调用,最简单的方法是在DllDemo.h文件中进行声明。。

声明如下:

#ifdef DLLDEMO_EXPORTS#define DLLDEMO_API __declspec(dllexport)#else#define DLLDEMO_API __declspec(dllimport)#endif//声明要导出测函数DLLDEMO_API void ExportFunc(LPCTSTR pszContent);

也可以这样声明导出函数:直接在返回类型前面加上_declspec(dllexport)就可以将该函数声明为导出函数

_declspec(dllexport) int Add(int a, int b){return b + a;}_declspec(dllexport) int Sub(int a, int b){return a - b;}


 


使用导出函数

调用DLL中的导出函数有两种方法:

(1). 装载期间动态链接:模块可以像调用本地函数一样调用从其他模块导出的函数(API就是这样调用的)。装载期间链接必须使用DLL的导入库(.lib文件),它为系统提供了加载这个DLL和定位DLL中的导出函数所需的信息。

实现装载期间动态链接需要将DllDemo.h、DllDemo.lib、DllDemo.dll三个文件拷贝到另外一个新建的工程中。

(2).运行期间动态链接:模块可以使用LoadLibrary或者LoadLibraryEx函数在运行期间加载这个DLL。DLL被加载之后,加载模块调用GetProcAddress函数取得DLL导出函数的地址,然后通过函数地址调用DLL中的函数。

为了实现运行期间动态的导出函数,一般要在原先创建动态链接库的工程中建立一个DEF文件来指定要导出的函数。例如添加一个DllDemo.def文件。

例子: DllDemo工程:

dllmain.cpp

// dllmain.cpp : 定义 DLL 应用程序的入口点。#include "stdafx.h"#include <stdio.h>#include "DllDemo.h"#include <stdexcept>using namespace std;HMODULE g_hModule;BOOL APIENTRY DllMain( HMODULE hModule,                       DWORD  ul_reason_for_call,                       LPVOID lpReserved ){switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:g_hModule = (HMODULE)hModule;break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;}//导出函数void ExportFunc(LPCTSTR pszContent){char sz[MAX_PATH];::GetModuleFileNameA(g_hModule, sz, MAX_PATH);::MessageBox(NULL, pszContent, strrchr(sz, '\\')+1, MB_OK);}namespace MathFuncs{double MyMathFuncs::Add(double a, double b){return a + b;}double MyMathFuncs::Sub(double a, double b){return a - b;}}


DllDemo.h文件

#ifdef DLLDEMO_EXPORTS#define DLLDEMO_API __declspec(dllexport)#else#define DLLDEMO_API __declspec(dllimport)#endif//声明要导出测函数DLLDEMO_API void ExportFunc(LPCTSTR pszContent);namespace MathFuncs{class MyMathFuncs{public://return a + bstatic DLLDEMO_API double Add(double a, double b);//return a - bstatic DLLDEMO_API double Sub(double a, double b);};}

另一个工程: 测试前面的动态链接库

#include <windows.h>#include "DllDemo.h"#pragma comment(lib, "DllDemo.lib")int main(){ExportFunc("大家好");return 0;}


如何使用动态链接库:

引入动态链接库:#pragma comment(lib, "Dll2013.lib")

声明外部函数:利用extern来声明,

extern int Add(int a, int b);extern int Sub(int a, int b);

或者直接利用_declspec(dllimport)来直接声明函数是从动态链接库中引入的。

_declspec(dllimport) int Add(int a, int b);_declspec(dllimport) int Sub(int a, int b);

_declspec(dllimport) 与 extern相比,使用_declspec(dllimport)标识符声明外部函数时,它将告诉编译器该函数是从动态链接库中引入的,编译器可以运行效率更高的代码。因此,如果调用的函数来自于动态链接库,应该采用这种方式声明外部函数。

dumpbin命令:

在VS2010中,通过“工具->visual studio 2010命令提示”就可以打开命令窗口。输入dumpbin可以查看该命令的使用方法。

输入dumpbin -exports [动态链接库的路径]就可以查看动态链接库的信息。

输入dumpbin -imports [应用程序的路径] 可以查看应用程序的导入信息。

 

从DLL中导出C++类:

不仅可以从动态链接库中导出函数,还可以导出一个C++类。为了导出一个C++类,我们可以在DLL的头文件中添加类似下面的代码

class DLL2013_API Point{public:void Output(int x, int y);};

然后再DLL的cpp文件中添加Point类对应的函数实现

void Point::Output(int x, int y){HWND hwnd = ::GetForegroundWindow();HDC hdc = ::GetDC(hwnd);char buf[20];memset(buf, 0, 20);sprintf(buf, "x=%d, y=%d", x, y);::TextOut(hdc, 0, 0, buf, strlen(buf));::ReleaseDC(hwnd, hdc);}

完成上面两步,就可以将Point类导出了。

我们也可以不导出整个类,而只是导出该类中的某些函数。为了只导出类中的部分函数,我们要将类前面使用的DLL2013_API注释掉,然后在导出函数前面放置DLL2013_API宏。这样就只有使用了DLL2013_API宏的函数才能被导出。

class /*DLL2013_API*/ Point{public:void DLL2013_API Output(int x, int y);void Test();};

上面的代码我们导出了函数Output,而函数Test没有导出。可以用工具dumpbin来查看。另外,在导出类的成员函数时需要注意,该函数必须具有public类型的访问权限,否则,该函数即使能够被导出,也不能被其它程序访问。

解决名字改编问题:

我们知道,C++编译器在生成DLL时,会对导出函数进行名字改编,并且不同的编译器使用的改编规则不一样,因此改编后的名字是不一样的。这样,如果利用不同的编译器分别生成DLL和访问DLL的客户端程序的话,后者在访问该DLL的导出函数时就会出现问题。例如C++编译器和C编译器,C编译器不会对名字进行改编。

为了解决上面的问题,我们希望动态链接库文件在编译时,导出函数的名称不要发生改变。为了实现这一目的,在定义导出函数是,需要加上限定符:extern "C"。注意双引号中的字母C一定要大写。看下面的例子:

在Dll的头文件(我的头文件是Dll2013.h)中添加如下代码:

#ifdef DLL2013_API#else#define DLL2013_API extern "C" _declspec(dllimport)#endifDLL2013_API int Add(int a, int b);DLL2013_API int Sub(int a, int b);

在Dll的源文件(我的源文件是dllmain.cpp)中添加如下代码:

#define DLL2013_API extern "C" _declspec(dllexport)#include <Windows.h>#include <stdio.h>#include "stdafx.h"#include "Dll2013.h"// dllmain.cpp : 定义 DLL 应用程序的入口点。BOOL APIENTRY DllMain( HMODULE 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;}int Add(int a, int b){return b + a;}int Sub(int a, int b){return a - b;}


重新生成DLL2013.dll之后,用dumpbin命令查看导出函数信息,可以发现DLL导出的Add和Sun函数的名字没有发生改编。

需要注意的是,extern "C" 可以解决C++和C语言之间相互调用是函数命名的问题,但是这种方法有一个缺陷,就是不能用于导出一个类的成员函数,只能用于导出全局函数这种情况。另外,如果导出函数的调用约定发生了改变,那么即使使用了限定符:extern "C" ,该函数的名字仍会发生改编

例如:上面的程序中,使导出函数Add和Sub使用标准调用约定(标准调用约定就是WINAPI约定,也就是pascal调用约定,这种约定方式与C调用约定不一样):即在声明这些函数时添加_stdcall关键字。

在Dll2013.h中:

DLL2013_API int _stdcall Add(int a, int b);DLL2013_API int _stdcall Sub(int a, int b);

在dllmain.cpp中:

int _stdcall Add(int a, int b){return b + a;}int _stdcall Sub(int a, int b){return a - b;}

上面的代码中,如果没有添加_stdcall关键字,那么函数的调用就是C调用约定。

还有一种方法来解决名字改编问题:使用模块定义文件(DEF)

LIBRARY DLL2013EXPORTAddSub

其中LIBRARY语句用来指定动态链接库的内部名称,该名称与生成的动态链接库的名称一定要匹配,不过这句代码并不是必须的。

EXPORT语句的作用是表明DLL将要导出的函数,以及为这些函数指定的符号名。当连接器在链接时,会分析这个DEF文件,当发现在EXPORT语句下面有Add和Sub这两个符号名,并且它们与源文件中定义的Add和Sub函数的名字一致时,它就会以Add和Su这两个符号导出相应的函数,如果不一致,就按照下述语法指定导出函数: entryname = internalname 。
其中entername是导出的符号名,interalname是DLL中将要导出的函数的名字。


显式加载DLL

使用动态方式加载动态链接库时,需要用到LoadLibrary函数,该函数的作用是将指定的可执行模块映射到调用进程的地址空间。Loadlibrary函数不仅能够加载DLL(.dll),还可以加载可执行模块(.exe)。

当获取到动态链接库的句柄后,接下来就是要获取动态链接库中导出函数的地址,这可以通过GetProcAddress函数来实现。

使用动态加载方式要注意的几个地方:

1.创建DLL时,一定要解决名字改变问题,例如可以利用extern "C" ,是编译器不对名字进行改编。

2.动态加载DLL时,客户端程序不再需要包含导出函数声明的头文件(.h)和引入库文件(.lib),只需要.dll文件即可

3.当采用动态方式加载DLL时,在客户端将不能看到调用该DLL的输入信息。可以用dumpbin -imports [.exe文件路径]来查看输入信息。

例子:

HINSTANCE hInst;//动态加载DLLhInst = ::LoadLibrary("F:\\windows\\000MFC\\Dll2013\\Debug\\Dll2013.dll");if(!hInst){MessageBox("加载失败");return;}//定义函数指针类型typedef int (*ADDPROC)(int a, int b);//获取DLL的导出函数ADDPROC add = (ADDPROC)GetProcAddress(hInst, "Add");if(!add){MessageBox("获取函数地址失败");return;}CString str;str.Format("5 + 3 = %d", add(5, 3));MessageBox(str);

调用约定:

如果我们将DLL工程中的导出函数的调用约定改为标准约定(_stdcall),那么生成的DLL,其导出函数名字会被改编吗???自己可以试验一下,结果是不会发生名字改编。

还要注意的地方:当DLL中导出函数采用的是标准调用约定时,放完该DLL的客户端程序也应该采用该约定类型来访问相应的导出函数。

DLL中:

int _stdcall Add(int a, int b){return b + a;}

客户端程序中:

typedef int (_stdcall *ADDPROC)(int a, int b);


根据序号访问DLL中的导出函数

当我们用dumpbin工具查看DLL文件是时,可以看到每个导出函数都进行了编号,而我们就可以利用这些编号来调用导出函数。在这里我们要解决的问题是,如何把int类型的序号转换为LPCSTR类型的变量???使用宏 MAKEINTRESOURCE ,该宏可以把指定的函数序号转换为相应的函数名字字符串。

//获取DLL的导出函数//ADDPROC add = (ADDPROC)GetProcAddress(hInst, "Add");ADDPROC add = (ADDPROC)GetProcAddress(hInst, MAKEINTRESOURCE(1));

 

最后,当不在需要访问该DLL时,调用FreeLibrary函数释放对该DLL的引用。
 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 骑三轮车被拘留怎么办 在看守所想上诉怎么办 南京老人去世后怎么办 吸毒强戒两年家人该怎么办 犯人在监狱里病亡怎么办 父母去世监狱人怎么办 公司不续签合同怎么办 股票亏20个点后怎么办 摩托车给扣了怎么办 通知拘留跑了怎么办 假烟倒卖会怎么办 卖了40万假烟怎么办 刑事拘留15天了怎么办 治安传唤人不到怎么办 治安处罚有劣迹怎么办 学生怀孕了该怎么办 有病不能拘留那怎么办 拘留所不交伙食费怎么办 治安拘留不执行怎么办 释放证明书丢了怎么办 银行提前收贷款怎么办 存货周转天数高怎么办 欠款人没有财产怎么办 起诉后对方没钱怎么办 法院起诉人不到怎么办 治安拘留跑了怎么办 看守所里生病了怎么办 二审上诉被驳回怎么办 醉酒驾车取保候审以后怎么办 小案子证据不足怎么办 撞车不严重逃逸怎么办 被执行人没有财产执行怎么办 挖到人头了怎么办 取保保证金不退怎么办 被诬陷经济诈骗怎么办 醉驾刑事拘留后怎么办 被别人举报赌博怎么办 涉黄刑事拘留怎么办取保候审 换了车牌保险怎么办 车辆转让后保险怎么办 立案后警察不管怎么办