转载地址:http://qiusuoge.com/8228.html
自从微软推出第一个版本的Windows操作系统以来,动态链接库(DLL)一直是Windows操作系统的基础。动态链接库通常都不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其它DLL调用来完成某项工作的函数。只有在其它模块调用动态链接库中的函数时,它才发挥作用。
Windows API中的所有函数都包含在DLL中。其中有3个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。
静态库和动态库
静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件(.EXE文件)。
在使用动态库的时候,往往提供两个文件:一个引入库和一个DLL。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去加载DLL,访问DLL中导出的函数。
使用动态链接库的好处
可以采用多种编程语言来编写。
增强产品的功能。
提供二次开发的平台。
简化项目管理。
可以节省磁盘空间和内存。
有助于资源的共享。
有助于实现应用程序的本地化。
动态链接库被多个进程访问
动态链接库加载的两种方式
预备知识
:::::::::::::::::::::::::::
进程的虚拟地址空间:
在32为系统中,系统为没个进程分配2^32的地址空间
具体可参看《windows核心编程》
预处理命令:
没有什么可以说的了
看看代码就明白了!!
环境变量:(环境变量的概念我就不介绍了,具体的可以参看windows 核心编程,上面有很详细的说明)把DLL放到当前任意的环境变量中就可以加载
定义函数指针
格式:typedef int (*proc)(int a,int b);
注意:proc是一个函数指针类型而不是一个变量
然后我们可以用这个指针类型去定义变量
Proc myproc;这里的myproc就是一个指针变量
要是实在无聊 内心空虚 没事可做的话 可以去这个网站看看函数的指针http://ufo.tyedu.com/study/programmer/language_C/200412/1472.html
函数的调用约定(可以不必了解,但是理解后可以让你理解DLL的调用更为深刻)
函数的调用约定:函数调用约定是函数调用者和被调用的函数体之间关于参数传递、返回值传递、堆栈清除、寄存器使用的一种约定;
它是需要二进制级别兼容的强约定,函数调用者和函数体如果使用不同的调用约定,将可能造成程序执行错误,必须把它看作是函数声明的一部分;
常见的函数调用约定:
VC6中的函数调用约定:
调用约定 堆栈清除 参数传递
__cdecl 调用者 从右到左,通过堆栈传递
__stdcall 函数体 从右到左,通过堆栈传递
__fastcall 函数体 从右到左,优先使用寄存器(ECX,EDX),然后使用堆栈
thiscall 函数体 this指针默认通过ECX传递,其它参数从右到左入栈
__cdecl是C/C++的默认调用约定; VC的调用约定中并没有thiscall这个关键字,它是类成员函数默认调用约定;
C/C++中的main(或wmain)函数的调用约定必须是__cdecl,不允许更改;
默认调用约定一般能够通过编译器设置进行更改,如果你的代码依赖于调用约定,请明确指出需要使用的调用约定;
常见的函数调用约定中,只有cdecl约定需要调用者来清除堆栈;
C/C++中的函数支持参数数目不定的参数列表,比如printf函数;由于函数体不知道调用者在堆栈中压入了多少参数,
所以函数体不能方便的知道应该怎样清除堆栈,那么最好的办法就是把清除堆栈的责任交给调用者;
这应该就是cdecl调用约定存在的原因吧;
VB一般使用的是stdcall调用约定;(ps:有更强的保证吗)
Windows的API中,一般使用的是stdcall约定;(ps: 有更强的保证吗)
建议在不同语言间的调用中(如DLL)最好采用stdcall调用约定,因为它在语言间兼容性支持最好;
三:函数返回值传递方式
其实,返回值的传递从处理上也可以想象为函数调用的一个out形参数; 函数返回值传递方式也是函数调用约定的一部分;
有返回值的函数返回时:一般int、指针等32bit数据值(包括32bit结构)通过eax传递,(bool,char通过al传递,short通过 ax传递),特别的__int64等64bit结构(struct) 通过edx,eax两个寄存器来传递(同理:32bit整形在16bit环境中通过dx,ax传递); 其他大小的结构(struct)返回时把其地址通过eax返回;(所以返回值类型不是1,2,4,8byte时,效率可能比较差)
参数和返回值传递中,引用方式的类型可以看作与传递指针方式相同;
float/double(包括Delphi中的extended)都是通过浮点寄存器st(0)返回;
具体的分析参看:http://blog.csdn.net/avalonbbs/archive/2004/12/25/229300.aspx
::::::::::::::::::::::::::::::::::::
隐式链接
本文现在对隐式链接不作具体的说明,只是做一个大概的介绍(下次再做具体的说明).当进程运行的时候,所有的相关的DLL都被加载到内存,然后映射到进程的地址空间,当一个进程要调用很多个DLL的时候,这种方法就显得特别浪费内存,所以在加载很多个DLL的时候,最好用显示加载的方式
在调用DLL里面的函数时候
要用extern 声明是外部变量
比如 extern int add(int num1,int num2);
但是还应该注意的就是:在编译DLL时,要把编译的LIB文件放到执行文件的目录下,并且在编译执行文件的时候要连接LIB文件。
在写DLL的时候要导出的函数也就是在能被外部程序调用的函数前面加上
一般大型项目在开发DLL中,要进行预定义声明的
======================================
在定义DLL的时候要定义导出函数就要在该函数前面加__declspec(DLLexport)时,C++编译器为了支持函数的重载会进行函数名字改编,当可执行模块执行该函数时由于找不到该函数的名字,于是调用就会出现错误!当使用extern “C”时就可以告诉编译器不必修改函数名和变量名。这样就可以用C++或C编译器程序调用该函数。
以上操作只有在VC++创建的的可执行模块来调用该DLL,如果使用其他的编译器的模块来调用该DLL,就需要执行一些额外的操作。
C编译在进行编译的时候也会进行名字的改编,当函数使用_stdcall(WINAPI)调用规则时,MS编译器就会改编函数的名称。
比如:__declspec(DLLexport) LONG __stdcall Proc(int a ,int b);
编译器会改编为__Proc@8
因此当非C++或非C编译器调用该DLL中的函数Proc时,就会出现找不到函数的情况。
这样我们就可以定义DEF文件来解决,并且在DEF文件加上下面的EXPORTS:
EXPORTS
Proc
Def模块执行原理:当连接程序分析这个DEF文件时,它发现Proc和__Proc@8都被输出,由于这两个函数是相互匹配的,因此连接程序使用Proc来输出该函数,根本不使用__Proc@8来输出函数名
=======================================
下面是def的具体使用方法
----------------------------------------------------------------------------------------------------------------------模块定义文件(.DEF)是一个或多个用于描述DLL属性的模块语句组成的文本文件,每个DEF文件至少必须包含以下模块定义语句:
* 第一个语句必须是LIBRARY语句,指出DLL的名字;
* EXPORTS语句列出被导出函数的名字;将要输出的函数修饰名罗列在EXPORTS之下,这
个名字必须与定义函数的名字完全一致,如此就得到一个没有任何修饰的函数名了。
* 可以使用DESCRIPTION语句描述DLL的用途(此句可选);
* ";"对一行进行注释(可选)。
----------------------------------------------------------------------------------------------------------------------
dlltest.h
#ifdef DLL1_API
#else
#define DLL1_API extern "C" _declspec(dllimport)
#endif
DLL1_API int _stdcall add(int a,int b);
DLL1_API int _stdcall subtract(int a,int b);
//
#define DLL1_API extern "C" _declspec(dllexport)
#include "Dll1.h"
#include <stdio.h>
int _stdcall add(int a,int b)
{
return a+b;
}
int _stdcall subtract(int a,int b)
{
return a-b;
}
//
// def文件
LIBRARY dlltest
EXPORTS
add
subtract
有了上面的那些文件之后就可以在如何地方调用这些函数了
void CDllTestDlg::OnBtnSubtract()
{
// TODO: Add your control notification handler code here
CString str;
str.Format("5-3=%d",subtract(5,3));
MessageBox(str);
}
void CDllTestDlg::OnBtnOutput()
{
// TODO: Add your control notification handler code here
Point pt;
pt.output(5,3);
}下面具体介绍显示加载
显示加载
VC++编译器在编译DLL的时候函数会发生名字改编;主要在非C++环境中就不能识别该函数了,所以这里应该定义模块文件类型DEF,主要就方便了非C++程序可以调用该DLL里面的函数
再使用显示加载前必须要注意的是名字的改编问题,因为再动态加载中名字改编后在加载就得不原来的函数名字了,这样加载就会失败。但是可以用另外一种方法加载:MSDN上对GetProAddress中的第二个参数是这样说明的Pointer to a null-terminated string that specifies the function or variable name, or the function's ordinal value.也就是说可以使用函数的序号来调用该函数,具体使用方法是ProcAdd = (MYPROC) GetProcAddress(hinstLib, MakeIntResource(i)); (i代表函数在DLL中的序号,可以用DUMPBIN工具查看),但是一般的都不用这种转换序号的方式来取得函数的地址,因为这样非常的不直观!下面就用模块定义文件(DEF)来避免DLL中函数的名字的改编问题
显示加载DLL
//MSDN上的对DLL进程显示加载的DEMO
Using Run-Time Dynamic Linking
You can use the same DLL in both load-time and run-time dynamic linking. The following example uses theLoadLibrary function to get a handle to the Myputs DLL (see Creating a Simple Dynamic-Link Library). If LoadLibrary succeeds, the program uses the returned handle in the GetProcAddressfunction to get the address of the DLL's myPuts function. After calling the DLL function, the program calls the FreeLibrary function to unload the DLL.
Because the program uses run-time dynamic linking, it is not necessary to link the module with an import library for the DLL.
This example illustrates an important difference between run-time and load-time dynamic linking. If the DLL is not available, the application using load-time dynamic linking must simply terminate. The run-time dynamic linking example, however, can respond to the error.
// A simple program that uses LoadLibrary and
// GetProcAddress to access myPuts from Myputs.dll.
#include <stdio.h>
#include <windows.h>
typedef int (*MYPROC)(LPTSTR);
VOID main(VOID)
{
HINSTANCE hinstLib;
MYPROC ProcAdd;
BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;
// Get a handle to the DLL module.
hinstLib = LoadLibrary(TEXT("DllTest"));
// If the handle is valid, try to get the function address.
if (hinstLib != NULL)
{
ProcAdd = (MYPROC) GetProcAddress(hinstLib, TEXT("Proc"));
// If the function address is valid, call the function.
if (NULL != ProcAdd)
{
fRunTimeLinkSuccess = TRUE;
(ProcAdd) (TEXT("Message via DLL function/n"));
}
// Free the DLL module.
fFreeResult = FreeLibrary(hinstLib);
}
// If unable to call the DLL function, use an alternative.
if (! fRunTimeLinkSuccess)
printf("Message via alternative method/n");
}
对以上的几个函数作一些必要的说明:
LoadLibrary:加载指定的DLL,加载方式是先在当前目录中查找,如果找不到再再环境变量目录下查找;
还是看MSDN上的说明
The LoadLibrary function maps the specified executable module into the address space of the calling process.
HMODULE LoadLibrary(
LPCTSTR lpFileName
);
Parameters
lpFileName
[in] Pointer to a null-terminated string that names the executable module (either a .dll or .exe file). The name specified is the file name of the module and is not related to the name stored in the library module itself, as specified by the LIBRARY keyword in the module-definition (.def) file.
GetProcAddress:是取得已知的DLL中的函数,返回指定函数的地址
MSDN上的说明
This function returns the address of the specified exported DLL function.
FARPROC GetProcAddress(
HMODULE hModule,
LPCWSTR lpProcName
);
Parameters
hModule
[in] Handle to the DLL module that contains the function.
The LoadLibrary or GetModuleHandle function returns this handle.
lpProcName
[out] Pointer to a null-terminated string containing the function name, or specifies the function's ordinal value.
If this parameter is an ordinal value, it must be in the low-order word; the high-order word must be zero.
The lpProcName parameter must be in Unicode.
Remark:
The GetProcAddress function is used to retrieve addresses of exported functions in DLLs.
The spelling and case of the function name pointed to by lpProcName must be identical to that in the EXPORTS statement of the source DLL's module-definition (.def) file.
The exported names of Win32 APIs might differ from the names you use when calling these functions in your code. This difference is hidden by macros used in the SDK header files.