Build(빌드)/CMake
[CMake] 플러그인 빌드 구조 만들기 - 예제 2. 정적라이브러리 심볼 활용
2026. 5. 18. 00:19반응형
실행파일 A가 플러그인 B를 동적로딩하고 이를 실행하는 예제를 살펴본다. 이 때, 각각 공통된 헤더(Log.h)를 사용하고 있고 이 Log.h의 구현부는 별도의 정적라이브러리(lib_src)로 실행파일 A 측에 구현되어있다.
1. 프로젝트 구조
.
├── CMakeLists.txt
├── main.cpp # 실행 파일 (A)
├── plugin.cpp # 플러그인 (B)
├── include_main/ # 메인용 헤더 디렉토리
│ └── Log.h
├── include_plugin/ # 플러그인용 헤더 디렉토리 (내용은 동일)
│ └── Log.h
└── lib_src/ # 정적 라이브러리 소스
└── Log.cpp
2. 소스코드
Log.h (양쪽 폴더에 동일하게 복사)
#pragma once
class Log {
public:
void print();
};
lib_src/Log.cpp (정적 라이브러리 구현부)
#include "Log.h" // 컴파일 시점의 경로를 따름
#include <iostream>
void Log::print() {
std::cout << "[Main App's Static Lib] Hello from Log::print!" << std::endl;
}
main.cpp (실행 파일 A)
#include <iostream>
#include <dlfcn.h>
#include "Log.h" // include_main의 헤더 사용
int main() {
Log mainLog;
mainLog.print(); // 메인에서 사용 (whole-archive 없이 포함되도록 함)
void* handle = dlopen("./libpluginB.so", RTLD_NOW);
if (!handle) {
std::cerr << "Load fail: " << dlerror() << std::endl;
return 1;
}
auto run = (void (*)())dlsym(handle, "run_plugin");
if (run) run();
dlclose(handle);
return 0;
}
plugin.cpp (공유 라이브러리 B)
#include "Log.h" // include_plugin의 헤더 사용 (경로가 다름)
extern "C" void run_plugin() {
Log pluginLog;
// 구현체가 없지만, 실행 시 메인 바이너리의 심볼을 찾아갑니다.
pluginLog.print();
}
CMakeLists.txt
핵심은 --export-dynamic과 헤더 경로 설정이다.
cmake_minimum_required(VERSION 3.10)
project(CrossDirTest)
# 1. 정적 라이브러리 생성 (Main용 헤더 참조)
add_library(LogStatic STATIC lib_src/Log.cpp)
target_include_directories(LogStatic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include_main)
# 2. 메인 실행 파일 생성
add_executable(AppA main.cpp)
target_link_libraries(AppA PRIVATE LogStatic ${CMAKE_DL_LIBS})
# [핵심] 메인 내부의 LogStatic 심볼을 플러그인이 볼 수 있게 공개
target_link_options(AppA PRIVATE "-Wl,--export-dynamic")
# 3. 플러그인 생성 (Plugin 전용 별도 헤더 경로 참조)
add_library(pluginB SHARED plugin.cpp)
# 구현체(Log.cpp)를 링크하지 않고 헤더만 참조함
target_include_directories(pluginB PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include_plugin)
set_target_properties(pluginB PROPERTIES PREFIX "lib" OUTPUT_NAME "pluginB")
3. 분석
위 코드를 빌드하고 실행하면 다음과 같은 흐름이 발생한다.
- 빌드 단계
- AppA는 LogStatic을 링크하여 Log::print의 기계어 코드를 자기 몸체에 포함한다.
- pluginB는 Log::print가 어디 있는지 모르지만, "나중에 누군가 채워주겠지" 하고 구멍(Undefined Symbol)을 남겨둔 채 빌드된다.
- 실행 단계 (./AppA)
- main()이 호출되어 첫 번째 "Hello..."를 출력한다.
- dlopen("./libpluginB.so")가 호출된다.
- 시스템의 동적 링커(ld.so)가 pluginB를 로드하면서 빈 구멍(Log::print)을 채우려 한다.
- AppA가 --export-dynamic으로 빌드되었으므로, 링커는 AppA 내부에서 Log::print 심볼을 찾아 pluginB와 연결해 준다.
4. 실행 결과
[Main App's Static Lib] Hello from Log::print!
[Main App's Static Lib] Hello from Log::print!
결과적으로, 이처럼 동일한 헤더 파일의 물리적 위치가 다르더라도, 클래스 이름과 함수 시그니처가 같으면 링커는 이를 동일한 대상으로 간주하고 성공적으로 연결한다. (--export-dynamic을 활용해야한다.)
단, 앞서 설명한 대로 한쪽 헤더에만 멤버 변수를 추가하는 등의 실수를 하면 Segmentation Fault가 발생할 수 있으니 내용 일치에만 주의하시면 된다.
5. 그림
위의 내용들을 간단하게 그림으로 나타내면 아래와 같다.
- 빌드타임

- 런타임

반응형
'Build(빌드) > CMake' 카테고리의 다른 글
| [CMake] 플러그인 빌드 구조 만들기 (--export-dynamic 활용) (0) | 2026.05.14 |
|---|---|
| [CMake] target_include_directories와 target_link_libraries 개념 (0) | 2026.05.11 |
| [CMake] CMake 명령어 순서에 대한 고찰 (0) | 2026.05.11 |
| [CMake] find_package와 pkg_check_modules(PkgConfig)의 차이에 대한 고찰 (0) | 2026.04.16 |
| [CMake] Module(모듈) (0) | 2025.12.03 |