반응형



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()으로 깔끔하게 정리됩니다.

반응형