關於 “將靜態庫改成動態庫” 的思考

在編寫任何一個框架(c++ 庫) 的時候,需要想好編譯配置:

  • 是否需要跨平臺:
    操作系統抽象層(條件編譯,系統時間,條件變量,鎖,文件讀寫,線程等等)
  • 靜態庫:
    頭文件&庫文件,區分好Debug/Release…
  • 動態庫:
    考慮好暴露哪些接口,需要在每一個暴露的接口處添加 DLL_PUBLIC 宏,所有暴露的接口都最好是純虛函數形式暴露等…
  • 動態庫的使用分爲隱式顯式:在 Windows 平臺,隱式地鏈接動態庫(在編譯的時候)需要通過 “import library” xxx.lib 和一個 xxx.dll; 而在 Linux 平臺則只需要 xxx.so 這一個庫, see link。顯示的使用動態庫(在運行的時候)就是通過 LoadLibraryLoadLibraryEx 直接打開*.dll文件(linux: dlopen 打開 *.so 文件)。

This is a typical way to export a DLL API for Windows and still support Linux:

#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

In the DLL source:

#define BUILDING_DLL
#include "module.h"

DLL_PUBLIC int exported_func()  // 在dll中暴露
{
    /* do something useful */
    return 0;
}

DLL_LOCAL int hidden_func()  // 不會在dll中暴露
{
    /* do something useful */
    return 0;
}

當然,在export一個c++函數時,需要考慮加上 extern "C",否則, Name mangling 會導致不同編譯器(或者不同版本的編譯器)生成不同的函數名,導致 GetProcAddress (linux: dlopen)找不到對應名字的函數,當然, 這樣也就不能使用重載函數了。

在暴露某一個類的時候,該類的所有成員都會暴露,如果使用了 標準庫的某些成員,會導致很多編譯錯誤,可以寫成這樣:

class DLL_PUBLIC ExportedClass
{
private:
    std::vector<int> *_integers;
public:
    ExportedClass()
    {
        _integers = new std::vector<int>();
    }
    ~ExportedClass()
    {
        delete _integers;
    }
};

注意,在Window平臺上 dll 和你的 exe 文件可能鏈接不同版本的 C runtime 庫,他們會使用不同的文件描述符、環境變量、本地語言設置,以及 不同的堆管理器,所以在哪個堆上new出來對象的就要在哪個堆上delete.

See DLL Boundaries: https://msdn.microsoft.com/en-us/library/ms235460.aspx

所以 stl 對象最好不要編譯進dll,或者按照該link 處理:https://stackoverflow.com/questions/4145605/stdvector-needs-to-have-dll-interface-to-be-used-by-clients-of-class-xt-war

總之,在設計接口的時候,考慮 不要 在基類中放置任何成員變量,以方便暴露出去,如下:

A C++ abstract interface (i.e., a C++ class that contains only pure virtual methods and no data members) tries to get the best of both worlds: a compiler independent clean interface to an object, and a convenient object oriented way of method calls. All that is required to do is to provide a header file with an interface declaration and implement a factory function that will return the newly created object instances. Only the factory function has to be declared with the __declspec(dllexport/dllimport) specifier. The interface does not require any additional specifiers.

// The abstract interface for Xyz object.
// No extra specifiers required.
struct IXyz
{
    virtual int Foo(int n) = 0;
    virtual void Release() = 0;
};

// Factory function that creates instances of the Xyz object.
extern "C" XYZAPI IXyz* APIENTRY GetXyz();

In the above code snippet, the factory function GetXyz is declared as extern “C”. It is required in order to prevent the mangling of the function name. So, this function is exposed as a regular C function, and can be easily recognized by any C-compatible compiler. This is how the client code looks like, when using an abstract interface:

#include "XyzLibrary.h"

...
IXyz* pXyz = ::GetXyz();

if(pXyz)
{
    pXyz->Foo(42);

    pXyz->Release();
    pXyz = NULL;
}

原理如圖:
在這裏插入圖片描述
copy from: https://www.codeproject.com/Articles/28969/HowTo-Export-C-classes-from-a-DLL

另外,編譯的時候需要考慮好 link dependency, check link: http://www.kaizou.org/2015/01/linux-libraries.html
如果 一個 static lib 依賴 一個 shared lib, 當這個 app link 的 static lib 的時候,需要同時加上 static lib 和 shared lib 鏈接選項。

To summarize, when linking an executable against:

a static library, you need to specify all dependencies towards other shared libraries this static library depends on explicitly on the link command.

a shared library, you don’t need to specify dependencies towards other shared libraries this shared library depends on, but you may need to specify the path to these libraries on the link command using the -rpath/-rpath-link options.

關於 Dynamic Linking 的過程,可以看一下 Load-time RelocationPIC(強烈推薦讀一讀):

  1. https://eli.thegreenplace.net/2011/08/25/load-time-relocation-of-shared-libraries
  2. https://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/

Other links:

  • Link1: https://www.codeproject.com/Articles/28969/HowTo-Export-C-classes-from-a-DLL
  • Link2: http://www.cplusplus.com/forum/general/120142/
  • Link3: https://stackoverflow.com/questions/22797418/how-do-i-safely-pass-objects-especially-stl-objects-to-and-from-a-dll?noredirect=1&lq=1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章