gcc (GCC) 4.7.2
您好,
我正在创建一个将在linux上编译的共享库和一个将在Windows上使用相同的源代码编译的DLL。所以我正在为linux和windows创建一个可移植的库。
在我的库头文件中就是这个,即module.h
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
#define LIB_INTERFACE(type) EXTERN_C __declspec(dllexport) type
#else
#define LIB_INTERFACE(type) type
#endif
LIB_INTERFACE(int) module_init();
#ifdef __cplusplus
}
#endif
在源代码中我有以下ie module.c
#include "module.h"
LIB_INTERFACE(int) module_init()
{
/* do something useful
return 0;
}
在我的测试应用程序中将链接并使用此模块。所以我有这个:
#include "module.h"
int main(void)
{
if(module_init() != 0) {
return -1;
}
return 0;
}
1)我上面所做的是为linux和windows创建可移植库的正确实现吗?
2)我只是想知道我已经将函数包装在extern "C"
中,以便可以从已用C ++编译的程序中调用该库。我是否还需要以下EXTERN_C
:
#define LIB_INTERFACE(type) EXTERN_C __declspec(dllexport) type
3)EXTERN_C
的目的是什么?
非常感谢,
答案 0 :(得分:23)
这是为Windows导出DLL API并且仍然支持Linux的典型方法:
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
# ifdef MODULE_API_EXPORTS
# define MODULE_API __declspec(dllexport)
# else
# define MODULE_API __declspec(dllimport)
# endif
#else
# define MODULE_API
#endif
MODULE_API int module_init();
#ifdef __cplusplus
}
#endif
在DLL源代码中:
#define MODULE_API_EXPORTS
#include "module.h"
MODULE_API int module_init()
{
/* do something useful */
return 0;
}
您的申请来源是正确的。
使用上述模型,在Windows上DLL将导出API,而应用程序将导入它。如果不在Win32上,则会删除__declspec
装饰。
由于标头将整个接口包装在extern "C"
中,因此不需要在每个接口上使用EXTERN_C
宏。 extern "C"
用于告诉链接器使用C
链接而不是C++
。 C链接是编译器的标准,而C ++则不是,将DLL的使用限制为使用相同编译器构建的应用程序。
无需将返回类型集成到API宏中。
答案 1 :(得分:13)
extern“C”基本上意味着你告诉编译器不要破坏你的函数名。 Mangling是“编码”函数名以供以后执行的过程,在C和C ++中完全不同,因为C ++可以具有不同的具有相同名称的函数(通过重载等...)。
In C++ source, what is the effect of extern "C"?
编译完成后,可以从任何地方调用这些函数,但是在开始之前,您可能想要确定要创建的库(静态或动态)。
另外,我建议您不要像在同一文件中那样使用DEFINES以实现可移植性,因为您在开发后期可能会遇到维护或可读性问题。 我将创建一个定义接口的基本文件,该接口完全可以移植到WIN和UNIX,然后创建另外两个实现接口但不同平台的库。
例如,您可以: AbstractInterface.h,WinInterface.h,UnixInterface.h
然后根据平台编译你需要的那些。
答案 2 :(得分:12)
对于Linux,没有-fvisibility = hidden的gcc将默认导出函数,静态函数除外。
使用-fvisibility = hidden时,除了由
修饰的函数外,gcc默认不会导出任何函数__attribute__ ((visibility ("default")))
对于Windows,导出的函数由
修饰__attribute__ ((dllexport))
使用导出的函数时,必须用
进行修饰__attribute__ ((dllimport))
帖子中的宏
__declspec(dllexport)
MSVC支持。
所以交叉的linux和windows宏如下:
#if defined _WIN32 || defined __CYGWIN__ || defined __MINGW32__
#ifdef BUILDING_DLL
#ifdef __GNUC__
#define DLL_PUBLIC __attribute__ ((dllexport))
#else
#define DLL_PUBLIC __declspec(dllexport) // Note: actually gcc seems to also supports this syntax.
#endif
#else
#ifdef __GNUC__
#define DLL_PUBLIC __attribute__ ((dllimport))
#else
#define DLL_PUBLIC __declspec(dllimport) // Note: actually gcc seems to also supports this syntax.
#endif
#endif
#define DLL_LOCAL
#else
#if __GNUC__ >= 4
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
#define DLL_LOCAL __attribute__ ((visibility ("hidden")))
#else
#define DLL_PUBLIC
#define DLL_LOCAL
#endif
#endif
答案 3 :(得分:3)
由于c ++语言特有的多态性概念,c ++中定义的所有函数都被破坏了。即,为每个重写函数创建唯一名称,“编译器”修饰函数名称。
由于名称修改由“编译器”处理,并且没有严格定义名称修改规则的规范,因此每个编译器以不同方式修饰名称。简而言之,gcc和msvc编译器为相同的代码创建不同的函数签名。你可以在wiki文章here上进一步阅读关于名称修改的内容。
你的module.h文件只是告诉编译器使用c样式名称修改或根本没有修改。由于该指令,gcc编译的库可用于链接到Visual Studio中编写的二进制文件。这将帮助您分发库的二进制文件而不是源代码。
另一方面,如果不使用EXTERN_C指令,则应使用相同的编译器编译链接到库的库和项目。例如,你必须使用gcc进行linux编译,使用msvc进行windows编译,以便链接到该库和链接到该库的项目。
答案 4 :(得分:2)
除了自己编写头文件外,您还可以让CMake使用CMake's generate_export_header生成这样的构建编译器(从链接页面获取示例):
add_library(libfoo foo.cpp)
generate_export_header(libfoo)
#include "libfoo_export.h"
class LIBFOO_EXPORT FooClass {
int bar;
};