(작성중) [sdbus] sdbus 예제 04
2026. 4. 13. 11:25
가능 여부
가능합니다. 단, 두 플러그인은 서로 독립적인 .so이므로 직접 함수 호출은 불가합니다. 방법은 두 가지입니다.
방법 비교
방법
설명
장점
단점
D-Bus 경유 호출
Greeter가 Calculator를 D-Bus 클라이언트로 호출
플러그인 간 완전 독립
오버헤드 있음
공유 인터페이스 경유 호출
server가 Calculator 포인터를 Greeter에 주입
직접 함수 호출, 빠름
플러그인 간 의존성 생김
실무에서는 공유 인터페이스 경유 호출이 일반적입니다. 두 방법 모두 설명드립니다.
방법 1: 공유 인터페이스 주입 (권장)
변경된 구조
project/
├── plugin/
│ ├── ICalculatorPlugin.h # ICalculator 인터페이스 추가
│ ├── IGreeterPlugin.h # start()에 ICalculator* 주입
│ ├── calculator_plugin.cpp
│ └── greeter_plugin.cpp
└── server.cpp # Calculator 포인터를 Greeter에 주입
ICalculatorPlugin.h (ICalculator 인터페이스 추가)
#pragma once
#include <sdbus-c++/sdbus-c++.h>
// Greeter가 사용할 Calculator 순수 인터페이스
class ICalculator
{
public:
virtual ~ICalculator() = default;
virtual int32_t Add(int32_t a, int32_t b) = 0;
virtual int32_t Sub(int32_t a, int32_t b) = 0;
};
class ICalculatorPlugin
{
public:
virtual ~ICalculatorPlugin() = default;
virtual void start(sdbus::IConnection& conn) = 0;
// server가 Greeter에 주입할 수 있도록 노출
virtual ICalculator* getCalculator() = 0;
};
extern "C" {
ICalculatorPlugin* create_plugin();
void destroy_plugin(ICalculatorPlugin* plugin);
}
IGreeterPlugin.h (ICalculator* 주입받도록 변경)
#pragma once
#include <sdbus-c++/sdbus-c++.h>
#include "ICalculatorPlugin.h"
class IGreeterPlugin
{
public:
virtual ~IGreeterPlugin() = default;
// ICalculator* 를 주입받아 내부에서 직접 호출
virtual void start(sdbus::IConnection& conn, ICalculator* calculator) = 0;
};
extern "C" {
IGreeterPlugin* create_greeter_plugin();
void destroy_greeter_plugin(IGreeterPlugin* plugin);
}
calculator_plugin.cpp (ICalculator 구현 추가)
#include "ICalculatorPlugin.h"
#include "../generated/calculator-adaptor.h"
#include <iostream>
class CalculatorImpl
: public sdbus::AdaptorInterfaces<org::example::Calculator_adaptor>
, public ICalculator // ← ICalculator도 구현
{
public:
CalculatorImpl(sdbus::IConnection& conn, sdbus::ObjectPath path)
: AdaptorInterfaces(conn, std::move(path))
{
registerAdaptor();
}
// ICalculator 구현 (Greeter가 직접 호출할 함수)
int32_t Add(int32_t a, int32_t b) override
{
int32_t result = a + b;
std::cout << "[Calculator] Add(" << a << ", " << b << ") = " << result << "\n";
return result;
}
int32_t Sub(int32_t a, int32_t b) override
{
int32_t result = a - b;
std::cout << "[Calculator] Sub(" << a << ", " << b << ") = " << result << "\n";
emitSubResult(result);
return result;
}
};
class CalculatorPlugin : public ICalculatorPlugin
{
public:
void start(sdbus::IConnection& conn) override
{
impl_ = std::make_unique<CalculatorImpl>(
conn,
sdbus::ObjectPath{"/org/example/Calculator"}
);
}
// CalculatorImpl 포인터 노출
ICalculator* getCalculator() override
{
return impl_.get();
}
private:
std::unique_ptr<CalculatorImpl> impl_;
};
extern "C" {
ICalculatorPlugin* create_plugin() { return new CalculatorPlugin(); }
void destroy_plugin(ICalculatorPlugin* p) { delete p; }
}
greeter_plugin.cpp (ICalculator* 주입받아 직접 호출)
#include "IGreeterPlugin.h"
#include "../generated/greeter-adaptor.h"
#include <iostream>
#include <map>
class GreeterImpl
: public sdbus::AdaptorInterfaces<org::example::Greeter_adaptor>
{
public:
GreeterImpl(sdbus::IConnection& conn,
sdbus::ObjectPath path,
ICalculator* calculator) // ← 주입
: AdaptorInterfaces(conn, std::move(path))
, calculator_(calculator)
{
registerAdaptor();
}
protected:
std::string SayHello(const std::string& name) override
{
static const std::map<std::string, std::string> greetings = {
{"en", "Hello"}, {"ko", "안녕하세요"},
{"ja", "こんにちは"}, {"fr", "Bonjour"},
};
auto it = greetings.find(lang_);
std::string greeting = (it != greetings.end()) ? it->second : "Hello";
// Calculator::Add 직접 호출 (글자 수 + 인사 길이 계산 예시)
int32_t nameLen = static_cast<int32_t>(name.size());
int32_t greetLen = static_cast<int32_t>(greeting.size());
int32_t totalLen = calculator_->Add(nameLen, greetLen);
std::string message = greeting + ", " + name + "!";
std::cout << "[Greeter] SayHello -> message=\"" << message << "\""
<< " totalLen(via Calculator::Add)=" << totalLen << "\n";
return message;
}
void SetLanguage(const std::string& lang) override
{
// Calculator::Sub 직접 호출 (언어 코드 길이 차이 계산 예시)
int32_t diff = calculator_->Sub(
static_cast<int32_t>(lang.size()),
static_cast<int32_t>(lang_.size())
);
std::cout << "[Greeter] SetLanguage: " << lang_
<< " -> " << lang
<< " (length diff via Calculator::Sub=" << diff << ")\n";
lang_ = lang;
emitLanguageChanged(lang_);
}
private:
ICalculator* calculator_{nullptr}; // 주입된 포인터
std::string lang_{"en"};
};
class GreeterPlugin : public IGreeterPlugin
{
public:
void start(sdbus::IConnection& conn, ICalculator* calculator) override
{
impl_ = std::make_unique<GreeterImpl>(
conn,
sdbus::ObjectPath{"/org/example/Greeter"},
calculator // ← 주입 전달
);
}
private:
std::unique_ptr<GreeterImpl> impl_;
};
extern "C" {
IGreeterPlugin* create_greeter_plugin() { return new GreeterPlugin(); }
void destroy_greeter_plugin(IGreeterPlugin* p) { delete p; }
}
server.cpp (Calculator → Greeter 순서로 로딩 후 주입)
#include <sdbus-c++/sdbus-c++.h>
#include "plugin/ICalculatorPlugin.h"
#include "plugin/IGreeterPlugin.h"
#include <dlfcn.h>
#include <iostream>
#include <stdexcept>
struct PluginHandle
{
void* handle{nullptr};
~PluginHandle() { if (handle) dlclose(handle); }
bool load(const char* path)
{
handle = dlopen(path, RTLD_LAZY);
if (!handle) std::cerr << "dlopen failed: " << dlerror() << "\n";
return handle != nullptr;
}
template<typename T>
T sym(const char* name)
{
dlerror();
auto fn = reinterpret_cast<T>(dlsym(handle, name));
if (const char* err = dlerror())
throw std::runtime_error(std::string("dlsym: ") + err);
return fn;
}
};
int main()
{
auto conn = sdbus::createSessionBusConnection(
sdbus::ServiceName{"org.example.Server"}
);
// 1. Calculator 먼저 로딩 (Greeter가 의존하므로)
PluginHandle calcHandle;
if (!calcHandle.load("./libcalculator_plugin.so")) return 1;
auto createCalc = calcHandle.sym<ICalculatorPlugin*(*)()>("create_plugin");
auto destroyCalc = calcHandle.sym<void(*)(ICalculatorPlugin*)>("destroy_plugin");
ICalculatorPlugin* calcPlugin = createCalc();
calcPlugin->start(*conn);
std::cout << "Calculator plugin loaded\n";
// 2. Calculator 포인터 획득
ICalculator* calculator = calcPlugin->getCalculator();
// 3. Greeter 로딩 후 Calculator 주입
PluginHandle greeterHandle;
if (!greeterHandle.load("./libgreeter_plugin.so")) return 1;
auto createGreeter = greeterHandle.sym<IGreeterPlugin*(*)()>("create_greeter_plugin");
auto destroyGreeter = greeterHandle.sym<void(*)(IGreeterPlugin*)>("destroy_greeter_plugin");
IGreeterPlugin* greeterPlugin = createGreeter();
greeterPlugin->start(*conn, calculator); // ← 주입
std::cout << "Greeter plugin loaded\n";
std::cout << "Server running...\n";
conn->enterEventLoop();
destroyGreeter(greeterPlugin);
destroyCalc(calcPlugin);
return 0;
}
방법 2: D-Bus 경유 호출
Greeter 플러그인 내부에서 Calculator를 D-Bus 클라이언트로 호출합니다. 플러그인 간 완전히 독립적이지만 같은 프로세스 안에서 D-Bus를 경유하는 오버헤드가 있습니다.
// greeter_plugin.cpp 내부 - D-Bus proxy로 Calculator 호출
class GreeterImpl : public sdbus::AdaptorInterfaces<org::example::Greeter_adaptor>
{
public:
GreeterImpl(sdbus::IConnection& conn, sdbus::ObjectPath path)
: AdaptorInterfaces(conn, std::move(path))
{
// Calculator proxy 생성 (D-Bus 경유)
calcProxy_ = sdbus::createProxy(
sdbus::ServiceName{"org.example.Server"},
sdbus::ObjectPath{"/org/example/Calculator"}
);
registerAdaptor();
}
protected:
std::string SayHello(const std::string& name) override
{
int32_t result{};
// D-Bus를 통해 Calculator::Add 호출
calcProxy_->callMethod("Add")
.onInterface(sdbus::InterfaceName{"org.example.Calculator"})
.withArguments(int32_t{1}, int32_t{2})
.storeResultsTo(result);
std::cout << "[Greeter] Calculator::Add via D-Bus = " << result << "\n";
return "Hello, " + name + "!";
}
// ...
private:
std::unique_ptr<sdbus::IProxy> calcProxy_;
};
최종 구조 비교
방법 1: 인터페이스 주입 (직접 호출)
server
├── calcPlugin->getCalculator() → ICalculator*
└── greeterPlugin->start(conn, ICalculator*)
│
GreeterImpl::SayHello()
│
calculator_->Add() ← 직접 함수 호출 (빠름)
방법 2: D-Bus 경유 호출
server
├── calcPlugin->start()
└── greeterPlugin->start()
│
GreeterImpl::SayHello()
│
calcProxy_->callMethod("Add") ← D-Bus 경유 (느림, 독립적)
플러그인 간 성능이 중요하면 방법 1, 완전한 독립성이 중요하면 방법 2를 선택하시면 됩니다.