在Linux和Windows上创建一个可移植的库

33
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

我有以下源代码,即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的目的是什么?

提前感谢。


5
这是一个宏,在C++编译时展开为extern "C",在C编译时则展开为extern或什么也不展开(我记不清了,但可以将其选中并按F12查看实际定义)。它的目的是指示编译器从您的库导出的符号中删除C++名称修饰。如果您已经将整个头文件包装在extern "C" {中,则无需添加该宏(假设gcc理解相同的块级别extern C,但我不确定)。 - WhozCraig
1
请阅读这个链接。您可能想要使用这种机制。 - n. m.
@ant2009 你的库必须从C中使用,还是可以限制为仅限于C++11? - John Bandela
看看pocoQt的源代码。它们都提供了适用于Windows和Linux的通用API库。 - Basile Starynkevitch
5个回答

31

这是一种为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宏中。


15

extern "C"基本上意味着您告诉编译器不要修改您的函数名称。 编码是对函数名称进行"编码"以供稍后执行的过程,在C和C++中有所不同,因为C ++可以具有相同名称的不同函数(通过重载等)。

在C++源代码中,extern“C”有什么作用?

一旦编译,这些函数可以从任何地方调用,但您可能需要确保在开始之前创建了什么类型的库(静态或动态)。

此外,我建议您不要像在同一文件中那样使用DEFINES来实现可移植性,因为您在开发中稍后可能会遇到维护或可读性问题。 我会创建一个定义完全适用于WIN和UNIX的接口的基本文件,然后创建两个其他库来实现接口,但针对不同的平台。

例如,您可以拥有: AbstractInterface.h, WinInterface.h, UnixInterface.h

然后根据平台只编译您需要的部分。


14

对于Linux系统,如果没有使用-fvisibility=hidden选项编译gcc,则默认情况下会导出函数,但静态函数除外。

使用-fvisibility=hidden选项编译gcc时,默认情况下不会导出任何函数,除非该函数被特别标记。

__attribute__ ((visibility ("default")))

对于Windows,导出函数需要使用修饰符

__attribute__ ((dllexport))

当使用导出函数时,它们必须被装饰。

__attribute__ ((dllimport))

你的帖子中的宏

__declspec(dllexport)

MSVC 支持这些特性,但是 GCC 也支持目标 Windows 平台,而不是使用上述的 __attribute__ 语法。

因此,跨 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
  • 确保共享对象或DLL项目必须使用-DBUILDING_DLL进行编译。
  • 依赖于您的共享对象或DLL的项目必须使用不带-DBUILDING_DLL进行编译。

有关可见性的更多详细信息,请阅读http://gcc.gnu.org/wiki/Visibility


4

您可以让CMake使用CMake的generate_export_header为编译器生成一个头文件,而不是自己编写一个头文件,如下所示(示例取自链接页面):

add_library(libfoo foo.cpp)
generate_export_header(libfoo)

#include "libfoo_export.h"
class LIBFOO_EXPORT FooClass {
    int bar;
};

4
由于多态的概念是C++语言特有的,所有在C++中定义的函数都会被名字混淆。也就是说,为了为每个重载的函数创建唯一的名称,"编译器"会修饰函数名。
由于名字混淆是由"编译器"处理的,并且没有规范来严格定义名字混淆规则,因此每个编译器都以不同的方式修饰名称。简单地说,gcc和msvc编译器为相同的代码创建不同的函数签名。您可以在维基百科文章here中进一步了解名称混淆。
您的module.h文件只是告诉编译器使用C风格的名称混淆或根本不使用名称混淆。由于这个指令,由gcc编译的库可以用于链接到在Visual Studio中编写的二进制文件。这将帮助您分发库的二进制文件而不是源代码。
另一方面,如果您不使用EXTERN_C指令,则链接到库的项目和库本身应该使用相同的编译器进行编译。例如,对于Linux编译,您必须使用gcc,对于Windows编译,您必须使用msvc,同时用于库和链接到该库的项目。

s/polymorphism/overloading/ - Matteo Italia
多态性是重载的超集; p - madrag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接