VC DLL总结

只谈情不闲聊 提交于 2020-03-08 03:42:51

一、DLL的导出方法

1、使用_declspec(dllexport) 方法

     DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类。

extern "C" _declspec(dllexport) int sum(int a,int b);//本文所有的例子只有一个sum即加法函数。

       在制作DLL导出函数时由于C++存在函数重载,因此__declspec(dllexport) function(int,int) 在DLL会被decorate,例如被decorate成为 function_int_int,而且不同的编译器decorate的方法不同,造成了在用GetProcAddress取得function地址时的不便,使用extern "C"时,上述的decorate不会发生,因为C没有函数重载,但如此一来被extern"C"修饰的函数,就不具备重载能力,可以说extern 和 extern "C"不是一回事。

        在VC++中,如果生成DLL可以不使用.def文件。只需要在VC++的函数定义前要加__declspec(dllexport)修饰就可以了。但是使用__declspec(dllexport)和使用.def文件是有区别的。如果DLL是提供给VC++用户使用的,只需要把编译DLL时产生的.lib提供给用户,它可以很轻松地调用你的DLL。但是如果DLL是供VB、PB、Delphi用户使用的,那么会产生一个小麻烦。因为VC++对于__declspec(dllexport)声明的函数会进行名称转换,如下面的函数:
    __declspec(dllexport) int __stdcall IsWinNT()
    会转换为IsWinNT@0,这样你在VB中必须这样声明:
    Declare Function IsWinNT Lib "my.dll" Alias "IsWinNT@0" () As Long
    @的后面的数由于参数类型不同而可能不同。这显然不太方便。所以如果要想避免这种转换,就要使用.def文件方式。
    EXPORTS后面的数可以不给,系统会自动分配一个数。对于VB、PB、Delphi用户,通常使用按名称进行调用的方式,这个数关系不大,但是对于使用.lib链接的VC程序来说,不是按名称进行调用,而是按照这个数进行调用的,所以最好给出。如:
EXPORTS 
test @1 

       若要导出函数,__declspec(dllexport) 关键字必须出现在调用约定关键字的左边(如果指定了关键字)。例如:

 复制代码
__declspec(dllexport) void __cdecl Function1(void);

若要导出类中的所有公共数据成员和成员函数,关键字必须出现在类名的左边,如下所示:

 复制代码
class __declspec(dllexport) CExampleExport : public CObject
{ ... class definition ... };

2、使用def文件定义导出

 

.def 文件必须至少包含下列模块定义语句:

 

  • 文件中的第一个语句必须是 LIBRARY 语句。此语句将 .def 文件标识为属于 DLL。LIBRARY 语句的后面是 DLL 的名称。链接器将此名称放到 DLL 的导入库中。

  • EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。如果希望按序号导出函数。

  • .def 文件中的注释由每个注释行开始处的分号 (;) 指定,且注释不能与语句共享

  • 用def文件还可以改变导出的函数的名字,例如   myadd = add  使得导出的函数叫myadd,而不是add

二、 dll文件的导入

1. 隐式的加载时链接

VC中加载DLL的LIB文件的方法有以下三种:
      ①LIB文件直接加入到工程文件列表中
在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中“Add Files to Project”菜单,在弹出的文件对话框中选中要加入DLL的LIB文件即可。
      ②设置工程的 Project Settings来加载DLL的LIB文件
打开工程的 Project Settings菜单,选中Link,然后在Object/library modules下的文本框中输入DLL的LIB文件。
       ③通过程序代码的方式
加入预编译指令#pragma comment (lib,”*.lib”),这种方法优点是可以利用条件预编译指令链接不同版本的LIB文件。因为,在Debug方式下,产生的LIB文件是Debug版本,如Regd.lib;在Release方式下,产生的LIB文件是Release版本,如Regr.lib。
       当应用程序对DLL的LIB文件加载后,还需要把DLL对应的头文件(*.h)包含到其中,在这个头文件中给出了DLL中定义的函数原型,然后声明。

       在创建DllTest.exe文件之前,要先将MyDll.dll和MyDll.lib拷贝到当前工程所在的目录下面,也可以拷贝到windows的System目录下。如果DLL使用的是def文件,要删除TestDll.h文件中关键字extern "C"。TestDll.h文件中的关键字Progam commit是要Visual C+的编译器在link时,链接到MyDll.lib文件,当然,开发人员也可以不使用#pragma comment(lib,"MyDll.lib")语句,而直接在工程的Setting->Link页的Object/Moduls栏填入MyDll.lib既可。
2.显式链接

  显式链接是应用程序在执行过程中随时可以加载DLL文件,也可以随时卸载DLL文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活性,对于解释性语言更为合适。不过实现显式链接要麻烦一些。在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态链接库调进来,动态链接库的文件名即是上述两个函数的参数,此后再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同在应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态链接库。下面是通过显式链接调用DLL中的Max函数的例子。


#include
#include
void main(void)
{
typedef int(*pMax)(int a,int b);
typedef int(*pMin)(int a,int b);
HINSTANCE hDLL;
PMax Max
HDLL=LoadLibrary("MyDll.dll");//加载动态链接库MyDll.dll文件;
Max=(pMax)GetProcAddress(hDLL,"Max");
A=Max(5,8);
Printf("比较的结果为%d\n",a);
FreeLibrary(hDLL);//卸载MyDll.dll文件;
}

  在上例中使用类型定义关键字typedef,定义指向和DLL中相同的函数原型指针,然后通过LoadLibray()将DLL加载到当前的应用程序中并返回当前DLL文件的句柄,然后通过GetProcAddress()函数获取导入到应用程序中的函数指针,函数调用完毕后,使用FreeLibrary()卸载DLL文件。在编译程序之前,首先要将DLL文件拷贝到工程所在的目录或Windows系统目录下。

  使用显式链接应用程序编译时不需要使用相应的Lib文件。另外,使用GetProcAddress()函数时,可以利用MAKEINTRESOURCE()函数直接使用DLL中函数出现的顺序号,如将GetProcAddress(hDLL,"Min")改为GetProcAddress(hDLL, MAKEINTRESOURCE(2))(函数Min()在DLL中的顺序号是2),这样调用DLL中的函数速度很快,但是要记住函数的使用序号,否则会发生错误。

 显式调用的方法很简单,调用LoadLibrary()和GetProcAddress()即可。

   HMODULE LoadLibrary(
       LPCTSTR lpFileName   // file name of module
   );
参数解释:lpFileName:指定要载入的动态链接库的名称。
          返回值:如何函数调用成功,则返回值是库模块的句柄,否则返回NULL。
FARPROC GetProcAddress(
  HMODULE hModule,    // handle to DLL module
  LPCSTR lpProcName   // function name
);
 
参数解释:hModule:包含此函数的DLL模块的句柄。LoadLibrary或者GetModuleHandle函数可以返回此句柄。
         lpProcName:包含函数名的以NULL结尾的字符串,或者指定函数的序数值。
         返回值:如果函数调用成功,返回值是DLL中的输出函数地址,否则返回NULL。
如下罗列出DLL显式调用的流程:
(1)使用LoadLibrary()加载指定名称的DLL。
(2)使用GetProcAddress()获取指定名称的函数地址,并将其转换成函数类型。
(3)通过函数地址调用函数。
(4)调用FreeLibrary()函数卸载DLL。
假设在DLL中有一导出函数:extern "C" __declspec(dllexport)  int tulip (int a ,int b);
在程序中调用DLL中的这一函数:
//定义一个函数指针 typedef int (  * FUNCTION )(int,int);    
int main()
{
    HINSTANCE hLib;
    hLib = ::LoadLibrary(_T("MyDll.dll"));
    
    FUNCTION tulipFunc; //定义一个函数指针变量
    tulipFunc =(FUNCTION )::GetProcAddress(hLib ,"tulip");  
    //调用dll里的函数    tulipFunc(8,10);
    ::FreeLibrary(hLib);
    return 0;
} 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!