DLL入门

静态库

       函数和数据被编译进一个二进制文件(通常扩展名为.lib),在使用静态库的情况下,在编译连接可执行文件时,连接器从库中复制这些函数和数据并把他们和应用程序的其他模块组合起来创建最终的可执行文件。(.exe文件)【在release程序的时候,只需要release可执行文件,静态库已经生成到了可执行文件】

动态库(只有导出的函数才能被使用)

        在使用动态库的时候,往往提供两个文件:一个引入库(后缀也是.lib)和一个DLL。引用库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译连接可执行文件时,只需要连接引用库,在运行的时候,再去加载DLL,访问DLL中导出的函数。

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

        隐式链接:使用extern声明外部函数。在调用DLL中的函数之前,为了让编译器知道这些函数,需要对这些函数做一个声明,增加响应函数的声明并在前面加上extern表明函数是在外部定义的。除了使用extern关键字表明函数时外部定义的之外,还可以使用标识符:_descspec(dllimport)来表明函数是从动态链接库中导入的。与使用extern关键字这种方式相比,使用_declspec(dllimport)标识符声明外部函数时,它将告诉编译器该函数时从动态链接库中导入的,编译器可以生成运行效率更高的代码,因此,如果调用的函数来自于动态链接库,应该采用这种方式声明外部函数。

        显示加载:LoadLibrary()。

生成DLL中有两个文件:一个是.lib文件;另一个是.Dll文件。

通常在编写动态链接库时,都会提供一个头文件,在此文件中提供DLL导出函数原型的声明,以及函数的有关注释文档。dll.h头文件是交给客户端的,是客户端使用的,因此在声明函数时使用的是dllimport关键字,向客户端程序表明他们是从动态链接库中导入的。

即在头文件中,使用的是_declspec(dllimport),在源文件中使用的是_declspec(dllexport)。

使用dll需注意三个文件:

•.h头文件,包含dll中说明输出的类或符号原型或数据结构的.h文件。应用程序调用dll时,需要将该文件包含入应用程序的源文件中。
•.LIB文件,是dll在编译、链接成功之后生成的文件,作用是当其他应用程序调用dll时,需要将该文件引入应用程序,否则产生错误(如果不想用lib文件或者没有lib文件,可以用WIN32 API函数LoadLibrary、GetProcAddress装载)。
•dll文件,真正的可执行文件,开发成功后的应用程序在发布时,只需要有.exe文件和.dll文件,并不需要.lib文件和.h头文件。

使用dumpbin -exports dllname.dll的输出信息

ordinalhintRVAname
10 00011078(函数在DLL中的内存地址)?Add@@YAHHH@Z(名字改编,以支持函数重载)
2100011069?subtract@@YAHHH@Z

使用dumpbin -imports dllname.dll的输入信息

导入动态链接库中的函数的两种方法:

       extern int add(int a,int b);

       _declspec(import) int add(int a,int b);  告诉编译器这个函数使用动态链接库.lib中导入的,生成运行效率更高的代码。

动态链接库往往提供头文件,在客户端程序访问动态链接库的时候,才能知道哪些函数是被导出的。

如果头文件.h是供客户端使用的时候,在函数声明前加上_declspec(dllimport),此头文件被需要此DLL的工程所包含,在程序中就不需要在_declspec(import) int add(int a,int b); 而在dll的cpp文件中,使用_declspec(export)int add(int a,int b),表明此函数导出给非此DLL的程序使用。注意:编写DLL工程中的.h和.cpp文件之间没有关系,.h文件是提供给客户端使用的,表明导出了那些函数。

例如:

      dll.h文件中: 

#ifdef DLL_API
#else
#define DLL_API _decl(dllimport)
#endif

DLL_API int add(int a,int b)

       dll .cpp文件中:

#define DLL_API _declspec(export)
#include "dll.h"
DLL_API int add(int a,int b)

       作用可以看出:dll.h的作用是将函数导入(即DLL_API实际上是_declspec(dllimport)),由于客户端会包含dll.h文件(即DLL_API实际上是_declspec(export),实际上的作用是将动态链接库中的导出函数等导入到客户端程序;而dll.cpp文件中是将函数导出,表明此DLL中的哪些函数是被导出的。对DLL.h进行改造实际上是在头文件中用DLL_API来代替_declspec(dllimport),在源文件中用DLL_API来代替_declspec(export)。

        在程序编译时,头文件不参与编译,源文件单独编译,编译时头文件在包含的文件中就地展开。

GetForegroundWnd():返回调用者进程当前正在使用的那个窗口的句柄。

多个进程或线程调用同一个DLL的时候,要注意全局变量的互斥访问,由于调用函数时会生成函数的调用堆栈,因此不会产生互斥的问题。

DLL中导出类:

       在实现动态链接库时,可以不导出整个类,而只导出该类中的某些函数。如果在声明类时,指定了导出标志,那么该类中的所有函数都将被导出;否则只有那些声明了导出标志的类成员才被导出。对于这两种情况生成的DLL,客户端程序在访问方式上是没用区别的,都是先构造该类的一个对象,然后利用该类的对象访问该类导出的成员函数。

     

解决名字改变问题:

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

         因此在动态链接库文件在编译时,导出函数的名称不要发生改变。为了实现这一目标,在定义导出函数时,需要加上限定符:extern "C"。C为大写。

         在需要以c的方式进行编译时,就需要用extern "c"告诉编译器【注意以extern "C"按C的编译方式编译时,是不支持函数重载的,即如果有两个同名函数都是以C的方式编译,就会出错。

       调用一个函数时,总是先把参数压入栈,然后通过call指令转移到被调用函数,在完成调用后清除堆栈.这里有两个问题:(1)哪个参数先入栈(2)由谁来清理堆栈.这两个方面的问题称为"调用约定(Calling Conventions)"问题.

       这里只讨论_stdcall和_cdecl调用约定,前者是Windows API函数常用的调用约定,后者即是C调用约定.

       _stcall:参数按从右向左的顺序入栈,由被调用函数清理堆栈.
       _cdecl :参数按从右向左的顺序入栈,由调用函数清理堆栈.

      利用限定符:extern "C"可以解决C++和C语言之间相互调用时函数命名的问题,但是这种方法有一个缺陷,就是不能导出一个类的成员函数,只能用于导出全局函数这种情况。

        另外,如果导出函数的调用约定发生了改变,那么即使用了限定符:extern "C",该函数的名字仍会发生改变。 在这种情况下,可以通过一个称为模块定义文件(DEF)的方式来解决名字改编的问题。

       在当调用别人写的库时,注意库是使用何种编译器,若是C的,则你在用VC中的C++编译器调用时就得加

#if defined(__cplusplus)
extern "C" {
#endif

..........声明被调用的函数名

#if defined(__cplusplus)
};
#endif

模块定义文件(DEF)

LIBRARY Dll2   【LIBRARY语句用来指定动态链接库的内部名称,该名称与生成动态链接库的名字一定要匹配】
EXPORTS        【EXPORTS语句的作用是表明DLL将要导出的函数,以及为这些函数指定的符号名  】
add
subtract
当链接器在链接时,会分析这个DEF文件,当发现在EXPORTS语句下面有add和substract这两个符号名,并且他们与源文件中定义的add和substract函数的名字是一样的时候,他们就会以add和substrct这两个符号名导出相应的函数。 如果将要导出的符号名和源文件中定义的函数名不一样,则可以按照下述的语法指定导出函数:

          entryname=internalname   【entryname项是导出的符号名,右边的internalname是DLL中将要导出的函数的名字】


显示加载方式加载DLL

        隐式链接加载DLL是通过导入库.lib文件加载DLL的;显示加载是通过LoadLibrary函数加载。

       使用动态方式加载动态链接库时,需要用到LoadLibrary函数,该函数的作用是将指定的可执行模块映射到调用进程的地址空间。(Loadlibrary加载动态链接库时,不需要lib文件和.h文件)。

       LoadLibrary不仅可以加载DLL,还可以加载可执行模块(.exe)。一般来说,当加载可执行模块时,主要是为了访问该模块内的一些资源,例如对话框资源、位图资源等。如果调用成功,返回所加载的那个模块的句柄,该函数的返回类型是HMODULE,HMODULE类型和HINSTANCE类型可以通用。

       当获取到动态链接库的句柄后,接下来就要想办法获取该动态链接库中导出函数的地址,使用GetProcAddress函数来实现,该函数用来获取DLL导出函数的地址。通过动态加载DLL时,客户端程序不再需要包含导出函数声明的头文件和引入库文件,只需要.dll文件即可。

       FARPROC GetProcAddress(HMODULE,LPCSTR),LPCSTR指向常量的字符指针,指定DLL导出函数的名字或函数的需要。如果该参数指定的是导出函数的需要,那么该序号必须在低位中,高位字必须是0。

       采用动态加载方式,可以在需要时才加载DLL,而隐式链接方式实现起来比较简单,在编写客户端代码时就可以把连接做好,在程序中随时可以调用DLL导出的函数。但是当加载的DLL较多时,在程序启动的时候,这些DLL都需要被加载到内存中。采用动态加载DLL时,在客户端中将看不到调用该DLL的输入信息,使用dumpbin就无法找到DLL的信息。

调用约定

       当DLL中导出函数采用的是标准调用约定时,访问该DLL的客户端程序也应该采用该约定类型来访问相应的导出函数。

       GetProcAddress中的LPCSTR为导出函数重编后的名字,名字一般看起来很乱,因此可以在编译DLL的时候使用DEF文件控制函数重编后的名字。

        使用静态加载DLL时,因为有lib文件和.h文件,Lib文件中存放的是DLL中函数重编后的符号,客户端使用.h文件来映射DLL中的函数,只要.h中生成的函数的符号和lib中生成的函数符号一直,就能访问到。要做到这一点,.h中函数的调用约定要和.lib中函数的调用约定一致才能够访问到这些函数。而使用动态加载DLL时,此时在DLL中,所有的函数名都是重编后的函数名,在客户端中没有映射到DLL中函数的入口,只能使用GetProcAddress来访问DLL中的函数,此时只能通过函数名访问和序号访问。通过函数名访问时,由于函数名发生重编,重编后的名字很难分别,此时可以通过DEF文件来控制重编后的函数名。

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

       GetProcAddress(hInst,MAKEINTRESOURCE(1));通过序号来访问函数,最好还是采用函数名来访问函数。

DllMain函数

       对于可执行模块来说,其入口函数时WinMain函数;对DLL来说,其入口函数是DllMain。但该函数是可选的,也就是说在编写DLL程序时,可以提供也可以不提供DllMain函数。如果提供了DllMain函数,那么当系统加载该Dll时,就会调用该函数。

http://www.cnblogs.com/devilmsg/articles/1266336.html

http://blog.chinaunix.net/uid-26983585-id-3364514.html

http://blog.chinaunix.net/uid-1857870-id-2823756.html









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值