3. 해결 방법

3.1. extern "C"

C++에는 extern "C"라는, C binding으로 함수를 정의하는 특별한 키워드가 있습니다. extern "C"로서 선언된 함수는 C처럼 함수의 이름을 symbol의 이름으로 사용합니다. 이러한 이유로, 멤버함수가 아닌 함수들만이 extern "C"로서 선언될 수 있고, 이러한 함수들은 오버로딩을 할 수 없습니다.

이런 심한 제한이 있지만, extern "C"함수는 C 함수처럼 dlopen을 써서 동적으로 적재할 수 있기 때문에 매우 유용합니다.

이것은 extern "C"로 선언된 함수가 C++ 코드를 포함할 수 없다는 것을 의미하는 것이아닙니다. 이런 함수는 어느 종류의 인자라도 받을 수 있고, C++의 특징을 쓸 수 있습니다.

3.2. 함수를 적재하는 법

C++에서 함수는 dlsym을 통해 C처럼 적재됩니다. 당신이 적재하고자 하는 함수는 symbol의 이름이 엉망으로 되지 않도록(name mangling이 이루어지지 않도록), extern "C"로서의 자격을 갖춰야 할 것입니다.

예 1. 함수를 적재하기

main.cpp:

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


int main() {
    using std::cout;
    using std::cerr;

    cout << "C++ dlopen demo\n\n";

    // open the library
    cout << "Opening hello.so...\n";
    void* handle = dlopen("./hello.so", RTLD_LAZY);
    
    if (!handle) {
        cerr << "Cannot open library: " << dlerror() << '\n';
        return 1;
    }
    
    // load the symbol
    cout << "Loading symbol hello...\n";
    typedef void (*hello_t)();
    hello_t hello = (hello_t) dlsym(handle, "hello");
    if (!hello) {
        cerr << "Cannot load symbol 'hello': " << dlerror() <<
            '\n';
        dlclose(handle);
        return 1;
    }
    
    // use it to do the calculation
    cout << "Calling hello...\n";
    hello();
    
    // close the library
    cout << "Closing library...\n";
    dlclose(handle);
}

hello.cpp:

#include <iostream>

extern "C" void hello() {
    std::cout << "hello" << '\n';
}

hello라는 함수는 hello.cpp에서 extern "C"로 선언되었습니다. 이것은 main.cpp에서 dlsym을 사용하여 적재할 수 있습니다. 함수는 extern "C" 로서의 자격을 갖추어야 합니다. 그렇지 않다면 우리는 hello 함수의 symbol의 이름을 알 수 없을테니까요.

주의

extern "C"의 선언에는 두가지의 다른 형태가 있습니다: 하나는 위에서 썼던extern "C"의 방법이고, 또 다른 하나는 extern "C" { … }형태로 중괄호 사이에 선언이 들어가는 방법입니다. 첫번째(inline)형태는 extern 연결과 C 언어 연결을 갖습니다. 두번째 형태는 C언어 연결에만 영향을 끼칩니다. 따라서 아래의 두 가지 선언 방법은 동일한 것입니다.

extern "C" int foo;
extern "C" void bar();
            
extern "C" {
     extern int foo;
     extern void bar();
 }

externextern이 아닌 함수의 선언에는 차이가 없기때문에, 당신이 어떠한 변수도 선언하지 않는 한 문제가 없습니다. 당신이 변수를 선언한다면,다음의 두 선언 방법은 같지 않다는 것을 명심해야 합니다.

extern "C" int foo;
extern "C" {
    int foo;
}

좀 더 자세한 설명을 원하시면, paragraph 7에 주의를 기울여 [ISO14882]의 7.5를 읽어보시거나, [STR2000]의 paragraph 9.2.4.를 참조해주십시오.

외부 변수로 무언가를 하기 전에, see also부분에 적혀 있는 문서들을 정독해주시기 바랍니다.

3.3. 클래스를 적재하는 법

클래스를 적재하는 것은 좀 더 어렵습니다. 왜냐하면 우리는 클래스의인스턴스를 필요로 하지, 함수에 대한 포인터를 필요로 하는게 아니기 때문입니다.

클래스가 실행파일에 정의되어 있지 않은데다가,(몇몇 상황에서는) 클래스의 이름조차도 알 수 없기 때문에, 우리는 new 를 이용하여 클래스의 인스턴스를 생성할 수 없습니다.

이것은 다형성(polymorphism) 을 통해 해결할 수 있습니다. 우리는 기반 클래스, 가상의 멤버를 가지고 있는 인터페이스클래스를 실행파일내에 선언하고, 실제 구현부의 클래스를 모듈 에 선언합니다. 일반적으로 인터페이스 클래스는 추상적입니다. (클래스가 순수가상함수(pure virtual function)을 가지고 있을때 클래스가 추상적이라고 합니다.)

클래스의 동적인 적재는 일반적으로 플러그인 — 명확하게 정의된 인터페이스를 보여주어야 하는 — 에 쓰이기 때문에, 우리는 어쨌거나 인터페이스 클래스와 거기서 파생된 구현부 클래스를 정의해야 합니다.

다음으로,모듈 안에Class factory function이라는 두개의 도움을 주는 함수를 추가로 선언해야 합니다. 이 함수 중 하나는 클래스의 인스턴스를 만들고 그것의 포인터를 반환하는 역할을 하고, 또 다른 하나의 함수는 factory에서 만들어진 함수의 포인터를 받아 그것(클래스의 인스턴스)를 파괴하는 역할을 합니다. 이 두 함수는 extern "C"의 자격을 가지고 있어야 합니다.

클래스를 모듈에서 쓰기 위해서,두개의 factory function을 우리가 hello함수를 적재했던 것처럼dlsym을 사용하여 적재하십시오.그럼 우리는 우리가 원하는 만큼의 인스턴스를 생성할수도 있고, 파괴할 수도 있습니다.

예 2. 클래스를 적재하는 법

여기서 우리는 일반적인다각형을 인터페이스로 하고, 삼각형을 구현부분으로 할 것입니다.

main.cpp:

#include "polygon.hpp"
#include <iostream>
#include <dlfcn.h>

int main() {
    using std::cout;
    using std::cerr;

    // load the triangle library
    void* triangle = dlopen("./triangle.so", RTLD_LAZY);
    if (!triangle) {
        cerr << "Cannot load library: " << dlerror() << '\n';
        return 1;
    }

    // load the symbols
    create_t* create_triangle = (create_t*) dlsym(triangle, "create");
    destroy_t* destroy_triangle = (destroy_t*) dlsym(triangle, "destroy");
    if (!create_triangle || !destroy_triangle) {
        cerr << "Cannot load symbols: " << dlerror() << '\n';
        return 1;
    }

    // create an instance of the class
    polygon* poly = create_triangle();

    // use the class
    poly->set_side_length(7);
        cout << "The area is: " << poly->area() << '\n';

    // destroy the class
    destroy_triangle(poly);

    // unload the triangle library
    dlclose(triangle);
}

polygon.hpp:

#ifndef POLYGON_HPP
#define POLYGON_HPP

class polygon {
protected:
    double side_length_;

public:
    polygon()
        : side_length_(0) {}

    void set_side_length(double side_length) {
        side_length_ = side_length;
    }

    virtual double area() const = 0;
};

// the types of the class factories
typedef polygon* create_t();
typedef void destroy_t(polygon*);

#endif

triangle.cpp:

#include "polygon.hpp"
#include <cmath>

class triangle : public polygon {
public:
    virtual double area() const {
        return side_length_ * side_length_ * sqrt(3) / 2;
    }
};


// the class factories

extern "C" polygon* create() {
    return new triangle;
}

extern "C" void destroy(polygon* p) {
    delete p;
}

클래스를 적재할때 주의해야 할 점이 몇가지 있습니다: