(작성중) shared lib 2
2026. 4. 10. 18:22
sdbus-c++ 2.0.0 기준으로 계산기 예제를 만들어 드릴게요.
전체 구조
calculator/
├── interface.xml # D-Bus interface 정의
├── generated/
│ ├── Calculator_proxy.h # codegen 결과 (client)
│ └── Calculator_adaptor.h # codegen 결과 (server)
├── CalculatorService.h # adaptor 구현
├── mylib.cpp # constructor에서 서비스 시작
├── mylib.h
└── main.cpp # client (proxy 사용)
1. interface.xml
<?xml version="1.0" encoding="UTF-8"?>
<node name="/com/example/Calculator">
<interface name="com.example.Calculator">
<method name="Add">
<arg name="a" type="d" direction="in"/>
<arg name="b" type="d" direction="in"/>
<arg name="result" type="d" direction="out"/>
</method>
<method name="Subtract">
<arg name="a" type="d" direction="in"/>
<arg name="b" type="d" direction="in"/>
<arg name="result" type="d" direction="out"/>
</method>
<method name="Multiply">
<arg name="a" type="d" direction="in"/>
<arg name="b" type="d" direction="in"/>
<arg name="result" type="d" direction="out"/>
</method>
<method name="Divide">
<arg name="a" type="d" direction="in"/>
<arg name="b" type="d" direction="in"/>
<arg name="result" type="d" direction="out"/>
</method>
<signal name="CalculationDone">
<arg name="operation" type="s"/>
<arg name="result" type="d"/>
</signal>
</interface>
</node>
2. codegen 실행
# sdbus-c++ codegen으로 adaptor/proxy 헤더 생성
sdbus-c++-xml2cpp interface.xml \
--adaptor=generated/Calculator_adaptor.h \
--proxy=generated/Calculator_proxy.h
3. generated/Calculator_adaptor.h (codegen 결과)
// 자동 생성 파일 — 직접 수정 금지
#pragma once
#include <sdbus-c++/sdbus-c++.h>
namespace com::example {
class Calculator_adaptor {
public:
static constexpr const char* INTERFACE_NAME = "com.example.Calculator";
protected:
Calculator_adaptor(sdbus::IObject& object) : object_(object) {
object_.addVTable(
sdbus::registerMethod("Add")
.withInputParamNames("a", "b")
.withOutputParamNames("result")
.implementedAs([this](double a, double b) {
return this->Add(a, b);
}),
sdbus::registerMethod("Subtract")
.withInputParamNames("a", "b")
.withOutputParamNames("result")
.implementedAs([this](double a, double b) {
return this->Subtract(a, b);
}),
sdbus::registerMethod("Multiply")
.withInputParamNames("a", "b")
.withOutputParamNames("result")
.implementedAs([this](double a, double b) {
return this->Multiply(a, b);
}),
sdbus::registerMethod("Divide")
.withInputParamNames("a", "b")
.withOutputParamNames("result")
.implementedAs([this](double a, double b) {
return this->Divide(a, b);
}),
sdbus::registerSignal("CalculationDone")
.withParameters<std::string, double>("operation", "result")
).forInterface(INTERFACE_NAME);
}
~Calculator_adaptor() = default;
// signal 발송 헬퍼
void emitCalculationDone(const std::string& operation, double result) {
object_.emitSignal("CalculationDone")
.onInterface(INTERFACE_NAME)
.withArguments(operation, result);
}
private:
virtual double Add(double a, double b) = 0;
virtual double Subtract(double a, double b) = 0;
virtual double Multiply(double a, double b) = 0;
virtual double Divide(double a, double b) = 0;
sdbus::IObject& object_;
};
} // namespace com::example
4. generated/Calculator_proxy.h (codegen 결과)
// 자동 생성 파일 — 직접 수정 금지
#pragma once
#include <sdbus-c++/sdbus-c++.h>
#include <functional>
namespace com::example {
class Calculator_proxy {
public:
static constexpr const char* INTERFACE_NAME = "com.example.Calculator";
protected:
Calculator_proxy(sdbus::IProxy& proxy) : proxy_(proxy) {
// signal 등록
proxy_.uponSignal("CalculationDone")
.onInterface(INTERFACE_NAME)
.call([this](const std::string& op, double result) {
this->onCalculationDone(op, result);
});
}
~Calculator_proxy() = default;
// signal 콜백 — override 선택적
virtual void onCalculationDone(const std::string& /*op*/,
double /*result*/) {}
public:
double Add(double a, double b) {
return proxy_.callMethod("Add")
.onInterface(INTERFACE_NAME)
.withArguments(a, b)
.returnValue<double>();
}
double Subtract(double a, double b) {
return proxy_.callMethod("Subtract")
.onInterface(INTERFACE_NAME)
.withArguments(a, b)
.returnValue<double>();
}
double Multiply(double a, double b) {
return proxy_.callMethod("Multiply")
.onInterface(INTERFACE_NAME)
.withArguments(a, b)
.returnValue<double>();
}
double Divide(double a, double b) {
return proxy_.callMethod("Divide")
.onInterface(INTERFACE_NAME)
.withArguments(a, b)
.returnValue<double>();
}
private:
sdbus::IProxy& proxy_;
};
} // namespace com::example
5. CalculatorService.h (adaptor 구현체)
#pragma once
#include <sdbus-c++/sdbus-c++.h>
#include <stdexcept>
#include "generated/Calculator_adaptor.h"
namespace com::example {
class CalculatorService final : public Calculator_adaptor {
public:
static constexpr const char* SERVICE_NAME = "com.example.Calculator";
static constexpr const char* OBJECT_PATH = "/com/example/Calculator";
explicit CalculatorService(sdbus::IConnection& conn)
: object_(sdbus::createObject(conn, sdbus::ObjectPath{OBJECT_PATH}))
, Calculator_adaptor(*object_)
{
// 서비스 이름 요청
conn.requestServiceName(SERVICE_NAME);
}
~CalculatorService() = default;
private:
// ── adaptor 순수 가상 함수 구현 ──────────────────────
double Add(double a, double b) override {
double r = a + b;
emitCalculationDone("Add", r);
return r;
}
double Subtract(double a, double b) override {
double r = a - b;
emitCalculationDone("Subtract", r);
return r;
}
double Multiply(double a, double b) override {
double r = a * b;
emitCalculationDone("Multiply", r);
return r;
}
double Divide(double a, double b) override {
if (b == 0.0)
throw sdbus::Error("com.example.Calculator.Error", "Division by zero");
double r = a / b;
emitCalculationDone("Divide", r);
return r;
}
std::unique_ptr<sdbus::IObject> object_;
};
} // namespace com::example
6. mylib.h
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void mylib_start_service(); // (선택) 외부에서 명시적 시작 가능
void mylib_stop_service();
#ifdef __cplusplus
}
#endif
7. mylib.cpp (constructor에서 서비스 기동)
#include <thread>
#include <memory>
#include <atomic>
#include <sdbus-c++/sdbus-c++.h>
#include "CalculatorService.h"
#include "mylib.h"
// ── 내부 상태 ────────────────────────────────────────────
static std::unique_ptr<sdbus::IConnection> g_conn;
static std::unique_ptr<com::example::CalculatorService> g_service;
static std::thread g_thread;
static std::atomic<bool> g_running{false};
// ── 서비스 스레드 ─────────────────────────────────────────
static void service_thread_func() {
g_conn->enterEventLoop(); // blocking: D-Bus 이벤트 처리
}
// ── public API ───────────────────────────────────────────
void mylib_start_service() {
if (g_running.exchange(true)) return; // 중복 호출 방지
g_conn = sdbus::createSessionBusConnection();
g_service = std::make_unique<com::example::CalculatorService>(*g_conn);
g_thread = std::thread(service_thread_func);
}
void mylib_stop_service() {
if (!g_running.exchange(false)) return;
g_conn->leaveEventLoop();
if (g_thread.joinable()) g_thread.join();
g_service.reset();
g_conn.reset();
}
// ── constructor / destructor ──────────────────────────────
__attribute__((constructor))
static void mylib_init() {
mylib_start_service();
}
__attribute__((destructor))
static void mylib_fini() {
mylib_stop_service();
}
8. main.cpp (proxy 사용 — client)
#include <sdbus-c++/sdbus-c++.h>
#include <stdio.h>
#include "generated/Calculator_proxy.h"
// proxy 구체 클래스
class CalculatorClient final : public com::example::Calculator_proxy {
public:
explicit CalculatorClient(sdbus::IProxy& proxy)
: Calculator_proxy(proxy) {}
// signal 수신 (선택 override)
void onCalculationDone(const std::string& op, double result) override {
printf("[signal] CalculationDone: %s = %.4f\n", op.c_str(), result);
}
};
int main() {
auto conn = sdbus::createSessionBusConnection();
auto proxy = sdbus::createProxy(
*conn,
sdbus::ServiceName{"com.example.Calculator"},
sdbus::ObjectPath{"/com/example/Calculator"}
);
CalculatorClient calc(*proxy);
printf("Add(3, 4) = %.2f\n", calc.Add(3, 4));
printf("Subtract(10,3) = %.2f\n", calc.Subtract(10, 3));
printf("Multiply(6, 7) = %.2f\n", calc.Multiply(6, 7));
printf("Divide(22, 7) = %.4f\n", calc.Divide(22, 7));
// Divide by zero 예외 처리
try {
calc.Divide(1, 0);
} catch (const sdbus::Error& e) {
printf("[error] %s\n", e.getMessage().c_str());
}
return 0;
}
9. CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(calculator_dbus)
set(CMAKE_CXX_STANDARD 17)
find_package(SDBusCpp REQUIRED)
# ── shared library ───────────────────────────────────────
add_library(mylib SHARED mylib.cpp)
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(mylib PRIVATE SDBusCpp::sdbus-c++)
# ── client ───────────────────────────────────────────────
add_executable(calculator_client main.cpp)
target_include_directories(calculator_client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(calculator_client PRIVATE SDBusCpp::sdbus-c++ mylib)
빌드 & 실행
mkdir build && cd build
cmake ..
make
# 터미널 1: 라이브러리 로드 (서버 역할)
LD_LIBRARY_PATH=. ./calculator_client
# 또는 dlopen 테스트용으로 서버/클라이언트 분리 실행도 가능
핵심 흐름 요약
dlopen(libmylib.so)
└─ __attribute__((constructor))
└─ mylib_start_service()
├─ createSessionBusConnection()
├─ CalculatorService 생성 ← addVTable() 호출
└─ thread: enterEventLoop() ← D-Bus 이벤트 대기
client → proxy.callMethod("Add") → D-Bus → CalculatorService::Add()
└─ emitCalculationDone signal
enterEventLoop()이 별도 스레드에서 돌기 때문에 main() 스레드를 블로킹하지 않고, library unload 시 destructor에서 leaveEventLoop() → join()으로 깔끔하게 정리됩니다.
