基于 Linux 的 C + + 动态共享库

这是 用 g + + 编译动态共享库的后续。

我试图在 Linux 上用 C + + 创建一个共享类库。我能够编译这个库,并且可以使用我找到的 给你给你教程调用一些(非类)函数。当我尝试使用库中定义的类时,我的问题就开始了。我链接到的第二个教程展示了如何加载用于创建库中定义的类的对象的符号,但是在 使用之前停止这些对象以完成任何工作。

有没有人知道一个更完整的教程来创建共享的 C + + 类库,也显示了如何 使用这些类在一个单独的可执行文件?一个非常简单的教程,显示对象的创建、使用(简单的 getter 和 setter 就可以了)和删除将是非常棒的。对某些开放源代码的链接或引用,说明如何使用共享类库也同样不错。


虽然来自 密码学Nimrodm的答案确实有效,但我想补充的是,自从问了这个问题后,我拿起了一份 开始 Linux 编程的副本,它的第一章有示例 C 代码和创建和使用静态库和共享库的很好的解释。这些例子可以通过 那本书的旧版本中的谷歌图书搜索找到。

267956 次浏览

下面显示了共享类库的示例。[ h,cpp ]和一个 main.cpp 模块使用该库。这是一个非常简单的示例,makefile 可以做得更好。但它确实有效,而且可能对你有帮助:

H 定义类:

class myclass {
int myx;


public:


myclass() { myx=0; }
void setx(int newx);
int  getx();
};

Cpp 定义了 getx/setx 函数:

#include "shared.h"


void myclass::setx(int newx) { myx = newx; }
int  myclass::getx() { return myx; }

Cpp 使用类,

#include <iostream>
#include "shared.h"


using namespace std;


int main(int argc, char *argv[])
{
myclass m;


cout << m.getx() << endl;
m.setx(10);
cout << m.getx() << endl;
}

以及生成 libshared.so 并将 main 链接到共享库的 makefile:

main: libshared.so main.o
$(CXX) -o main  main.o -L. -lshared


libshared.so: shared.cpp
$(CXX) -fPIC -c shared.cpp -o shared.o
$(CXX) -shared  -Wl,-soname,libshared.so -o libshared.so shared.o


clean:
$rm *.o *.so

要实际运行‘ main’并链接到 libshared.so 您可能需要指定加载路径(或者将其放在/usr/local/lib 或类似的地方)。

下面指定工作目录作为库的搜索路径并运行 main (bash 语法) :

export LD_LIBRARY_PATH=.
./main

要查看程序是否与 libshared.so 可以尝试 ldd:

LD_LIBRARY_PATH=. ldd main

我机器上的指纹:

  ~/prj/test/shared$ LD_LIBRARY_PATH=. ldd main
linux-gate.so.1 =>  (0xb7f88000)
libshared.so => ./libshared.so (0xb7f85000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e74000)
libm.so.6 => /lib/libm.so.6 (0xb7e4e000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0xb7e41000)
libc.so.6 => /lib/libc.so.6 (0xb7cfa000)
/lib/ld-linux.so.2 (0xb7f89000)

基本上,您应该在要在共享库中使用该类的代码中包含该类的头文件。然后,当您链接时,使用’-l’标志将您的代码链接到共享库。当然,这需要。所以要去操作系统能找到的地方。见 3.5. 安装和使用共享库

当您在编译时不知道要使用哪个库时,可以使用 dlsym。听起来不像是这里的情况。也许令人困惑的是,无论是在编译时还是在运行时(使用类似的方法)进行链接,Windows 都会调用动态加载的库?如果是这样,那么可以将 dlsym 视为 LoadLibrary 的等价物。

如果您确实需要动态加载这些库(例如,它们是插件) ,那么 本常见问题应该会有所帮助。

我的同学

#ifndef __MYCLASS_H__
#define __MYCLASS_H__


class MyClass
{
public:
MyClass();


/* use virtual otherwise linker will try to perform static linkage */
virtual void DoSomething();


private:
int x;
};


#endif

Myclass.cc

#include "myclass.h"
#include <iostream>


using namespace std;


extern "C" MyClass* create_object()
{
return new MyClass;
}


extern "C" void destroy_object( MyClass* object )
{
delete object;
}


MyClass::MyClass()
{
x = 20;
}


void MyClass::DoSomething()
{
cout<<x<<endl;
}

Class _ user. cc

#include <dlfcn.h>
#include <iostream>
#include "myclass.h"


using namespace std;


int main(int argc, char **argv)
{
/* on Linux, use "./myclass.so" */
void* handle = dlopen("myclass.so", RTLD_LAZY);


MyClass* (*create)();
void (*destroy)(MyClass*);


create = (MyClass* (*)())dlsym(handle, "create_object");
destroy = (void (*)(MyClass*))dlsym(handle, "destroy_object");


MyClass* myClass = (MyClass*)create();
myClass->DoSomething();
destroy( myClass );
}

在 Mac OS X 上,使用以下命令编译:

g++ -dynamiclib -flat_namespace myclass.cc -o myclass.so
g++ class_user.cc -o class_user

在 Linux 上,使用以下命令编译:

g++ -fPIC -shared myclass.cc -o myclass.so
g++ class_user.cc -ldl -o class_user

如果这是一个插件系统,您将使用 MyClass 作为基类,并定义所有必需的虚函数。然后插件作者从 MyClass 派生,覆盖虚拟并实现 create_objectdestroy_object。您的主应用程序不需要以任何方式进行更改。

在前面的答案之上,我想提醒大家注意一个事实,即应该使用 RAII (资源获取是初始化)习语来保证处理程序销毁的安全。

下面是一个完整的工作示例:

接口声明: Interface.hpp:

class Base {
public:
virtual ~Base() {}
virtual void foo() const = 0;
};


using Base_creator_t = Base *(*)();

共享图书馆内容:

#include "Interface.hpp"


class Derived: public Base {
public:
void foo() const override {}
};


extern "C" {
Base * create() {
return new Derived;
}
}

动态共享库处理程序: Derived_factory.hpp:

#include "Interface.hpp"
#include <dlfcn.h>


class Derived_factory {
public:
Derived_factory() {
handler = dlopen("libderived.so", RTLD_NOW);
if (! handler) {
throw std::runtime_error(dlerror());
}
Reset_dlerror();
creator = reinterpret_cast<Base_creator_t>(dlsym(handler, "create"));
Check_dlerror();
}


std::unique_ptr<Base> create() const {
return std::unique_ptr<Base>(creator());
}


~Derived_factory() {
if (handler) {
dlclose(handler);
}
}


private:
void * handler = nullptr;
Base_creator_t creator = nullptr;


static void Reset_dlerror() {
dlerror();
}


static void Check_dlerror() {
const char * dlsym_error = dlerror();
if (dlsym_error) {
throw std::runtime_error(dlsym_error);
}
}
};

客户端代码:

#include "Derived_factory.hpp"


{
Derived_factory factory;
std::unique_ptr<Base> base = factory.create();
base->foo();
}

注:

  • 为了简洁起见,我把所有内容都放在头文件中。在现实生活中,当然应该在 .hpp.cpp文件之间分割代码。
  • 为了简单起见,我忽略了需要处理 new/delete重载的情况。

两篇清晰的文章来获得更多的细节: