一直在鼓捣DLL,每天的工作都是调试一个一个的DLL,往DLL里面添加自己的代码,但是对于DLL一直不太了解啊!今天一查资料,才发现自己对于DLL编写的一些基本知识也不了解。要学习,这篇文章先总结DLL的导出函数的方法。

1. 首先说一下如何建立一个普通的DLL工程!(以VS2008为例)

New Project  -->  Win32 标签 --> 填写工程名称 -->  点 OK,进入创建 Widzard  -->  Next 进入第二步 -->  Application Type 中选择选择Dll 单选按钮。--> 在Additional Options中可以根据自己需求,确定是生成空工程,是否需要导出符号。

都不勾选,后期完全自己添加,看到的是一个DLL框架被创建,只有一个DLL函数,在DLL被加载时,由线程调用该函数,做初始化。如下图所示:

很简单,不多说了!

勾选不同的额外选项的情况,不再说明,自己可以尝试创建一下,看有什么区别。

2. 导出模块中符号的三种方法

在说三种导出方式之前,先说一点关于VS编译器的东西。VS编译器,默认创建出来的文件为 .cpp,即它默认的编译方式是C++。因此一般情况下,写函数时随时声明函数,随时使用,这是C++编译方式的好处,也是很多书上所提倡的变量使用方式,到使用时再定义,声明。而如果是 .c 文件,会被VS按照C的方式进行编译。会有什么区别呢?就是函数,变量名字的命名区别。编译器编译源文件后,会将源文件中用到的符号进行修改,C的方式是在符号(Symbols,函数名称,变量名称等)的前面加下划线,对于函数名称,后面会跟着@Num,Num为传参所占用的大小。而C++为了实现函数重载,对函数实行了变名机制(具体机制不再详解释了,可以搜索一下),比如函数名称,在变名中会体现函数源代码中名称,函数参数个数,参数类型,返回值等信息。因此用C方式编译的DLL库是无法使用C++方式编译的程序直接连接调用的;同理反过来亦如此。在编程中就有了extern "C" 的出现。如果C++项目中,要调用以C编译方式编译出来的DLL库,则需要将要导入的函数用extern "C" 的方式声明,以便这些函数在C++项目中被编译为C编译方式的函数名称,在C的lib库中可以找到相应的符号,链接。反过来,对于C++方式编译的DLL,则无法以静态链接的方式在C程序中调用;主要还是因为C编译方式编译出来的函数,无法链接到C++方式编译的DLL,及相应的lib上。  C++和C中的变名具体的内容,还和函数的调用方式相关__cdecl / __stdcall / _fastcall 几个方式的修饰符号也不相同。

但是在这种情况下,还是可以通过动态引入的方式,使用GetProcAddress 函数根据函数名称获取函数地址,然后调用相应的函数。

在X64上又有不同的解释,X64编译的函数不变名。前面即没有下划线,函数名结尾处也没有参数空间大小,当然也不会类似C++的变名方式。

通过导出符号导出

  1. _declspec(dllexport) int MyFunction(int a, char c, char* pInfo)
  2. {
  3. char szInfo[MAX_PATH] = {};
  4. sprintf(szInfo, "a=%d, c=%c, Info: %s", a, c, pInfo);
  5. OutputDebugStringA(szInfo);
  6. return 0;
  7. }
_declspec(dllexport) int MyFunction(int a, char c, char* pInfo)
{
char szInfo[MAX_PATH] = {};
sprintf(szInfo, "a=%d, c=%c, Info: %s", a, c, pInfo);
OutputDebugStringA(szInfo); return 0;
}

按照如上的方式,MyFunction 函数将被DLL导出,C/C++编译方式下,分别为导出为如下的函数(可以使用PE解析工具查看):

如下为C++的编译方式导出的函数,可看到函数名称开始处有一个"?",结尾处为两个@@加上YAHHDPAD@Z ,他们共同表示了函数的参数,以及返回值。@@YA表示函数调用方式为 __cdecl,默认的调用方式。H表示返回值为Int,后面依次为参数列表的类型。(详细的内容可以搜索一下)

如下为C编译方式导出的函数,仅有函数名称。

如上所述,通常为了能够使得C++编译的模块能够在C和C++程序中都能调用,通常定义如下的宏,用于声明函数。一方面将函数导出,同时使得无论C++程序编译,还是C程序编译的DLL都按照C的编译方式导出函数。这样就可以满足C 和C++ 程序都可以调用。

#define DLLEXPORT_API extern "C" _declspec(dllexport)

或者

  1. #ifdef __cplusplus
  2. extern "C"{
  3. #endif
  4. 的函数名称
  5. #ifdef __cplusplus
  6. };
  7. #endif
        #ifdef __cplusplus
extern "C"{
#endif
// 要导出的函数名称
#ifdef __cplusplus
};
#endif

通过Link选项设置导出函数

通过linker 选项来导出函数,通过这种方式可以将函数进行改名,即将在C++编译方式下导出的名称导出为一个指定的名称,而不用带有? @ 等字符的函数原名。

  1. #pragma comment(linker, "/EXPORT:MyFunction=?MyFunction@@YAHHDPAD@Z")
  2. int MyFunction(int a, char c, char* pInfo)
  3. {
  4. char szInfo[MAX_PATH] = {};
  5. sprintf(szInfo, "Dll module: a=%d, c=%c, Info: %s\n", a, c, pInfo);
  6. OutputDebugStringA(szInfo);
  7. return 0;
  8. }
#pragma comment(linker, "/EXPORT:MyFunction=?MyFunction@@YAHHDPAD@Z")

int MyFunction(int a, char c, char* pInfo)
{
char szInfo[MAX_PATH] = {};
sprintf(szInfo, "Dll module: a=%d, c=%c, Info: %s\n", a, c, pInfo);
OutputDebugStringA(szInfo); return 0;
}

如下图所示,上面代码中的函数,以C++的方式编译完成后,和之前的以C++方式导出函数的名称相同,我们使用 #pragma comment的形式,将该函数以函数原名称的形式导出。这样可以方便引用。

通过.def 文件导出函数

最正规的方法,还是通过定义.def 文件来导出函数。将要导出的函数,写到.def 文件 EXPORTS 字段下,即可将函数导出。在连接器阶段,使用/def 连接器选项调用.def 文件。def的内容对应于link 选项,.def 的语法如下所述:

×  语句,属性,关键字和指定的标识符区分大小写

×  使用一个,多个空格,制表符或换行符将语句关键字同其他参数分开和将语句分开。指定参数的冒号或等号前后可以有零个,多个空格,制表符或换行。

×  NAME 和 LIBRARY 语句,必须位于所有的其他的语句之前。

×  注释以 分号 开始

EXPORTS 语句的语法如下:

EXPORTS

entryname[=internalname] [@ordinal  [NONAME] ]  [PRIVATE]  [DATA]

entryname 是要导出的函数名或变量名称。这一项必须有。

=internalname 表示将一个名字为internalname的函数导出为entryname的函数。

@ordinal  允许指定是序号导出函数,而不是以函数名导出。.lib 文件中包含了序号和函数之间的映射。

NONAME为可选项,该关键字指明,只允许按照序号导出。

PRIVATE 可选项,表示禁止将entryname放到LINK生成的导入库中。

DATA 可选项,表示导出的为数据,而非代码。

示例:

  1. EXPORTS
  2. DllCanUnloadNow         @1          PRIVATE     DATA
  3. DllWindowName  = Name                           DATA
  4. DllGetClassObject       @4  NONAME      PRIVATE
  5. DllRegisterServer       @7
  6. DllUnregisterServer
EXPORTS
DllCanUnloadNow @1 PRIVATE DATA
DllWindowName = Name DATA
DllGetClassObject @4 NONAME PRIVATE
DllRegisterServer @7
DllUnregisterServer

LIBRARY 选项

LIBRARY  [library] [BASE=address]

library 表示让link创建DLL,同时创建导入库。

BASE=address 设置操作系统用来加载DLL的基址。

SECTIOINS 选项

SECTIONS

definitions

用于指定一些节区的属性,比如设置节区为共享节区等。

3. 基于MFC框架的DLL

可以很方便地到处全局变量,导出函数,导出类。有了MFC的支持,方便不少。这块已经不怎么用了吧,我平时很难涉及到,不想再总结了。有兴趣的可以找资料看一下。

ATL同样也有很多,基于ATL的COM模块,基本都是建立一个DLL模块。这块也不想多说了,无非是将ATL的框架,模板集成进去了,DLL作为容器而已。其实这个和DLL的知识没什么关系,完全不同的两部分内容,需要分开学。我觉得是酱紫的,哈哈哈哈!

没准以后工作中会遇到,遇到再说吧!^_^! 以够用为原则,学多了也记不住!

By  AndyGuo @ 2015-08-11 午

MFC DLL 导出函数的定义方式的更多相关文章

  1. dll导出函数的两种方式的比较

    最初的网页链接已经挂了, 在此贴一个中间的转载链接 https://blog.csdn.net/zhazhiqiang/article/details/51577523 一 概要 vs中导出 dll的 ...

  2. DLL导出函数和类的定义区别 __declspec(dllexport)

    DLL导出函数和类的定义区别 __declspec(dllexport) 是有区别的, 请看 : //定义头文件的使用方,是导出还是导入 #if defined(_DLL_API) #ifndef D ...

  3. AFX_MANAGE_STATE(AfxGetStaticModuleState())DLL导出函数包含MFC资源

    AFX_MANAGE_STATE(AfxGetStaticModuleState()) 先看一个例子: .创建一个动态链接到MFC DLL的规则DLL,其内部包含一个对话框资源.指定该对话框ID如下: ...

  4. dll 导出函数名的那些事

    dll 导出函数名的那些事 关键字: VC++  DLL  导出函数 经常使用VC6的Dependency或者是Depends工具查看DLL导出函数的名字,会发现有DLL导出函数的名字有时大不相同,导 ...

  5. C# 遍历DLL导出函数

    C#如何去遍历一个由C++或E语言编写的本地DLL导出函数呢 不过在这里我建议对PE一无所知的人 你或许应先补补这方面的知识,我不知道为什么PE方面的 应用在C#中怎么这么少,我查阅过相关 C#的知识 ...

  6. Dll 导出函数那些破事

    经常使用VC6的Dependency查看DLL导出函数的名字,会发现有DLL导出函数的名字有时大不相同,导致不同的原因大多是和编译DLL时候指定DLL导出函数的界定符有关系. VC++支持两种语言:即 ...

  7. DLL导出函数

    使用DEF文件从DLL导出 模块定义(.def)文件时包含一个或多个描述DLL各种属性的Module语句的文本文件.如果不使用_declspec(dllexport)关键字导出DLL的函数,则DLL需 ...

  8. 动态链接库DLL导出函数并导入使用

    动态链接库DLL导出函数并导入使用 本文完全参考自<vs2008制作dll笔记,回带值样例>. 首先制作DLL文件,在vs2010中新建Win32控制台项目,选择DLL选项,简历头文件,源 ...

  9. 使用dumpbin命令查看dll导出函数及重定向输出到文件【轉】

    查看dll导出函数,一般使用Viewdll等第三方工具. VS开发环境中,可以查看32位和64位的dll.具体使用方法如下: 1. 进入VS开发环境,然后Tools -> Visual stud ...

随机推荐

  1. CSL 的密码(后缀数组)

    CSL 的密码 题目传送门 解题思路 后缀数组.对于每一个后缀\(k\)都有\(n - k + 1\)个前缀,把所有不和前一个(排序后的)公共且长度大于\(m\)的前缀个数加起来. 代码如下 #inc ...

  2. JQuery判断radio(单选框)是否选中和获取选中值

    一.设置选中方法 代码如下: $("input[name='名字']").get(0).checked=true; $("input[name='名字']"). ...

  3. javaIO流(三)--IO深入操作

    一.字符编码 在计算机的世界中,本质上只认识0,1的字节数据,如果要想描述一些文字的编码就需要对这些二进制的数据进行组合,就需要对二进制的数据进行组合,所以才有了现在可看见的中文显示,但是在进行编码的 ...

  4. 插件化框架解读之四大组件调用原理-Service(三)下篇

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本文将继续通过Service调用原理来解读Replugin插件化 ...

  5. Git忽略已经跟踪的文件 转摘:http://blog.csdn.net/huguohuan/article/details/7380349

    某工程project用Git管理代码,但是在他的根目录下有个配置文件,比如project.iws是不需要git每次跟踪它的修改记录的. 一般做法是在.gitignore文件中添加一行 project. ...

  6. Codeforces 1100E 拓扑排序

    题意及思路:https://blog.csdn.net/mitsuha_/article/details/86482347 如果一条边(u, v),v的拓扑序小于u, 那么(u, v)会形成环,要反向 ...

  7. Robot Framework使用技巧之内部变量

    [转载] 1.变量的使用 变量可以在命令行中设置,个别变量设置使用--variable (-v)选项,变量文件的选择使用--variablefile (-V)选项. 通过命令行设置的变量是全局变量,对 ...

  8. linux CentOS7 nginx nginx-rtmp-module搭建直播

    直播配置 1. 安装 Nginx 依赖软件 yum -y install gcc gcc-c++ autoconf automake make yum -y install zlib zlib-dev ...

  9. Yum与RPM

    Yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的Shell前端软件包管理器.基于RPM包管理,能够从指定的服务器自动下载 ...

  10. array_reduce — 用回调函数迭代地将数组简化为单一的值

    定义和用法 array_reduce() 函数向用户自定义函数发送数组中的值,并返回一个字符串. 注释:如果数组是空的且未传递 initial 参数,该函数返回 NULL. 说明 array_redu ...