Linux上的C ++动态共享库

时间:2009-01-30 18:41:56

标签: c++ linux shared-libraries

这是Dynamic Shared Library compilation with g++的后续行动。

我正在尝试在Linux上用C ++创建一个共享类库。我能够编译库,我可以使用我发现herehere的教程调用一些(非类)函数。当我尝试使用库中定义的类时,我的问题开始了。我链接的第二个教程展示了如何加载符号来创建库中定义的类的对象,但是没有使用这些对象来完成任何工作。

有没有人知道有关创建共享C ++类库的更完整的教程,该教程还展示了如何在单独的可执行文件中使用这些类?一个非常简单的教程,显示对象创建,使用(简单的getter和setter会很好),删除将是太棒了。一些链接或对一些开源代码的引用说明了共享类库的使用同样很好。


尽管来自codelogicnimrodm的答案确实有效,但我只是想补充说,自从提出这个问题以来,我选择了Beginning Linux Programming的副本,其第一章有示例C创建和使用静态库和共享库的代码和良好解释。这些示例可通过an older edition of that book中的Google图书搜索获得。

4 个答案:

答案 0 :(得分:143)

<强> myclass.h

#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。您的主要应用程序无需以任何方式进行更改。

答案 1 :(得分:51)

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

shared.h定义了类:

class myclass {
   int myx;

  public:

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

shared.cpp定义了getx / setx函数:

#include "shared.h"

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

main.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)

答案 2 :(得分:8)

基本上,您应该在要在共享库中使用该类的代码中包含类'头文件。然后,当您链接时,use the '-l' flag将您的代码与共享库链接起来。当然,这要求.so是操作系统可以找到它的地方。见3.5. Installing and Using a Shared Library

使用dlsym是为了在编译时不知道要使用哪个库。这听起来不像是这样的情况。也许混淆是Windows调用动态加载的库,无论你是在编译还是运行时进行链接(使用类似的方法)?如果是这样,那么你可以认为dlsym相当于LoadLibrary。

如果你确实需要动态加载库(即它们是插件),那么this FAQ应该有所帮助。

答案 3 :(得分:1)

除了先前的答案,我想提高人们对使用RAII (Resource Acquisition Is Initialisation) idiom的安全性,以免破坏处理程序这一事实。

这是一个完整的工作示例:

接口声明: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重载的情况。

两条清晰的文章以获取更多详细信息: