반응형


가능 여부
가능합니다. 단, 두 플러그인은 서로 독립적인 .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를 선택하시면 됩니다.

반응형