45fan.com - 路饭网

搜索: 您的位置主页 > 网络频道 > 阅读资讯:编写和使用动态链接库的步骤

编写和使用动态链接库的步骤

2016-09-01 10:57:56 来源:www.45fan.com 【

编写和使用动态链接库的步骤

作者:zieckey(zieckey@yahoo.com.cn)
All Rights Reserved!

首先说下DLL的原理.
自从微软推出第一个版本的Windows操作系统以来,
动态链接库(DLL)一直是Windows操作系统的基矗
动态链接库通常都不能直接运行,也不能接收消息。
它们是一些独立的文件,其中包含能被可执行程序或其它DLL调用来完成某项工作的函数。
只有在其它模块调用动态链接库中的函数时,它才发挥作用。
Windows API中的所有函数都包含在DLL中。
其中有3个最重要的DLL,
Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;
User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;
GDI32.dll,它包含用于画图和显示文本的各个函数。


静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。
在使用静态库的情况下,在编译链接可执行文件时,
链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件(.EXE文件)。
在使用动态库的时候,往往提供两个文件:一个引入库和一个DLL。
引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。
在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,
在运行的时候,再去加载DLL,访问DLL中导出的函数。

使用动态链接库的好处:
可以采用多种编程语言来编写。
增强产品的功能。
提供二次开发的平台。
简化项目管理。
可以节省磁盘空间和内存。
有助于资源的共享。
有助于实现应用程序的本地化。

动态链接库加载的两种方式:隐式链接,显示加载


新建一个空的dll工程,然后创建一个dll1.cpp文件如下
int add(int a,int b)
{
return a+b;
}

int subtract(int a,int b)
{
return a-b;
}

编译之后,我们看看导出函数
E:/zieckey/CPP-study/Dll1/Debug>dumpbin -exports dll1.dll
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file dll1.dll

File Type DLL

Summary

7000 .data
1000 .idata
2000 .rdata
2000 .reloc
2A000 .text
这里没有任何导出函数,那么我们可以这样,更改源文件
_declspec(dllexport) int add(int a,int b)
{
return a+b;
}

_declspec(dllexport) int subtract(int a,int b)
{
return a-b;
}

编译之后再去看看

E:/zieckey/CPP-study/Dll1/Debug>dumpbin -exports dll1.dll
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file dll1.dll

File Type DLL

Section contains the following exports for Dll1.dll

0 characteristics
456BD6E2 time date stamp Tue Nov 28 142746 2006
0.00 version
1 ordinal base
2 number of functions
2 number of names

ordinal hint RVA name

1 0 0000100A add@@YAHHH@Z
2 1 00001005 subtract@@YAHHH@Z

Summary

7000 .data
1000 .idata
3000 .rdata
2000 .reloc
2A000 .text

好了,这次我们看到了两个我们自己下的函数名字,只是名字似乎被更改过了,这是VC为了函数重载的方便,它以自己的规则修改了我们的源程序的函数名字.
下面我们来测试下刚刚建立的dll1.dll动态链接库.
我们新建一个win32控制台的空工程,新建一个dlltest.cpp ,下面是它的源码
#include iostream.h
extern int add(int,int); 声明这个函数在外部定义
extern int subtract(int,int);
void main(void)
{
cout3+5 = add(3,5)endl;
cout5-3 = subtract(5,3)endl;
}
编译一下
Compiling...
dlltest.cpp
Linking...
dlltest.obj error LNK2001 unresolved external symbol int __cdecl substract(int,int) (substract@@YAHHH@Z)
dlltest.obj error LNK2001 unresolved external symbol int __cdecl add(int,int) (add@@YAHHH@Z)
DebugDllTest.exe fatal error LNK1120 2 unresolved externals
执行 link.exe 时出错.
DllTest.exe - 1 error(s), 0 warning(s)

可以发现是在连接的时候出错了,则说明编译是没有错,编译器找到了这两个函数,因为这两个函数已经声明了.
但是在连接的时候没有找到.我们可以这样做;
将刚刚生成动态链接库文件的同一目录下,找到dll1.lib输入库文件,拷贝到我们的工程目录下,
最后 Project-Settings 在Link选项卡找到Objectlibrary modules 在最后填入 dll1.lib 。
如果原来就有链接,请使用空格分隔。

好的我们再编译下
--------------------Configuration DllTest - Win32 Debug--------------------
DllTest.exe - 0 error(s), 0 warning(s)
没有错误,执行时又出现错误,哦,我们的程序在执行时没有找到dll1.dll这个文件,好说,将它也拷贝到当前工程的目录下.
再执行,ye,这次就好了,输出信息如下
3+5 = 8
5-3 = 2
Press any key to continue

我们可以查看以查看一下这个可执行程序的输入信息
E:/zieckey/CPP-study/DllTest/Debug>dumpbin -imports DllTest.exe
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file DllTest.exe

File Type EXECUTABLE IMAGE

Section contains the following imports

Dll1.dll
43019C Import Address Table
43003C Import Name Table
0 time date stamp
0 Index of first forwarder reference

0 add@@YAHHH@Z
1 subtract@@YAHHH@Z

KERNEL32.dll
4301D0 Import Address Table
430070 Import Name Table
0 time date stamp
0 Index of first forwarder reference

CA GetCommandLineA
174 GetVersion
7D ExitProcess
22F RtlUnwind
11A GetLastError
1B CloseHandle
2DF WriteFile
218 ReadFile
26A SetFilePointer
26D SetHandleCount
152 GetStdHandle
115 GetFileType
150 GetStartupInfoA
51 DebugBreak
1AD InterlockedDecrement
1F5 OutputDebugStringA
13E GetProcAddress
1C2 LoadLibraryA
1B0 InterlockedIncrement
124 GetModuleFileNameA
29E TerminateProcess
F7 GetCurrentProcess
2AD UnhandledExceptionFilter
B2 FreeEnvironmentStringsA
B3 FreeEnvironmentStringsW
2D2 WideCharToMultiByte
106 GetEnvironmentStrings
108 GetEnvironmentStringsW
126 GetModuleHandleA
109 GetEnvironmentVariableA
175 GetVersionExA
19D HeapDestroy
19B HeapCreate
19F HeapFree
2BF VirtualFree
AA FlushFileBuffers
1B8 IsBadWritePtr
1B5 IsBadReadPtr
1A7 HeapValidate
27C SetStdHandle
241 SetConsoleCtrlHandler
BF GetCPInfo
B9 GetACP
131 GetOEMCP
199 HeapAlloc
2BB VirtualAlloc
1A2 HeapReAlloc
28B SetUnhandledExceptionFilter
1B2 IsBadCodePtr
1E4 MultiByteToWideChar
1BF LCMapStringA
1C0 LCMapStringW
153 GetStringTypeA
156 GetStringTypeW

Summary

6000 .data
1000 .idata
2000 .rdata
2000 .reloc
27000 .text

另外我们可以通过VC提供的图形化工具查看我们的可执行程序依赖的动态链接库文件,
在VC的安装目录下的 DSoftwaresMicrosoft Visual StudioVC98Microsoft Visual C++ 6.0 Tools 目录下有这个工具 Depends .
它可以查看可执行程序依赖的动态链接库文件

我们还可以这样声明动态链接库里的函数接口,源码修改为
#include iostream.h
extern int add(int,int);
extern int subtract(int,int);
_declspec(dllimport) int add(int,int);
_declspec(dllimport) int subtract(int,int);
void main(void)
{
cout3+5 = add(3,5)endl;
cout5-3 = subtract(5,3)endl;
}
编译运行.
_declspec(dllimport) 就告诉编译器这个函数是从动态链接库的.lib文件中输入的,编译器就能生成运行效率更高的代码.

我们可以把我们自己做的动态链接库文件的函数声明都放到一个头文件中,以便告诉用户这个动态链接库文件里封装的函数原型.
好的我们在前面动态链接库的工程中添加一个头文件 dll1.h ,其里面的内容是
_declspec(dllimport) int add(int,int);
_declspec(dllimport) int subtract(int,int);

那么现在我们就可以将源码修改为
#include iostream.h
extern int add(int,int);
extern int subtract(int,int);
#include ..Dll1dll1.h
void main(void)
{
cout3+5 = add(3,5)endl;
cout5-3 = subtract(5,3)endl;
}
编译运行.

我们到这里可以发现,前面的动态链接库源码文件dll1.cpp
_declspec(dllexport) int add(int a,int b)
{
return a+b;
}
_declspec(dllexport) int subtract(int a,int b)
{
return a-b;
}
这里是导出形式(dllexport),那么这个动态链接库就不能被它自己的源码程序调用,这时我们可以改改就行了
dll1.cpp
#define DLL1_API _declspec(dllexport)
#include dll1.h
int add(int a,int b)
{
return a+b;
}
int subtract(int a,int b)
{
return a-b;
}


dll1.h
#ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport)
#endif
DLL1_API int add(int,int);
DLL1_API int subtract(int,int);


现在这个dll库就可以被自己调用也可以被其他程序调用了.
我们再看看怎么将类导出,象这样就好了
#ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport)
#endif
DLL1_API int add(int,int);
DLL1_API int subtract(int,int);
class DLL1_API Point
{
public
void output( int x,int y);
};

如果我们不想将整个类都导出,我们只需将类的部分公有成员函数导出,
我们可以这样做
#ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport)
#endif
DLL1_API int add(int,int);
DLL1_API int subtract(int,int);
class DLL1_API Point
{
public
DLL1_API void output( int x,int y);
void test();
};
这里的void test();是做一个对比用的.
我们用dumpbin -exports dll1.dll看看导出情况
E:/zieckey/CPP-study/Dll1/Debug>dumpbin -exports dll1.dll
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file dll1.dll

File Type DLL

Section contains the following exports for Dll1.dll

0 characteristics
456BE737 time date stamp Tue Nov 28 153727 2006
0.00 version
1 ordinal base
3 number of functions
3 number of names

ordinal hint RVA name

1 0 00001019 add@@YAHHH@Z
2 1 0000101E output@Point@@QAEXHH@Z
3 2 00001014 subtract@@YAHHH@Z

Summary

7000 .data
1000 .idata
3000 .rdata
2000 .reloc
31000 .text

这里就可以发现只有
2 1 0000101E output@Point@@QAEXHH@Z
而没有test()


上面提到了VC++编译器将函数名字进行了改写,这对于其他编译器来说也许就不是什么好事,因为其他编译器很可能找不到导出的函数,
因为名字已经被改动了.那么我们就希望这个名字不要更改.这里可以加上 extern C 标识
dll1.cpp
#define DLL1_API extern C _declspec(dllexport)
#include dll1.h
int add(int a,int b)
{
return a+b;
}
int subtract(int a,int b)
{
return a-b;
}

dll1.h
#ifdef DLL1_API
#else
#define DLL1_API extern C _declspec(dllimport)
#endif
DLL1_API int add(int,int);
DLL1_API int subtract(int,int);
编译下,看看导出情况,
E:/zieckey/CPP-study/Dll1/Debug>dumpbin -exports dll1.dll
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file dll1.dll

File Type DLL

Section contains the following exports for Dll1.dll

0 characteristics
456BE969 time date stamp Tue Nov 28 154649 2006
0.00 version
1 ordinal base
2 number of functions
2 number of names

ordinal hint RVA name

1 0 0000100A add
2 1 00001005 subtract

Summary

7000 .data
1000 .idata
3000 .rdata
2000 .reloc
2A000 .text

这里就可以看到名字没有改变,
但是用 extern C 标识也有缺陷,就是它只能表示全局函数,
而对于类说,它不能导出类的成员函数.
如果调用约定被改变了的话,即使加了 extern C 标识,函数名还是会被改变
这里我们举例看看
dll1.cpp
#define DLL1_API extern C _declspec(dllexport)
#include dll1.h
int _stdcall add(int a,int b)
{
return a+b;
}
int _stdcall subtract(int a,int b)
{
return a-b;
}

dll1.h
#ifdef DLL1_API
#else
#define DLL1_API extern C _declspec(dllimport)
#endif
DLL1_API int _stdcall add(int,int);
DLL1_API int _stdcall subtract(int,int);
编译下,看看导出情况
E:/zieckey/CPP-study/Dll1/Debug>dumpbin -exports dll1.dll
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file dll1.dll

File Type DLL

Section contains the following exports for Dll1.dll

0 characteristics
456BEBDB time date stamp Tue Nov 28 155715 2006
0.00 version
1 ordinal base
2 number of functions
2 number of names

ordinal hint RVA name

1 0 00001005 _add@8
2 1 0000100A _subtract@8

Summary

7000 .data
1000 .idata
3000 .rdata
2000 .reloc
2A000 .text
这里就可以看到名字还是发生了改变,
_add@8这里的8表示add函数的参数所占字节数.
我们看看下面的解决方法.

再重新新建一个dll的空工程,命名为Dll2
再新建一个C++源文件,命名为dll2.cpp,
dll2.cpp
int add(int a,int b)
{
return a+b;
}
int subtract(int a,int b)
{
return a-b;
}

然后我们创建一个模块定义文件,命名为dll2.def,然后添加我们的工程中.其内容如下

LIBRARY Dll2

EXPORTS
add
subtract

其中 LIBRARY Dll2 这个名字一定要与我们的工程名字匹配,另外这句话也不是必须的.
EXPORTS表明动态链接库中要导出的函数名字,我们可以在 MSDN 中查看一下它的用法.

编译一下,看看导出情况
E:/zieckey/CPP-study/Dll2/Debug>dumpbin -exports dll2.dll
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file dll2.dll

File Type DLL

Section contains the following exports for Dll2.dll

0 characteristics
456BEECE time date stamp Tue Nov 28 160950 2006
0.00 version
1 ordinal base
2 number of functions
2 number of names

ordinal hint RVA name

1 0 0000100A add
2 1 00001005 subtract

Summary

7000 .data
1000 .idata
3000 .rdata
2000 .reloc
2A000 .text

这里就可以看到名字没有改变,和原来的一样.


上面我们都是用隐式链接的方式调用动态链接库的,
下面我们看看另一种方法,显示调用
将我们的 DllTest 工程中的 Project-Settings
在Link选项卡中的 Objectlibrary modules 删除 dll1.lib

这里就要用到一个函数
HINSTANCE LoadLibrary
(
LPCTSTR lpLibFileName address of filename of executable module
);
它是映射指定的模块到应用进程的地址空间

获得函数的地址空间用如下函数,
FARPROC GetProcAddress(
HMODULE hModule, handle to DLL module
LPCSTR lpProcName name of function
);

好的,现在我们看看我们的测试程序怎么写,将测试工程中的 dlltest.cpp 文件改为如下

dlltest.cpp
#include iostream.h
#include windows.h
void main(void)
{
HINSTANCE hInst;
hInst=LoadLibrary(Dll2.dll);
typedef int (FUNCPROC)(int a,int b);
FUNCPROC ADD=(FUNCPROC)GetProcAddress(hInst,add);
FUNCPROC SUB=(FUNCPROC)GetProcAddress(hInst,subtract);
if(!ADD)
{
cout获取函数地址失败!endl;
return;
}
cout3+5 = ADD(3,5)endl;
cout5-3 = SUB(5,3)endl;
FreeLibrary(hInst); 释放动态链接库
}

编译运行,哦,别忘了将我们新编译处理的dll文件 dll2.dll 复制到我们的测试程序工程目录下.
运行结果如下
3+5 = 8
5-3 = 2
Press any key to continue

这里我们可以发现我们只拷贝了一个 dll2.dll 文件,
而没有拷贝我们的 dll2.lib ,这里是因为我们是动态加载的.
动态加载的好处是在我们需要的地方加载,这样可以节约内存空间.
如果是用隐式链接的方式,那么在我们启动程序时,多个dll库都要加载到内存中,
这时很浪费内存的,而且程序启动时间也很慢.

我们再来看看改变调用的方式,将dll2.cpp文件改变如下
dll2.cpp
int add(int a,int b)
{
return a+b;
}
int subtract(int a,int b)
{
return a-b;
}

编译一下,看看导出情况
E:/zieckey/CPP-study/Dll2/Debug>dumpbin -exports dll2.dll
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file dll2.dll

File Type DLL

Section contains the following exports for Dll2.dll

0 characteristics
456BF57D time date stamp Tue Nov 28 163821 2006
0.00 version
1 ordinal base
2 number of functions
2 number of names

ordinal hint RVA name

1 0 0000100A add
2 1 00001005 subtract

Summary

7000 .data
1000 .idata
3000 .rdata
2000 .reloc
2A000 .text
这里就可以看到即使改变了调用方式,函数名字没有改变,和原来的一样.
好的,那么我们来看看测试程序,
当然,别忘了将我们新编译处理的dll文件 dll2.dll 复制到我们的测试程序工程目录下.
编译,没有错误,在运行时出现了错误,这是因为我们改变了调用的约定,
当然,在调用的时候也要遵循这样的约定,将测试工程中的 dlltest.cpp 文件改为如下

dlltest.cpp
#include iostream.h
#include windows.h
void main(void)
{
HINSTANCE hInst;
hInst=LoadLibrary(Dll2.dll);
typedef int (_stdcall FUNCPROC)(int a,int b);
FUNCPROC ADD=(FUNCPROC)GetProcAddress(hInst,add);
FUNCPROC SUB=(FUNCPROC)GetProcAddress(hInst,subtract);
if(!ADD)
{
cout获取函数地址失败!endl;
return;
}
cout3+5 = ADD(3,5)endl;
cout5-3 = SUB(5,3)endl;
FreeLibrary(hInst); 释放动态链接库
}
好了这样就行了.
我们再来看看现在编译出来的可执行程序 dlltest.exe 的导入信息.
E:/zieckey/CPP-study/DllTest/Debug>dumpbin -imports DllTest.exe
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file DllTest.exe

File Type EXECUTABLE IMAGE

Section contains the following imports

KERNEL32.dll
43015C Import Address Table
430028 Import Name Table
0 time date stamp
0 Index of first forwarder reference

B4 FreeLibrary
13E GetProcAddress
1C2 LoadLibraryA
CA GetCommandLineA
174 GetVersion
7D ExitProcess
22F RtlUnwind
11A GetLastError
1B CloseHandle
2DF WriteFile
218 ReadFile
26A SetFilePointer
26D SetHandleCount
152 GetStdHandle
115 GetFileType
150 GetStartupInfoA
51 DebugBreak
1AD InterlockedDecrement
1F5 OutputDebugStringA
1B0 InterlockedIncrement
124 GetModuleFileNameA
29E TerminateProcess
F7 GetCurrentProcess
2AD UnhandledExceptionFilter
B2 FreeEnvironmentStringsA
B3 FreeEnvironmentStringsW
2D2 WideCharToMultiByte
106 GetEnvironmentStrings
108 GetEnvironmentStringsW
126 GetModuleHandleA
109 GetEnvironmentVariableA
175 GetVersionExA
19D HeapDestroy
19B HeapCreate
19F HeapFree
2BF VirtualFree
AA FlushFileBuffers
1B8 IsBadWritePtr
1B5 IsBadReadPtr
1A7 HeapValidate
27C SetStdHandle
241 SetConsoleCtrlHandler
BF GetCPInfo
B9 GetACP
131 GetOEMCP
199 HeapAlloc
2BB VirtualAlloc
1A2 HeapReAlloc
28B SetUnhandledExceptionFilter
1B2 IsBadCodePtr
1E4 MultiByteToWideChar
1BF LCMapStringA
1C0 LCMapStringW
153 GetStringTypeA
156 GetStringTypeW

Summary

6000 .data
1000 .idata
2000 .rdata
2000 .reloc
27000 .text

这里就看不到我们的 dll2.dll 的信息了.因为我们时动态加载

我们看到dll2.dll库的信息有如下
1 0 0000100A add
2 1 00001005 subtract
我们可以通过序号调用这两个函数,这里我们用到了 MAKEINTRESOURCE
将测试工程中的 dlltest.cpp 文件改为如下

dlltest.cpp
#include iostream.h
#include windows.h
void main(void)
{
HINSTANCE hInst;
hInst=LoadLibrary(Dll2.dll);
typedef int (_stdcall FUNCPROC)(int a,int b);
FUNCPROC ADD=(FUNCPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
FUNCPROC SUB=(FUNCPROC)GetProcAddress(hInst,MAKEINTRESOURCE(2));
if(!ADD)
{
cout获取函数地址失败!endl;
return;
}
cout3+5 = ADD(3,5)endl;
cout5-3 = SUB(5,3)endl;
FreeLibrary(hInst); 释放动态链接库
}
好了这样就行了.
但是我们应该通过函数名来调用dll库中的函数.
因为这个让程序可读性更好,出错几率也小些

对于动态链接库的可选程序入口点时 DllMain
BOOL WINAPI DllMain(
HINSTANCE hinstDLL, handle to DLL module
DWORD fdwReason, reason for calling function
LPVOID lpvReserved reserved
);

这里我们看到通过模块化生成动态链接库是比较好的选择.


一点小技巧:
VC的bin目录下的 VCVARS32.BAT 批处理程序就是建立VC的开发环境的.
在命令提示符下的粘贴和拷贝
粘贴:点击鼠标右键粘贴就可以了
复制:点击鼠标右键选择标记,然后用鼠标左键标记一段,单击鼠标左键,那么就将相应内容复制到剪贴板下了.

 

本文地址:http://www.45fan.com/a/question/70730.html
Tags: 动态 详解 编写
编辑:路饭网
关于我们 | 联系我们 | 友情链接 | 网站地图 | Sitemap | App | 返回顶部