Linux의 C ++ 동적 공유 라이브러리


167

이것은 g ++을 사용한 Dynamic Shared Library 컴파일에 대한 후속 조치 입니다.

Linux에서 C ++로 공유 클래스 라이브러리를 만들려고합니다. 라이브러리를 컴파일 할 수 있으며 herehere 에서 찾은 자습서를 사용하여 (비 클래스) 함수 중 일부를 호출 할 수 있습니다 . 라이브러리에 정의 된 클래스를 사용하려고하면 문제가 시작됩니다. 내가 링크 한 두 번째 튜토리얼은 라이브러리에 정의 된 클래스의 객체를 생성하기위한 심볼을로드하는 방법을 보여 주지만 해당 객체를 사용하여 작업을 완료하는 데에는 시간이 걸리지 않습니다.

누구든지 별도의 실행 파일에서 해당 클래스 를 사용 하는 방법을 보여주는 공유 C ++ 클래스 라이브러리를 작성하기위한보다 완전한 자습서를 알고 있습니까? 객체 생성, 사용 (간단한 게터 및 세터가 훌륭함) 및 삭제가 환상적으로 보이는 매우 간단한 자습서입니다. 공유 클래스 라이브러리의 사용을 보여주는 오픈 소스 코드에 대한 링크 또는 참조도 동일합니다.


codelogicnimrodm 의 답변이 효과가 있지만 이 질문을 한 이후 Beginning Linux Programming 의 사본을 선택 했으며 첫 번째 장에는 예제 C 코드와 정적 라이브러리와 공유 라이브러리를 만들고 사용하는 데 대한 좋은 설명이 있습니다. . 이 예는 해당 도서 의 이전 버전 에서 Google 도서 검색을 통해 제공됩니다 .


객체를 가리키는 포인터가 반환되면 객체에 대한 다른 포인터를 사용하는 것처럼 사용할 수 있습니다.
codelogic

내가 링크 한 기사는 dlsym을 사용하여 객체 팩토리 함수에 대한 함수 포인터를 만드는 방법을 보여줍니다. 라이브러리에서 객체를 만들고 사용하는 구문은 표시하지 않습니다.
Bill the Lizard

1
클래스를 설명하는 헤더 파일이 필요합니다. 왜 OS가로드 타임에 라이브러리를 찾고 링크하도록하는 대신 "dlsym"을 사용해야한다고 생각하십니까? 간단한 예가 필요하면 알려주십시오.
nimrodm

3
@nimrodm : "dlsym"을 사용하는 대안은 무엇입니까? 공유 라이브러리에 정의 된 클래스를 모두 사용하는 3 개의 C ++ 프로그램을 작성하고 있습니다. 또한 사용할 Perl 스크립트가 1 개 있지만 다음 주에는 완전히 다른 문제입니다.
Bill the Lizard

답변:


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의에서 파생 플러그인 저자는 virtuals에를 무시하고 구현 create_object하고 destroy_object. 기본 응용 프로그램은 어떤 식 으로든 변경할 필요가 없습니다.


6
나는 이것을 시도하고 있지만 한 가지 질문이 있습니다. void *를 사용해야합니까, 아니면 create_object 함수가 대신 MyClass *를 반환 할 수 있습니까? 나는 당신이 나를 위해 이것을 바꾸라고 요구하지 않고, 나는 하나를 다른 것보다 사용해야하는 이유가 있는지 알고 싶습니다.
Bill the Lizard

1
고마워, 나는 이것을 시도했고 명령 줄에서 리눅스에서와 같이 작동했다 (코드 주석에서 제안한 변경을 한 후에). 시간 내 주셔서 감사합니다.
Bill the Lizard

1
extern "C"로 선언 할 이유가 있습니까? 이것은 g ++ 컴파일러를 사용하여 컴파일됩니다. 왜 c 명명 규칙을 사용하고 싶습니까? C는 C ++을 호출 할 수 없습니다. c ++로 작성된 랩퍼 인터페이스는 c에서 이것을 호출하는 유일한 방법입니다.
ant2009

6
함수는 C 함수 extern "C"이므로 @ ant2009가 필요합니다 dlsym. 그리고 create_object함수 를 동적으로로드하기 위해 C 스타일 링키지를 사용합니다. 을 사용하지 않으면 C ++ 컴파일러의 이름 관리로 인해 .so 파일 extern "C"create_object함수 이름을 알 수있는 방법이 없습니다 .
kokx

1
좋은 방법입니다. 누군가 Microsoft 컴파일러에서하는 것과 매우 비슷합니다. 약간의 #if # 다른 작업으로, 당신은 멋진 플랫폼 독립적 인 시스템을 얻을 수 있습니다
Ha11owed

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.와 링크하려면로드 경로를 지정해야합니다 (또는 / usr / local / lib 또는 이와 유사한 경로에 배치).

다음은 현재 디렉토리를 라이브러리의 검색 경로로 지정하고 기본 (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)

1
이것은 런타임에 동적 링크를 사용하는 대신 libshared.so를 실행 파일에 정적으로 링크하는 것처럼 보입니다. 나 맞아?
Bill the Lizard

10
아니요. 이것은 표준 Unix (Linux) 동적 연결입니다. 동적 라이브러리는 ".so"(공유 객체) 확장자를 가지며로드 시마다 main이로드 될 때마다 실행 파일 (이 경우 main)과 연결됩니다. 정적 링크는 링크 타임에 발생하며 확장자가 ".a"(아카이브) 인 라이브러리를 사용합니다.
nimrodm

9
빌드 시 동적으로 연결됩니다 . 다시 말해, 연결하려는 라이브러리에 대한 사전 지식이 필요합니다 (예 : dlopen의 경우 'dl'에 대한 링크). 이는 사전 지식이 필요하지 않은 사용자 지정 파일 이름을 기반으로 라이브러리 를 동적으로 로드 하는 것과 다릅니다 .
codelogic 2018 년

10
내가 잘못 설명하려고했던 것은이 경우 빌드 타임에 라이브러리 이름을 알아야한다는 것입니다 (-lshared를 gcc에 전달해야 함). 일반적으로 해당 정보를 사용할 수없는 경우 dlopen ()을 사용합니다. 즉, 라이브러리 이름이 런타임에 검색됩니다 (예 : 플러그인 열거).
codelogic 2016 년

3
-L. -lshared -Wl,-rpath=$$(ORIGIN)링크 및 드롭 할 때 사용하십시오 LD_LIBRARY_PATH=..
Maxim Egorushkin

9

기본적으로 공유 라이브러리에서 클래스를 사용하려는 코드에 클래스 헤더 파일을 포함시켜야합니다. 그런 다음 링크 할 때 '-l'플래그 를 사용하여 코드를 공유 라이브러리와 링크하십시오. 물론, 이것은 OS가 그것을 찾을 수있는 곳에 .so가 필요합니다. 3.5를 참조하십시오 . 공유 라이브러리 설치 및 사용

dlsym 사용은 컴파일 할 때 사용할 라이브러리를 모르는 경우에 사용됩니다. 그것은 여기의 경우처럼 들리지 않습니다. 어쩌면 혼란은 Windows가 컴파일 또는 런타임에 링크를 수행하든 (유사한 방법으로) 동적으로로드 된 라이브러리를 호출한다는 것입니다. 그렇다면 dlsym을 LoadLibrary와 동등한 것으로 생각할 수 있습니다.

라이브러리를 동적으로로드해야하는 경우 (즉, 플러그인) 이 FAQ 가 도움이됩니다.


1
동적 공유 라이브러리가 필요한 이유는 Perl 코드에서도 호출하기 때문입니다. 필자가 개발중인 다른 C ++ 프로그램에서 동적으로 호출해야한다는 것은 내 자신의 완전한 오해 일 수도 있습니다.
Bill the Lizard

필자는 통합 된 perl과 C ++를 시도한 적이 없지만 XS를 사용해야한다고 생각합니다. johnkeiser.com/perl-xs-c++.html
Matt Lewis

5

이전 답변 외에도 처리기 파괴에 대해 안전 하기 위해 RAII (Resource Acquisition Is Initialization) 관용구 를 사용해야한다는 사실에 대한 인식을 높이고 싶습니다 .

완전한 작업 예는 다음과 같습니다.

인터페이스 선언 : 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 2014 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.