【问题标题】:Creating a portable library to run on both linux and windows创建可在 linux 和 windows 上运行的可移植库
【发布时间】:2013-10-25 11:33:59
【问题描述】:
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 的用途是什么?

非常感谢,

【问题讨论】:

  • 它是一个宏,对于 C++ 编译解析为 extern "C",对于 C 编译解析为 extern 或无。 (老实说,我不记得是哪个,但突出显示它并按 F12,它应该带你到实际定义)。它的 目的 是指示编译器从从库中导出的符号中删除 C++ 名称修饰。不,如果您已经将整个标头包含在 extern "C" { 中,则可以消除它(假设 gcc 理解相同的 block-extern-C,而且我不记得是否确实如此)。
  • 请阅读this。您可能想使用这种机制。
  • @ant2009 您的库是否必须从 C 中使用,或者您可以将其限制为 C++11?
  • 查看pocoQt源代码。两者都为 Windows 和 Linux 提供具有通用 API 的库。

标签: c++ c compatibility


【解决方案1】:

除了自己编写头文件之外,您还可以让 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;
};

【讨论】:

    【解决方案2】:

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

    更多详情请阅读http://gcc.gnu.org/wiki/Visibility

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        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

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

        【讨论】:

          【解决方案5】:

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

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

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

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

          【讨论】:

          • s/polymorphism/overloading/
          • 多态是重载的超集 ;p
          猜你喜欢
          • 1970-01-01
          • 2019-04-23
          • 2016-02-04
          • 2013-11-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多