看到 C/C++初學者 FB 社團的 發問
情境是沒有 header file 下要如何使用 .so 編譯 C++ 的程式。
因為沒有後續,所以也不確定是否解決,不過最後的回答的確是突破口。

我之前有記錄過 “ 107.08.25 C 語言 static 和 shared 函式庫” 不過是純 C 不適用此問題。以下為此問題的解決過程記錄。

TL;DR

C++ (g++) 可行版本

問題定義

手邊有 libabc.so, libabc.a,沒有 *.h, *.c

這邊就使用 libabc.so 為主要問題 (因為解決後,libabc.a 可用相同步驟解決)

libabc.so 中有 xyz() 可供呼叫

為了重現問題以下為 libabc 的測試程式 (因為問題中提到用 gcc 與 c 是可以使用,代表該函式庫有使用 extern "C" {...}):

  • abc.cpp:
    #include <iostream>
    extern "C" {
        void xyz() { std::cout << "xyz" << std::endl; }
    }
    
  • 產生 libabc.so:
    $ g++ -c -fPIC -o libabc.o abc.cpp
    $ g++ -shared -o libabc.so libabc.o && rm libabc.o
    

C (gcc) 可行版本

  • test0.c
    #include <stdio.h>
    extern void xyz();
    int main(int argc, char *argv[]) {
        xyz();
        return 0;
    }
    
  • 編譯並執行
    $ gcc -o test test0.c -L. -labc -Wl,-rpath=.
    $ ./test
    xyz
    

C++ (g++) 無法鏈結版本

  • test1.cpp
    #include <iostream>
    extern void xyz();
    int main(int argc, char *argv[]) {
        xyz();
        return 0;
    }
    
  • 編譯
    $ g++ -o test test1.cpp -L. -labc -Wl,-rpath=.
    /tmp/ccL0SPL8.o: In function `main':
    t1.cpp:(.text+0x10): undefined reference to `xyz()'
    collect2: error: ld returned 1 exit status
    
  • 找出問題: 可以看到需要 _Z3xyzv 這個 symbol 但是在 linker 鏈結時找不到,因為 libabc 所提供的 xyz() 有定義 extern "C" {...} 所以其 symbol 是 xyz
    $ g++ -c test1.cpp
    $ nm test1.o
                     U _GLOBAL_OFFSET_TABLE_
    0000000000000064 t _GLOBAL__sub_I_main
                     U _Z3xyzv
    000000000000001b t _Z41__static_initialization_and_destruction_0ii
                     U _ZNSt8ios_base4InitC1Ev
                     U _ZNSt8ios_base4InitD1Ev
    0000000000000000 r _ZStL19piecewise_construct
    0000000000000000 b _ZStL8__ioinit
                     U __cxa_atexit
                     U __dso_handle
    0000000000000000 T main
    

C++ (g++) 可行版本

  • test2.cpp
    #include <stdio.h>
    extern "C" void xyz();
    int main(int argc, char *argv[]) {
        xyz();
        return 0;
    }
    
  • 編譯並執行
    $ gcc -o test test2.cpp -L. -labc -Wl,-rpath=.
    $ ./test
    xyz
    
  • 確認
    $ g++ -c t1.cpp
    $ nm t1.o
                     U _GLOBAL_OFFSET_TABLE_
    0000000000000064 t _GLOBAL__sub_I_main
    000000000000001b t _Z41__static_initialization_and_destruction_0ii
                     U _ZNSt8ios_base4InitC1Ev
                     U _ZNSt8ios_base4InitD1Ev
    0000000000000000 r _ZStL19piecewise_construct
    0000000000000000 b _ZStL8__ioinit
                     U __cxa_atexit
                     U __dso_handle
    0000000000000000 T main
                     U xyz
    

C++ (g++) dlopen 噁心可行版本

原先我覺得沒有 header file (*.h) 應該無法做到,所以就先繞了遠路用 dlopen 1 解決,後來才想到可以先用 extern 宣告尚未定義的函式。需要注意的是 dlopen 可能會影響效能。

  • test3.cpp
    #include <dlfcn.h>
    #include <iostream>
    
    using func_t = void (*)();
    
    int main(int argc, char *argv[]) {
    
        const char *error;
        auto lib = dlopen("libabc.so", RTLD_LAZY | RTLD_GLOBAL);
        if ((error = dlerror()) != NULL) {
            std::cerr << error;
            exit(EXIT_FAILURE);
        }
    
        func_t xyz = (func_t)dlsym(lib, "xyz");
        if ((error = dlerror()) != NULL) {
            std::cerr << error;
            exit(EXIT_FAILURE);
        }
        xyz();
        return EXIT_SUCCESS;
    }
    
  • 編譯並執行
    $ g++ -o test test3.cpp -ldl -Wl,-rpath=.
    $ ./test
    xyz
    

References

Footnotes

  1. dlopen(3) — Linux manual page ↩︎

  • ⊛ Back to top
  • ⊛ Go to bottom