今天使用Unrar.dll,在调用RARProcessFileW时,VS总是提示“error LNK2001: 无法解析的外部符号”。

Unrar.dll中是使用 extern "C" 对外输出函数,测试发现,其他函数可以正常调用,只有RARProcessFileW有此错误。

最后检查发现在.def文件中没有导出这个文件。

EXPORTS
RAROpenArchive
RAROpenArchiveEx
RARCloseArchive
RARReadHeader
RARReadHeaderEx
RARProcessFile
RARProcessFileW
RARSetCallback
RARSetChangeVolProc
RARSetProcessDataProc
RARSetPassword
RARGetDllVersion

DLL的export是指将DLL中的函数和数据输出到其它程式中,以供其使用。

DLL的import是指使用DLL的程式引入DLL中的函数和数据。

DLL的export
DLL 中包含有一个表,称为export table(以下简称ET),其中包含了DLL中可以被外部程式使用的所有函数和数据的名字。只有记录在ET中的函数和数据才可以被外部程式所使用(如果 没有.DEF文件的话),其它所有没有记录在ET中的函数和数据都被视为是DLL私有的。因此,要将DLL中的函数和数据export只有两个方法:
1、为DLL创建一个.DEF文件(模块定义文件),并在build该DLL时使用这个.DEF文件。使用这种方法使你可以将函数按序号export。
2、在DLL中想要export的函数和数据定义前添加_declspec(dllexport)关键字(对于函数和变量定义,加在最前面;对于class定义,加在class关键字后),这样该函数和数据就会被添加到ET中。使用这种方法函数将按名字export。
 
在WINDOWS下,无论使用上述的哪一种方法,都必须要将export函数声明为_stdcall。
 
关于C和C++的兼容问题
如果要写C和C++兼容的DLL,因为在C和C++下使用了不同的名字修饰规则以及不同的调用约定,所以,如果DLL是用C编写和编译的,则在用于C++模块时,函数的声明前应加上extern “C”关键字,以告诉LINKER使用C外部连接(即按照C名字修饰规则在外部模块中寻找函数);反之,如果DLL是用C++编写和编译的,则在用于C模块时,函数的声明前要加上extern “C++”关键字。VC++通过_cplusplus宏来标识C++程式。如果是C++程式,VC编译器就会为你定义_cplusplus宏。所以在DLL中可以使用如下的技术来解决兼容问题:
#ifdef _cplusplus
extern “C” {
#endif
// 将所有的函数声明放在这里
#ifdef _cplusplus
}
#endif
.DEF文件
    .DEF文件是包含了DLL模块信息的文本文件。其语法结构如下:
    LIBRARY           DLL file name
    DESCRIPTION   “descriptions”
    EXPORTS
    Function names   @nums
    LIBRARY为关键字,后面紧跟关联的DLL文件名;
            DESCRIPTION后为可选的描述字符串,除了增加可读性外没什么用处;
            EXPORTS后是export函数的列表,首先是函数名,然后是@符号,后紧跟一十进制数,为该函数的标号,范围从1到DLL可export函数的总数。注意,这里的名字是经过名字修饰后的函数名字,如果是DLL是用C++写的话,那么就很郁闷了。
    如果是扩展DLL(extension DLL),并且通过.DEF文件export,那么必须在头文件中添加如下的语句:
         #undef AFX_DATA
        #define AFX_DATA AFX_EXT_DATA
        // 头文件中的其它内容
        #undef AFX_DATA
        #define AFX_DATA
这些语句确保一些MFC中内部使用的变量被export到外部程式中。例如:在class中通过DECLARE_DYNAMIC获得的CRuntimeClass变量。否则DLL将会无法正确地编译和连接,或外部程式无法正确连接到该DLL。
 
DLL的import
    外部程式的一个源文件要使用DLL中的函数和数据,就像要使用外部模块中的函数和数据一样,必须首先给出函数和数据的声明;对于class则要给出类的定 义。这就称为import。对于VC编译器,Import DLL的函数和数据的语法与一般的声明类似,但要在前面加上_declspec(dllimport)关键字(对于函数和变量声明,加在最前面;对于 class定义,加在class关键字后)。如果是函数,则该关键字是可选的,但使用该关键字有可能会导致编译器产生较高效的代码。但对于变量和 class,则必须使用该关键字。
    通过使用以下的技术,可以编写在.LIB文件和外部程序源文件通用的头文件:
        #ifdef _EXPORTING
        #define CLASS_DECLSPEC    __declspec(dllexport)
     #else
        #define CLASS_DECLSPEC    __declspec(dllimport)
        #endif
编译器提供的_EXPORTING宏可以用于标式该源文件来自DLL文件还是外部程式
 
 

一般来讲,在DLL编程过程中,对于导出的函数前 都需要加入 extern “C”,

extern 表示这是个全局函数,可以供各个其他的函数调用;

“C” 表示编译时按照 C编译器的方式进行编译,而不是C++。 C++的编译方式考虑了函数重载,所以对函数名进行了新的修饰,产生了所谓的破坏性命名。

不过,也有特殊情况,有三种例外情况可以不加extern   “C”: 
1。如果不是用C++编译器而是用C编译DLL,名字不会变,可以不加extern   "C" 
2。如果DLL的使用者知道是用C++编译器编译DLL,不加extern   “C”也可以,因为他知道名字改变的规则。调用GetProcAddress时,把函数名字改了就是了,改为修饰后的函数名。 如 fnDll1 改为 ?fnDll1@@YAHXZ。

例子关键代码如下: 
---------------------------- 
DLL部分: 
//   This   is   an   example   of   an   exported   function. 
DLL1_API   int   __cdecl   fnDll1(void) 

return   42; 

输出的修饰函数名为?fnDll1@@YAHXZ 

DLL1_API   int   __cdecl   fnDll1(int   a) 

return   42+a; 

输出的修饰函数名为?fnDll1@@YAHH@Z 
----------------------------- 
EXE部分: 
HINSTANCE   hModule   =   LoadLibrary("dll1.dll"); 
ASSERT(hModule); 
typedef   int   (*fnDll1)(); 
fnDll1   pfnDll1   =   NULL; 
//VERIFY(pfnDll1   =   (fnDll1)::GetProcAddress(hModule,   "fnDll1")); 
VERIFY(pfnDll1   =   (fnDll1)::GetProcAddress(hModule,   "?fnDll1@@YAHXZ")); 
ASSERT(pfnDll1()   ==   42); 

typedef   int   (*fnDll2)(int); 
fnDll2   pfnDll2   =   NULL; 
VERIFY(pfnDll2   =   (fnDll2)::GetProcAddress(hModule,   "?fnDll1@@YAHH@Z")); 
ASSERT(pfnDll2(3)   ==   45); 
--------------------------- 

 

3.上面的2太麻烦了。所以还有一种方法是使用def文件。

 (如果DLL使用的是def文件,要删除TestDll.h文件中关键字extern "C",即2者是不能共存的)。

def 文件(模板定义文件),第一个语句必须是 LIBRARY 语句,指出DLL的名字;

EXPORTS语句 列出被导出函数的名字;将要输出的函数罗列出来,这个函数名字必须与定义函数的名字完全一致,如此既可以得到

一个没有任何修饰符的函数名了。

被导出的函数 可以和一个序号相对应。定义序号时必须在数字前加一个@。例如  isRUINIan  @1      //IsRuiNian 函数对应序号为 1

这样的话,我们既可以GetProAddress(hinstance,“IsRuiNian”),也可以 GetProAddress(hinstance,(LPCSTR)1)实现调用。

DLL的Export和Import及extern "C"的更多相关文章

  1. export和import实现模块化

    export和import实现模块化 阅读目录 ES6的模块化的基本规则或特点: 下面列出几种import和export的基本语法: ES6导入的模块都是属于引用: 循环依赖的问题: 浏览器兼容: 参 ...

  2. 【从翻译mos文章】不再用par file如果是,export or import 包含大写和小写表名称表

    不再用par file如果是,export or import 包含大写和小写表名称表 参考原始: How to Export or Import Case Sensitive Tables With ...

  3. module.exports,exports,export和export default,import与require区别与联系【原创】

    还在为module.exports.exports.export和export default,import和require区别与联系发愁吗,这一篇基本就够了! 一.首先搞清楚一个基本问题: modu ...

  4. ES6中的export以及import的使用多样性

    模块功能主要由两个命令构成:export和import.export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能. 一.export导出模块使用部分的几种方式 一个模块就是一 ...

  5. ES6模块之export和import详解

    ES6中的模块即使一个包含JS代码的文件,在这个模块中所有的变量都是对其他模块不可见的,除非我们导出它.ES6的模块系统大致分为导出(export)和导入(import)两个模块. 1.模块导出(ex ...

  6. ES6 Module export与import复合使用

    export与import复合使用 基本语法 export {...} from '文件'; 等价于 import {...} from "文件": export {...} 先加 ...

  7. export 和import使用

    在JavaScript ES6中,export与export default均可用于导出常量.函数.文件.模块等,你可以在其它文件或模块中通过import+(常量 | 函数 | 文件 | 模块)名的方 ...

  8. export,import ,export default区别

    export,import ,export default区别 一.export,import ,export default ES6模块主要有两个功能:export和import export用于对 ...

  9. export,import ,export default 彻底弄痛

    ES6模块主要有两个功能:export和import 说白了就是一个淡出一个导入,就相当于以前的公共js样,哪个页面要用,就script 引入这个js  ,然后  无耻的调用这个js中的方法了. ex ...

随机推荐

  1. 学习笔记32—python常见问题及解决办法

    1.Anaconda3 中 Spyder 无法打开/点击没有反应 应对方法 1).通过pip安装pyqt5:pip install pyqt5 2).输入以下命令:spyder --new-insta ...

  2. windows 网卡配置的设置命令

    (1)设置为DHCP自动分配 netsh interface ip set address "本地连接"  dhcp netsh interface ip set dns &quo ...

  3. (转)linux各文件夹的作用

    原文地址:<linux各文件夹的作用> linux下的文件结构,看看每个文件夹都是干吗用的/bin 二进制可执行命令 /dev 设备特殊文件 /etc 系统管理和配置文件 /etc/rc. ...

  4. Memcached遇到的问题及解决办法

    1. memcached make: *** No targets specified and no makefile found. Stop. 其实是因为在安装libevent时增加了版本号导致的, ...

  5. legend2---开发日志4(常用的链接传值方式有哪些)

    legend2---开发日志4(常用的链接传值方式有哪些) 一.总结 一句话总结:常用的其实就是get和post,不过有具体细分 a标签 post表单 js方式拼接url 1.js正则尽量少匹配的符号 ...

  6. Linux 各种软件的安装-mysql篇

    作为一个长期混迹在windows圈的小白,当拿到一个新的linux服务器时,有点手足无措的赶脚.但是万事开头难嘛,Just Do It! 下面记录一下自己安装各种软件时遇到的坑.这一篇先讲mysql ...

  7. HTML 第九章总结

    前言 这一章节主要讲了关于 HTML 中关于留白的知识:在这一章节中,从大到小,有: margin border padding context 这四个部分. 关于margin和padding mar ...

  8. @Basic表示一个简单的属性 懒加载,急加载

    5.@Basic(fetch=FetchType,optional=true) 可选 @Basic表示一个简单的属性到数据库表的字段的映射,对于没有任何标注的getXxxx()方法,默认 即为 @Ba ...

  9. You Don't Know JS: this & Object Prototypes( 第2章 this)

    this is a binding made for each function invocation, based entirely on its call-site (how the functi ...

  10. 从早期 Spring Boot 版本升级

    如果你现在正在从早期的 Spring Boot 版本进行升级的话,请访问 “migration guide” on the project wiki 页面,这个页面提供了有关升级的详细指南.同时也请查 ...