Biblioteca compartilhada dinâmica C ++ no Linux

167

Este é um acompanhamento da compilação da Biblioteca Compartilhada Dinâmica com g ++ .

Estou tentando criar uma biblioteca de classes compartilhadas em C ++ no Linux. Consigo fazer a biblioteca compilar e posso chamar algumas das funções (sem classe) usando os tutoriais que encontrei aqui e aqui . Meus problemas começam quando tento usar as classes definidas na biblioteca. O segundo tutorial ao qual vinculei mostra como carregar os símbolos para criar objetos das classes definidas na biblioteca, mas deixa de usar esses objetos para realizar qualquer trabalho.

Alguém conhece um tutorial mais completo para criar bibliotecas de classes C ++ compartilhadas que também mostra como usar essas classes em um executável separado? Um tutorial muito simples que mostra a criação e o uso de objetos (getters e setters simples seriam bons) e a exclusão seria fantástica. Um link ou uma referência a algum código-fonte aberto que ilustra o uso de uma biblioteca de classes compartilhadas seria igualmente bom.


Embora as respostas de codelogic e nimrodm funcionem, eu só queria acrescentar que peguei uma cópia do Beginning Linux Programming desde que fiz essa pergunta, e seu primeiro capítulo tem exemplo de código C e boas explicações para criar e usar bibliotecas estáticas e compartilhadas . Esses exemplos estão disponíveis na Pesquisa de Livros do Google em uma edição mais antiga desse livro .

Bill the Lizard
fonte
Não sei se entendi o que você quer dizer com "usá-lo". Depois que um ponteiro para o objeto é retornado, você pode usá-lo como qualquer outro ponteiro para um objeto.
codelogic 30/01/09
O artigo ao qual vinculei mostra como criar um ponteiro de função para uma função de fábrica de objetos usando o dlsym. Ele não mostra a sintaxe para criar e usar objetos da biblioteca.
Bill o Lagarto
1
Você precisará do arquivo de cabeçalho que descreve a classe. Por que você acha que precisa usar o "dlsym" em vez de permitir que o sistema operacional encontre e vincule a biblioteca no tempo de carregamento? Deixe-me saber se você precisa de um exemplo simples.
Nimrodm
3
@nimrodm: Qual é a alternativa ao uso do "dlsym"? Estou (supostamente) escrevendo 3 programas C ++ que usarão as classes definidas na biblioteca compartilhada. Eu também tenho um script Perl que o utilizará, mas esse é outro problema para a próxima semana.
Bill o Lagarto

Respostas:

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 );
}

No Mac OS X, compile com:

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

No Linux, compile com:

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

Se fosse para um sistema de plug-ins, você usaria MyClass como classe base e definiria todas as funções necessárias virtuais. O autor do plugin derivaria do MyClass, substituiria os virtuais e implementaria create_objecte destroy_object. Seu aplicativo principal não precisaria ser alterado de forma alguma.

codelogic
fonte
6
Estou tentando isso, mas só tenho uma pergunta. É estritamente necessário usar void *, ou a função create_object retornará MyClass *? Não estou pedindo que você mude isso para mim, gostaria de saber se há um motivo para usar um sobre o outro.
Bill the Lizard
1
Obrigado, eu tentei isso e funcionou como no Linux a partir da linha de comando (depois de fazer a alteração que você sugeriu nos comentários do código). Eu aprecio seu tempo.
Bill Bill Lizard
1
Existe algum motivo para você declarar isso com "C" externo? Como isso é compilado usando um compilador g ++. Por que você deseja usar a convenção de nomenclatura c? C não pode chamar c ++. Uma interface de wrapper escrita em c ++ é a única maneira de chamar isso de c.
Ant2009
6
@ ant2009 você precisa extern "C"porque a dlsymfunção é uma função C. E para carregar dinamicamente a create_objectfunção, ele usará ligação em estilo C. Se você não usasse o extern "C", não haveria como saber o nome da create_objectfunção no arquivo .so, por causa da manipulação de nomes no compilador C ++.
Kokx
1
Bom método, é muito semelhante ao que alguém faria em um compilador da Microsoft. com um pouco de #if trabalho #else, você pode obter um sistema independente de plataforma agradável
Ha11owed
52

A seguir, mostra um exemplo de uma biblioteca de classes compartilhada compartilhada. [H, cpp] e um módulo main.cpp usando a biblioteca. É um exemplo muito simples e o makefile pode ser melhorado. Mas funciona e pode ajudá-lo:

shared.h define a classe:

class myclass {
   int myx;

  public:

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

shared.cpp define as funções getx / setx:

#include "shared.h"

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

main.cpp usa a classe

#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;
}

e o makefile que gera libshared.so e vincula main com a biblioteca compartilhada:

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

Para executar 'main' e conectar-se com libshared.so, você provavelmente precisará especificar o caminho de carregamento (ou colocá-lo em / usr / local / lib ou similar).

A seguir, especifica o diretório atual como o caminho de pesquisa para bibliotecas e executa main (sintaxe do bash):

export LD_LIBRARY_PATH=.
./main

Para verificar se o programa está vinculado ao libshared.so, você pode tentar o ldd:

LD_LIBRARY_PATH=. ldd main

Imprime na minha máquina:

  ~/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)
nimrodm
fonte
1
Isso parece (para meus olhos não treinados) vincular estaticamente o libshared.so ao seu executável, em vez de usar o vínculo dinâmico em tempo de execução. Estou correcto?
Bill the Lizard
10
Não. Esse é o vínculo dinâmico padrão do Unix (Linux). Uma biblioteca dinâmica possui a extensão ".so" (Objeto Compartilhado) e é vinculada ao executável (principal neste caso) no momento do carregamento - sempre que o main é carregado. O link estático ocorre no momento do link e usa bibliotecas com a extensão ".a" (archive).
Nimrodm
9
Isso é vinculado dinamicamente no momento da construção . Em outras palavras, você precisa ter conhecimento prévio da biblioteca contra a qual está vinculando (por exemplo, vincular contra 'dl' para dlopen). Isso é diferente de carregar dinamicamente uma biblioteca, com base, digamos, em um nome de arquivo especificado pelo usuário, onde o conhecimento prévio não é necessário.
Codelogic
10
O que eu estava tentando explicar (mal) é que, neste caso, você precisa saber o nome da biblioteca no momento da compilação (você precisa passar -shared para o gcc). Geralmente, usa-se dlopen () quando essa informação não está disponível, ou seja, o nome da biblioteca é descoberto em tempo de execução (por exemplo: enumeração de plug-in).
codelogic 02/03/09
3
Use -L. -lshared -Wl,-rpath=$$(ORIGIN)ao vincular e soltar isso LD_LIBRARY_PATH=..
Maxim Egorushkin
9

Basicamente, você deve incluir o arquivo de cabeçalho da classe no código em que deseja usar a classe na biblioteca compartilhada. Em seguida, ao vincular, use o sinalizador '-l' para vincular seu código à biblioteca compartilhada. Obviamente, isso requer que o .so esteja onde o sistema operacional possa encontrá-lo. Veja 3.5. Instalando e usando uma biblioteca compartilhada

O uso do dlsym é para quando você não sabe em tempo de compilação qual biblioteca deseja usar. Isso não parece ser o caso aqui. Talvez a confusão seja que o Windows chama as bibliotecas carregadas dinamicamente se você faz o vínculo no momento da compilação ou do tempo de execução (com métodos análogos)? Nesse caso, você pode pensar em dlsym como o equivalente a LoadLibrary.

Se você realmente precisar carregar dinamicamente as bibliotecas (ou seja, são plug-ins), então estas Perguntas frequentes devem ajudar.

Matt Lewis
fonte
1
A razão pela qual eu preciso de uma biblioteca compartilhada dinâmica é que também a chamarei do código Perl. Pode ser um completo equívoco da minha parte que eu também precise chamá-lo dinamicamente de outros programas C ++ que estou desenvolvendo.
Bill o Lagarto
Eu nunca tentei perl integrado e C ++, mas eu acho que você precisa usar XS: johnkeiser.com/perl-xs-c++.html
Matt Lewis
5

Além das respostas anteriores, gostaria de aumentar a conscientização sobre o fato de que você deve usar o idioma RAII (Aquisição de Recursos É Inicialização) para estar seguro sobre a destruição do manipulador.

Aqui está um exemplo de trabalho completo:

Declaração de interface Interface.hpp::

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

using Base_creator_t = Base *(*)();

Conteúdo da biblioteca compartilhada:

#include "Interface.hpp"

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

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

Manipulador de biblioteca compartilhada dinâmica 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);
        }
    }
};

Código do cliente:

#include "Derived_factory.hpp"

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

Nota:

  • Eu coloquei tudo nos arquivos de cabeçalho por concisão. Na vida real, é claro que você deve dividir seu código entre .hppe.cpp arquivos.
  • Para simplificar, ignorei o caso em que você deseja lidar com uma new/ deletesobrecarga.

Dois artigos claros para obter mais detalhes:

Xavier Lamorlette
fonte
Este é um excelente exemplo. RAII é definitivamente o caminho a percorrer.
David Steinhauer