Linux上的C ++动态共享库


167

这是使用g ++进行动态共享库编译的后续版本。

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

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


尽管来自codelogicnimrodm的答案确实有用,但我只是想补充一点,因为提出了这个问题,所以我选择了《Beginning Linux Programming》的副本,并且其第一章包含示例C代码以及有关创建和使用静态库和共享库的良好解释。 。这些示例可通过该书的旧版 Google图书搜索获得。


我不确定我“使用”它是什么意思,一旦返回指向对象的指针,就可以像使用其他指向对象的指针一样使用它。
codelogic

我链接到的文章介绍了如何使用dlsym创建指向对象工厂函数的函数指针。它没有显示用于创建和使用库中对象的语法。
比尔蜥蜴,

1
您将需要描述该类的头文件。为什么您认为必须使用“ dlsym”而不是仅让操作系统在加载时找到并链接库?让我知道您是否需要一个简单的例子。
nimrodm

3
@nimrodm:使用“ dlsym”的替代方法是什么?我(应该)正在编写3个C ++程序,它们都将使用共享库中定义的类。我也有1个将使用它的Perl脚本,但这是下周的另一个问题。
比尔蜥蜴

Answers:


154

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


6
我正在尝试这个过程,但是只有一个问题。严格必须使用void *,还是可以将create_object函数返回MyClass *?我并不是要您为我更改此设置,我只是想知道是否有理由在一个选项上使用另一个选项。
比尔蜥蜴

1
谢谢,我尝试了此操作,并且它在Linux上可以从命令行像以前一样工作(一旦完成了您在代码注释中建议的更改)。感谢您的宝贵时间。
比尔蜥蜴

1
您是否有任何理由要用extern“ C”声明它们?由于使用g ++编译器进行编译。为什么要使用c命名约定?C无法调用c ++。用c ++编写的包装器接口是从c调用此接口的唯一方法。
ant2009年

6
@ ant2009您需要使用,extern "C"因为该dlsym函数是C函数。为了动态加载该create_object函数,它将使用C样式的链接。如果您不使用,由于C ++编译器中的名称修饰,将无法extern "C"得知create_object.so文件中函数的名称。
kokx

1
不错的方法,它与某人在Microsoft编译器上执行的操作非常相似。#if #else工作,您可以得到一个不错的平台独立系统
2013年

52

下面显示了一个共享类库shared。[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.link链接,可以尝试使用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)

1
(对我而言,这是未经培训的)似乎是将libshared.so静态链接到可执行文件,而不是在运行时使用动态链接。我对么?
比尔蜥蜴

10
否。这是标准的Unix(Linux)动态链接。动态库具有扩展名“ .so”(共享对象),并在加载时(每次加载main时)都与可执行文件(在本例中为main)链接。静态链接在链接时发生,并使用扩展名为“ .a”(归档)的库。
nimrodm,2009年

9
这是在构建时动态链接的。换句话说,您需要先了解要链接的库(例如,为dlopen链接到“ dl”)。这与基于用户指定的文件名动态加载库不同,后者不需要先验知识。
codelogic

10
我试图(严重)解释的是,在这种情况下,您需要在构建时知道库的名称(需要将-lshared传递给gcc)。通常,当该信息不可用时,即使用dlopen(),即在运行时发现库的名称(例如:插件枚举)。
codelogic

3
-L. -lshared -Wl,-rpath=$$(ORIGIN)在链接时使用并删除该链接LD_LIBRARY_PATH=.
Maxim Egorushkin

9

基本上,您应该在要在共享库中使用该类的代码中包含该类的头文件。然后,当您链接时,使用“ -l”标志将您的代码与共享库链接。当然,这要求.so是操作系统可以找到的位置。参见3.5。安装和使用共享库

当您在编译时不知道要使用哪个库时,可以使用dlsym。听起来好像不是这里。也许混淆是Windows调用了动态加载的库,而不管您是在编译时还是在运行时(使用类似方法)进行链接?如果是这样,那么您可以将dlsym视为LoadLibrary的等效项。

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


1
我需要动态共享库的原因是,我还将从Perl代码中调用它。就我自己而言,这可能是一个完全的误解,我还需要从正在开发的其他C ++程序中动态调用它。
比尔蜥蜴

我从未尝试过将Perl和C ++集成在一起,但是我认为您需要使用XS:johnkeiser.com/perl-xs-c
Matt Lewis,2009年

5

除了先前的答案,我想提高您对以下事实的认识:您应该使用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重载的情况。

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


这是一个很好的例子。RAII绝对是必经之路。
David Steinhauer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.