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什么?

提前谢谢了,

4

5 回答 5

30

这是为 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"使用宏。用于告诉链接器使用链接而不是. C 链接是跨编译器的标准,而 C++ 不是,将 DLL 的使用限制为使用相同编译器构建的应用程序。EXTERN_Cextern "C"CC++

无需将返回类型集成到 API 宏中。

于 2013-10-23T16:46:29.197 回答
15

extern "C" 基本上意味着您告诉编译器不要破坏您的函数名称。修饰是“编码”函数名称以供以后执行的过程,并且在 C 和 C++ 中完全不同,因为 C++ 可以具有具有相同名称的不同函数(通过重载等......)。

在 C++ 源代码中,extern "C" 的作用是什么?

编译后,可以从任何地方调用这些函数,但您可能想在开始之前确定您正在创建哪种库(静态或动态)。

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

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

然后根据平台只编译你需要的那些。

于 2013-10-18T13:23:45.450 回答
14

对于 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
  • 确保必须 使用-DBUILDING_DLL 编译共享对象或 DLL 项目。
  • 依赖于您的共享对象或 DLL 的项目必须在没有-DBUILDING_DLL的情况下进行编译

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

于 2013-10-29T18:51:46.457 回答
4

由于 c++ 语言特有的多态性概念,c++ 中定义的所有函数都被破坏了。即,要为每个被覆盖的函数创建唯一的名称,“编译器”会修饰函数名称。

由于名称修饰由“编译器”处理,并且没有规范来严格定义名称修饰规则,因此每个编译器都以不同的方式修饰名称。简单地说,gcc 和 msvc 编译器为相同的代码创建不同的函数签名。您可以在此处的 wiki 文章中阅读有关名称修饰的更多信息。

您的 module.h 文件只是告诉编译器使用 c 样式名称重整或根本不重整。由于这个指令,由 gcc 编译的库可用于链接到在 Visual Studio 中编写的二进制文件。这将帮助您分发库的二进制文件而不是源代码。

另一方面,如果不使用 EXTERN_C 指令,则库和链接到该库的项目应使用相同的编译器进行编译。例如,对于库和链接到该库的项目,您必须使用 gcc 进行 linux 编译和使用 msvc 进行 windows 编译。

于 2013-10-23T10:38:31.923 回答
3

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

add_library(libfoo foo.cpp)
generate_export_header(libfoo)
#include "libfoo_export.h"
class LIBFOO_EXPORT FooClass {
    int bar;
};
于 2017-06-29T17:00:30.633 回答