(已解决)C++ 加载一个具有依赖项的共享 Object 并访问它们的函数

(solved) C++ Load one Shared Object with dependencies and access their functions

一个程序应该加载一个依赖于其他 SO 的 SO。所有 SO 的所有导出函数都应由程序调用。 我发现了几个相关问题,但 none 明确处理了这种情况。

这是我正在尝试做的一个例子: 共有 3 个共享库和 1 个应用程序:

libtest1.so    has a extern "C" print function
 libtest2.so    has a base class "Base" with a function usig the C-function
 libtest3.so    has a derived class "Test" with a function using the C-function-
 DllTest        Application: loads the *.so's

这是应用程序应该做的

pHandle = OpenSharedLib("./libtest1.so");      # works
 OpenSymbol(pHandle, "GetName");            # works
 CloseLib(pHandle);

 pHandle = OpenSharedLib("./libtest2.so");      # error
 OpenSymbol(pHandle, "GetName");
 OpenSymbol(pHandle, "printBase");
 CloseLib(pHandle);

 pHandle = OpenSharedLib("./libtest3.so");
 OpenSymbol(pHandle, "GetName");
 OpenSymbol(pHandle, "printBase");
 OpenSymbol(pHandle, "print");
 CloseLib(pHandle);

错误是 dlopen() 由于未定义的符号而未能加载:./libtest2.so: undefined symbol: GetName"。 nm 输出显示符号丢失,但我没有找到如何防止它。

基本思想是有一个 'front SO' 收集所有类型的独立 SO 并为程序提供一个统一的库。在这个例子中,程序应该只加载 libtest3.so。然后它应该能够加载任何符号,只要它被任何单个 SO 公开。

我的问题:是否可以做我想做的事以及如何做?或者换句话说:我的错误是什么?

这是代码,下面给出了我用来编译它们的命令。

  lib1.h, lib1.cpp     for libtest1.so
  lib2.h, lib2.cpp     for libtest2.so
  lib3.cpp             for libtest3.so
  DllTest.cpp          the application

libtest1.so

header

extern "C" __attribute__((visibility("default"))) const char* GetName();

Cpp

#include <stdio.h>
#include <stdlib.h>
#include "lib1.h"
    
__attribute__((visibility("default"))) const char* GetName() 
{
   return "Hello from lib1";
} 

编译为

g++ -c -fvisibility=hidden -fPIC -o lib1.o  lib1.cpp
g++ -shared -o libtest1.so lib1.o

libtest2.so

Header

#include <stdio.h>
#include <stdlib.h>
    
class __attribute__((visibility("default"))) Base 
{
public:
 Base();
 virtual ~Base();

 const char* printBase();
 int nTest;
};

Cpp

#include <stdio.h>
#include <stdlib.h>
#include "lib2.h"

#include "lib1.h"   // for GetName()
    
Base::Base() 
{ nTest=1; }

 
Base::~Base()
{ }

const char* Base::printBase()
{   return GetName(); }

编译为

g++ -c -fvisibility=hidden -fPIC -o lib2.o  lib2.cpp
g++ -shared -o libtest2.so -L. -ltest1 lib2.o

libtest3.so

#include <stdio.h>
#include <stdlib.h>

#include "lib1.h"
#include "lib2.h"

class __attribute__((visibility("default"))) Test : public Base
{
public:
 Test();
 virtual ~Test();
 const char* print();
};

Test::Test()
{ }

Test::~Test()
{ }


const char* Test::print() {
 char* pChar = (char*)GetName();
 printf( "hello from lib3: %d", nTest);
 return "test3";
}

已编译

g++ -c -fvisibility=hidden -fPIC -o lib3.o  lib3.cpp
g++ -shared -o libtest3.so -L. -ltest1 -ltest2 lib3.o

** 正在加载应用程序 **

#include <stdio.h> 
#include <stdlib.h>  
#include <iostream>
#include <dlfcn.h> 

void OpenSymbol(void* pHandle, char* strName)
 {
 typedef char* (*pfnChar)(void);
 pfnChar pFunction   = NULL;
 char*  cError;

 printf(" Find symbol %s\n", strName);
 dlerror();
 pFunction = (pfnChar)dlsym( pHandle, strName );
 cError = dlerror();
 if (cError != 0)   {
  std::cout << cError << std::endl;
  exit(1);   }
 printf(" Exec symbol: %p\n", pFunction );
 std::cout << pFunction() << std::endl;
 }


void* OpenSharedLib(char* strName)
 {
 void*   pHandle; 
 char*  cError;

 printf(" open lib %s\n", strName);
 dlerror();
 pHandle = dlopen( strName, RTLD_NOW ); 
 cError = dlerror();
 if (cError != 0)  {
  std::cout << cError << std::endl;
  exit(1);  }
 printf(" found DLL %p\n", pHandle );
 return pHandle;
 }

void* CloseLib(void* pHandle)
 {  dlclose(pHandle);  }

main()
{
 void*   pHandle; 

 pHandle = OpenSharedLib("./libtest1.so");
 OpenSymbol(pHandle, "GetName");
 CloseLib(pHandle);

 pHandle = OpenSharedLib("./libtest2.so");
 OpenSymbol(pHandle, "GetName");
 OpenSymbol(pHandle, "printBase");
 CloseLib(pHandle);

 pHandle = OpenSharedLib("./libtest3.so");
 OpenSymbol(pHandle, "GetName");
 OpenSymbol(pHandle, "printBase");
 OpenSymbol(pHandle, "print");
 CloseLib(pHandle);

 std::cout << "done" << std::endl;
}

运行 nm -DC 表明对于最后两个库,一些符号没有导出。

...
000000000000057a T GetName
...
                 U GetName
                 U operator delete(void*, unsigned long)
000000000000094c T Base::printBase()
00000000000008da T Base::Base()
00000000000008da T Base::Base()
0000000000000920 T Base::~Base()
0000000000000902 T Base::~Base()
0000000000000902 T Base::~Base()
0000000000200e08 V typeinfo for Base
0000000000000969 V typeinfo name for Base
0000000000200de8 V vtable for Base
                 U vtable for __cxxabiv1::__class_type_info
...
                 U GetName
                 U printf
                 U operator delete(void*, unsigned long)
                 U Base::Base()
                 U Base::~Base()
0000000000000ab2 T Test::print()
0000000000000a2a T Test::Test()
0000000000000a2a T Test::Test()
0000000000000a86 T Test::~Test()
0000000000000a58 T Test::~Test()
0000000000000a58 T Test::~Test()
                 U typeinfo for Base
0000000000200df0 V typeinfo for Test
0000000000000b0f V typeinfo name for Test
0000000000200dd0 V vtable for Test
                 U vtable for __cxxabiv1::__si_class_type_info
 open lib ./libtest1.so
 found DLL 0x55965d711ea0
 Find symbol GetName
 Exec symbol: 0x7f902c38157a
Hello from lib1
 open lib ./libtest2.so
./libtest2.so: undefined symbol: GetName

选择答案后编辑 代码无法正常工作有两个主要问题。首先加载失败是因为动态加载器没有找到依赖的sharedobject,即加载libtest2.so时定位不到libtest1.so,加载libtest3.so时定位不到libtest2.solibtest1.so。这可以通过在链接期间添加路径来解决。其次,访问像 类 这样的非 extern "C" object 需要一个必须在共享 object 中创建的 object。因此必须重构lib2.h/cpplib2.h/cppDllTest.cpp

为方便起见,这里是完整的编译序列以及答案中的更正

g++ -c -fvisibility=hidden -fPIC -o lib1.o  lib1.cpp
g++ -shared -o libtest1.so lib1.o
  
g++ -c -fvisibility=hidden -fPIC -o lib2.o  lib2.cpp
g++ -shared -o libtest2.so -Wl,-rpath,$PWD -L.lib2.o -ltest1 

g++ -c -fvisibility=hidden -fPIC -o lib3.o  lib3.cpp
g++ -shared -o libtest3.so -Wl,-rpath,$PWD -L. lib3.o -ltest1 -ltest2 

g++ -o DllTest DllTest.cpp -ldl

有了这个,代码允许使用 const char* GetName() 函数,无论三个共享 object 被加载。

首先,更改 link-commands

中参数的顺序
g++ -g -shared -o libtest2.so -L. lib2.o -ltest1
g++ -g -shared -o libtest3.so -L. lib3.o -ltest1 -ltest2

注意:every linker 没有必要,但较新的 gold linker 只解析 “从左到右”。 还有一个 -Wl,--no-undefined 选项,这对捕获这些错误非常有用。

然后将rpath添加到这些命令中,以便共享对象找到它们的依赖项运行-time:

g++ -g -shared -o libtest2.so "-Wl,-rpath,${PWD}" -L. lib2.o -ltest1
g++ -g -shared -o libtest3.so "-Wl,-rpath,${PWD}" -L. lib3.o -ltest1 -ltest2

在 linkage 之后,readelf -d 应该像这样显示 RPATH:

$ readelf -d libtest3.so |head -n10

Dynamic section at offset 0xdd8 contains 28 entries:
 Tag        Type                 Name/Value
 0x0000000000000001 (NEEDED)     Shared library: [libtest1.so]
 0x0000000000000001 (NEEDED)     Shared library: [libtest2.so]
 0x0000000000000001 (NEEDED)     Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)     Shared library: [libc.so.6]
 0x000000000000001d (RUNPATH)    Library runpath: [/home/projects/proba/CatMan]

(还有libtool处理共享库。)

请注意,由于 name-mangling,应执行以下更改(尽管它是特定于编译器的)

- OpenSymbol(pHandle, "printBase");
+ OpenSymbol(pHandle, "_ZN4Base9printBaseEv");