반응형

 

CMake 환경에서 플러그인 빌드 구조를 만들기 위한 방법을 소개한다. 

 

예시로 설명한 플러그인 구조 및 요구사항은 아래와 같다.

- main.cpp -> plugin.cpp의 .so파일을 load한다.

- plugin.cpp -> 공유라이브러리로 구현되어 산출물은 .so 파일이다.

- 플러그인이 main.cpp 측에 구현된 C++ 클래스를 사용이 가능하도록 구성한다.

 

1. 프로젝트 구조

.
├── CMakeLists.txt
├── Log.h          # main.cpp와 plugin.cpp가 공유하는 인터페이스
├── main.cpp       # 실행 파일 (구현체 포함)
└── plugin.cpp     # 공유 라이브러리

 

 

2. 소스코드

Log.h

#pragma once
#include <iostream>

class Log {
public:
    // 구현은 main.cpp에만 둘 예정
    void print();
};

 

 

main.cpp

실행 파일 (main)이 Log::print의 실체를 가지고 있으며, 플러그인 (plugin.cpp)을 로드한다.

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

// Log 클래스의 구현부 - 실행 파일 A에 위치함
void Log::print() {
    std::cout << "Hello World from A's Log class!" << std::endl;
}

int main() {
    // 플러그인 B(libpluginB.so)를 로드
    void* handle = dlopen("./libpluginB.so", RTLD_NOW);
    if (!handle) {
        std::cerr << "Cannot open library: " << dlerror() << std::endl;
        return 1;
    }

    // 플러그인 내부의 'run_plugin' 함수 호출
    typedef void (*run_func)();
    run_func run = (run_func)dlsym(handle, "run_plugin");
    
    if (run) {
        run();
    } else {
        std::cerr << "Cannot load symbol 'run_plugin': " << dlerror() << std::endl;
    }

    dlclose(handle);
    return 0;
}

 

 

plugin.cpp (공유 라이브러리)

이 plugin은 Log.h만 알고 있고, 구현체가 없지만 Log 객체를 사용한다.

#include "Log.h"

extern "C" void run_plugin() {
    Log logger;
    // 플러그인에는 print()의 구현이 없지만, 실행 시 main쪽의 심볼을 찾아서 호출한다.
    logger.print();
}

 

 

CMakeLists.txt

여기서 --export-dynamic 설정이 핵심이다.

cmake_minimum_required(VERSION 3.10)
project(ExportDynamicTest)

# 1. 실행 파일 A 생성
add_executable(AppA main.cpp)

# --export-dynamic 옵션 부여 (A의 심볼을 외부에 노출)
target_link_options(AppA PRIVATE "-Wl,--export-dynamic")
# dlopen 기능을 위해 dl 라이브러리 링크 (리눅스)
target_link_libraries(AppA PRIVATE ${CMAKE_DL_LIBS})

# 2. 공유 라이브러리 B 생성
# 주의: 여기서는 Log.cpp(구현부)를 절대 추가하지 않습니다.
add_library(pluginB SHARED plugin.cpp)

# B는 링크 시점에 Log::print 심볼이 없어도 무시하도록 설정 (Undefined symbols)
# 하지만 보통 Linux 링커는 공유 라이브러리 빌드 시 
# 정의되지 않은 심볼이 있어도 기본적으로 허용합니다.

 

 

3. 테스트 및 결과

빌드

 

rm -rf build
mkdir build && cd build
cmake ..
make

 

실행

./AppA

 

결과

Hello World from A's Log class!

 

 

 

 

4. 고찰

만약 AppA의 target_link_options에서 -Wl,--export-dynamic을 제거하고 다시 빌드하면 어떻게 될까?

  • AppA는 문제없이 실행되지만, dlopen으로 pluginB를 로드하는 순간 다음과 같은 에러가 발생하며 중단된다.
  • undefined symbol: _ZN3Log5printEv (Log::print()의 망글링된 이름)

이는 pluginB가 자신의 메모리 안에서 Log::print를 찾지 못했고, 메인 프로그램(AppA)이 그 심볼을 공개하지 않았기 때문에 발생하는 현상이다. 따라서 --export-dynamic은 "내 안에 구현된 클래스 기능을 너(플러그인)에게 공유해줄게"라는 선언과 같다.

반응형