반응형






# Adaptor (서버 측) 코드 생성
sdbus-c++-xml2cpp calculator.xml --adaptor=calculator-adaptor.h

# Proxy (클라이언트 측) 코드 생성
sdbus-c++-xml2cpp calculator.xml --proxy=calculator-proxy.h



xml

<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/example/Calculator">
    <interface name="org.example.Calculator">
        <method name="Add">
            <arg name="a" type="i" direction="in"/>
            <arg name="b" type="i" direction="in"/>
            <arg name="result" type="i" direction="out"/>
        </method>
        <method name="Sub">
            <arg name="a" type="i" direction="in"/>
            <arg name="b" type="i" direction="in"/>
            <arg name="result" type="i" direction="out"/>
        </method>
        <method name="Multiply">
            <arg name="a" type="i" direction="in"/>
            <arg name="b" type="i" direction="in"/>
            <arg name="result" type="i" direction="out"/>
        </method>
        <method name="Divide">
            <arg name="a" type="i" direction="in"/>
            <arg name="b" type="i" direction="in"/>
            <arg name="result" type="i" direction="out"/>
        </method>
        <signal name="Calculated">
            <arg name="operation" type="s"/>
            <arg name="result" type="i"/>
        </signal>
    </interface>
</node>





cmake list

cmake_minimum_required(VERSION 3.12)
project(calculator_sdbus_cpp CXX)

# C++ 표준 설정
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 컴파일 옵션
add_compile_options(-Wall -Wextra)

# pkg-config 사용
find_package(PkgConfig REQUIRED)

# sdbus-c++ 찾기
pkg_check_modules(SDBUSCPP REQUIRED sdbus-c++)

# XML 파일 경로
set(CALCULATOR_XML ${CMAKE_CURRENT_SOURCE_DIR}/calculator.xml)

# 코드 생성 명령 (옵션)
# find_program(SDBUSCPP_XML2CPP sdbus-c++-xml2cpp)
# if(SDBUSCPP_XML2CPP)
#     # Adaptor 생성
#     add_custom_command(
#         OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/calculator-adaptor.h
#         COMMAND ${SDBUSCPP_XML2CPP} ${CALCULATOR_XML}
#                 --adaptor=${CMAKE_CURRENT_BINARY_DIR}/calculator-adaptor.h
#         DEPENDS ${CALCULATOR_XML}
#         COMMENT "Generating adaptor from XML"
#     )
#     
#     # Proxy 생성
#     add_custom_command(
#         OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/calculator-proxy.h
#         COMMAND ${SDBUSCPP_XML2CPP} ${CALCULATOR_XML}
#                 --proxy=${CMAKE_CURRENT_BINARY_DIR}/calculator-proxy.h
#         DEPENDS ${CALCULATOR_XML}
#         COMMENT "Generating proxy from XML"
#     )
# endif()

# 서버 실행 파일
add_executable(calculator-server
    calculator-server.cpp
    # ${CMAKE_CURRENT_BINARY_DIR}/calculator-adaptor.h  # 생성된 헤더
)

target_include_directories(calculator-server PRIVATE
    ${SDBUSCPP_INCLUDE_DIRS}
    ${CMAKE_CURRENT_BINARY_DIR}
)

target_link_libraries(calculator-server
    ${SDBUSCPP_LIBRARIES}
)

target_link_directories(calculator-server PRIVATE
    ${SDBUSCPP_LIBRARY_DIRS}
)

target_compile_options(calculator-server PRIVATE
    ${SDBUSCPP_CFLAGS_OTHER}
)

# 클라이언트 실행 파일
add_executable(calculator-client
    calculator-client.cpp
    # ${CMAKE_CURRENT_BINARY_DIR}/calculator-proxy.h  # 생성된 헤더
)

target_include_directories(calculator-client PRIVATE
    ${SDBUSCPP_INCLUDE_DIRS}
    ${CMAKE_CURRENT_BINARY_DIR}
)

target_link_libraries(calculator-client
    ${SDBUSCPP_LIBRARIES}
)

target_link_directories(calculator-client PRIVATE
    ${SDBUSCPP_LIBRARY_DIRS}
)

target_compile_options(calculator-client PRIVATE
    ${SDBUSCPP_CFLAGS_OTHER}
)

# 설치 규칙
install(TARGETS calculator-server calculator-client
    RUNTIME DESTINATION bin
)

install(FILES ${CALCULATOR_XML}
    DESTINATION share/dbus-1/interfaces
)

# 빌드 정보 출력
message(STATUS "sdbus-c++ version: ${SDBUSCPP_VERSION}")
message(STATUS "sdbus-c++ include dirs: ${SDBUSCPP_INCLUDE_DIRS}")
message(STATUS "sdbus-c++ libraries: ${SDBUSCPP_LIBRARIES}")



server.cpp

#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <csignal>

// 생성된 adaptor 헤더를 포함해야 하지만, 여기서는 직접 구현
class Calculator : public sdbus::AdaptorInterfaces<>
{
public:
    Calculator(sdbus::IConnection& connection, std::string objectPath)
        : AdaptorInterfaces(connection, std::move(objectPath))
    {
        // 인터페이스 등록
        registerAdaptor();
    }

    ~Calculator()
    {
        unregisterAdaptor();
    }

protected:
    // 메서드 구현
    int32_t Add(int32_t a, int32_t b)
    {
        int32_t result = a + b;
        std::cout << "Add called: " << a << " + " << b << " = " << result << std::endl;
        
        // 시그널 발송
        emitCalculated("Add", result);
        
        return result;
    }

    int32_t Sub(int32_t a, int32_t b)
    {
        int32_t result = a - b;
        std::cout << "Sub called: " << a << " - " << b << " = " << result << std::endl;
        
        emitCalculated("Sub", result);
        
        return result;
    }

    int32_t Multiply(int32_t a, int32_t b)
    {
        int32_t result = a * b;
        std::cout << "Multiply called: " << a << " * " << b << " = " << result << std::endl;
        
        emitCalculated("Multiply", result);
        
        return result;
    }

    int32_t Divide(int32_t a, int32_t b)
    {
        if (b == 0) {
            throw sdbus::Error("org.example.Calculator.DivisionByZero", "Division by zero");
        }
        
        int32_t result = a / b;
        std::cout << "Divide called: " << a << " / " << b << " = " << result << std::endl;
        
        emitCalculated("Divide", result);
        
        return result;
    }

    void emitCalculated(const std::string& operation, int32_t result)
    {
        auto signal = createSignal("org.example.Calculator", "Calculated");
        signal << operation << result;
        emitSignal(signal);
    }

private:
    void registerAdaptor()
    {
        // Add 메서드 등록
        registerMethod("org.example.Calculator", "Add", "ii", "i",
                      [this](sdbus::MethodCall call) {
                          int32_t a, b;
                          call >> a >> b;
                          auto reply = call.createReply();
                          reply << Add(a, b);
                          reply.send();
                      });

        // Sub 메서드 등록
        registerMethod("org.example.Calculator", "Sub", "ii", "i",
                      [this](sdbus::MethodCall call) {
                          int32_t a, b;
                          call >> a >> b;
                          auto reply = call.createReply();
                          reply << Sub(a, b);
                          reply.send();
                      });

        // Multiply 메서드 등록
        registerMethod("org.example.Calculator", "Multiply", "ii", "i",
                      [this](sdbus::MethodCall call) {
                          int32_t a, b;
                          call >> a >> b;
                          auto reply = call.createReply();
                          reply << Multiply(a, b);
                          reply.send();
                      });

        // Divide 메서드 등록
        registerMethod("org.example.Calculator", "Divide", "ii", "i",
                      [this](sdbus::MethodCall call) {
                          int32_t a, b;
                          call >> a >> b;
                          auto reply = call.createReply();
                          reply << Divide(a, b);
                          reply.send();
                      });

        // Calculated 시그널 등록
        registerSignal("org.example.Calculator", "Calculated", "si");
    }
};

static bool running = true;

void signalHandler(int signal)
{
    std::cout << "\nReceived signal " << signal << ", shutting down..." << std::endl;
    running = false;
}

int main()
{
    // 시그널 핸들러 설정
    std::signal(SIGINT, signalHandler);
    std::signal(SIGTERM, signalHandler);

    try
    {
        // D-Bus 연결 생성
        auto connection = sdbus::createSessionBusConnection();

        // 서비스 이름 요청
        connection->requestName("org.example.Calculator");

        // Calculator 객체 생성
        Calculator calculator(*connection, "/org/example/Calculator");

        std::cout << "Calculator server started at org.example.Calculator" << std::endl;
        std::cout << "Object path: /org/example/Calculator" << std::endl;
        std::cout << "Press Ctrl+C to exit." << std::endl;

        // 이벤트 루프
        while (running)
        {
            connection->processPendingEvent();
        }

        std::cout << "Server stopped." << std::endl;
    }
    catch (const sdbus::Error& e)
    {
        std::cerr << "D-Bus error: " << e.what() << std::endl;
        return 1;
    }
    catch (const std::exception& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}



client.cpp

#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <string>
#include <thread>
#include <chrono>

class CalculatorProxy : public sdbus::ProxyInterfaces<>
{
public:
    CalculatorProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath)
        : ProxyInterfaces(connection, std::move(destination), std::move(objectPath))
    {
        registerProxy();
    }

    ~CalculatorProxy()
    {
        unregisterProxy();
    }

    // 메서드 호출
    int32_t Add(int32_t a, int32_t b)
    {
        int32_t result;
        auto method = createMethodCall("org.example.Calculator", "Add");
        method << a << b;
        auto reply = method.call();
        reply >> result;
        return result;
    }

    int32_t Sub(int32_t a, int32_t b)
    {
        int32_t result;
        auto method = createMethodCall("org.example.Calculator", "Sub");
        method << a << b;
        auto reply = method.call();
        reply >> result;
        return result;
    }

    int32_t Multiply(int32_t a, int32_t b)
    {
        int32_t result;
        auto method = createMethodCall("org.example.Calculator", "Multiply");
        method << a << b;
        auto reply = method.call();
        reply >> result;
        return result;
    }

    int32_t Divide(int32_t a, int32_t b)
    {
        int32_t result;
        auto method = createMethodCall("org.example.Calculator", "Divide");
        method << a << b;
        auto reply = method.call();
        reply >> result;
        return result;
    }

    // 시그널 핸들러 등록
    void onCalculated(std::function<void(const std::string&, int32_t)> callback)
    {
        registerSignalHandler("org.example.Calculator", "Calculated", 
                            [callback](sdbus::Signal signal) {
                                std::string operation;
                                int32_t result;
                                signal >> operation >> result;
                                callback(operation, result);
                            });
    }

private:
    void registerProxy()
    {
        // 프록시 등록 완료
        finishRegistration();
    }
};

int main(int argc, char* argv[])
{
    try
    {
        // D-Bus 연결 생성
        auto connection = sdbus::createSessionBusConnection();

        // Calculator 프록시 생성
        CalculatorProxy calculator(*connection, "org.example.Calculator", "/org/example/Calculator");

        // 시그널 핸들러 등록
        calculator.onCalculated([](const std::string& operation, int32_t result) {
            std::cout << "Signal received - Operation: " << operation 
                      << ", Result: " << result << std::endl;
        });

        // 시그널 수신을 위한 스레드
        std::thread eventLoop([&connection]() {
            while (true) {
                connection->processPendingEvent();
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
        });
        eventLoop.detach();

        std::cout << "Calculator Client started" << std::endl;
        std::cout << "Commands: add, sub, multiply, divide, quit" << std::endl;

        // 대화형 루프
        std::string command;
        while (true)
        {
            std::cout << "\nEnter command: ";
            std::cin >> command;

            if (command == "quit" || command == "exit")
            {
                break;
            }

            int32_t a, b, result;
            std::cout << "Enter first number: ";
            std::cin >> a;
            std::cout << "Enter second number: ";
            std::cin >> b;

            try
            {
                if (command == "add")
                {
                    result = calculator.Add(a, b);
                    std::cout << "Result: " << a << " + " << b << " = " << result << std::endl;
                }
                else if (command == "sub")
                {
                    result = calculator.Sub(a, b);
                    std::cout << "Result: " << a << " - " << b << " = " << result << std::endl;
                }
                else if (command == "multiply")
                {
                    result = calculator.Multiply(a, b);
                    std::cout << "Result: " << a << " * " << b << " = " << result << std::endl;
                }
                else if (command == "divide")
                {
                    result = calculator.Divide(a, b);
                    std::cout << "Result: " << a << " / " << b << " = " << result << std::endl;
                }
                else
                {
                    std::cout << "Unknown command: " << command << std::endl;
                }
            }
            catch (const sdbus::Error& e)
            {
                std::cerr << "D-Bus error: " << e.getName() << " - " << e.getMessage() << std::endl;
            }
        }

        std::cout << "Client stopped." << std::endl;
    }
    catch (const sdbus::Error& e)
    {
        std::cerr << "D-Bus error: " << e.what() << std::endl;
        return 1;
    }
    catch (const std::exception& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}




빌드 및 실행
# 프로젝트 구조
project/
├── calculator.xml
├── calculator-server.cpp
├── calculator-client.cpp
└── CMakeLists.txt

# 코드 생성 (수동)
sdbus-c++-xml2cpp calculator.xml --adaptor=calculator-adaptor.h
sdbus-c++-xml2cpp calculator.xml --proxy=calculator-proxy.h

# 빌드
mkdir build && cd build
cmake ..
make

# 서버 실행 (터미널 1)
./calculator-server

# 클라이언트 실행 (터미널 2)
./calculator-client

반응형