반응형

 

 client가 덧셈(Add), 뺄셈(Sub) DBus method 요청을 하면 server가 이를 수행하여 이 결과를 DBus method 응답으로 반환하고, server가 덧셈, 뺄셈 연산의 결과를 signal로 전송하는 예제이다.

 이 때, gdbus-codegen을 활용하여 DBus method와 signal을 핸들링하는 코드를 자동 생성하게하고 이를 이용하여 빌드하고 실행해본다.

 

 

프로젝트 구조

project/
├── CMakeLists.txt
├── example.xml
├── server.c
└── client.c

 

 

1. example.xml

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.example.Calculator">
    <method name="Add">
      <arg type="i" name="a" direction="in"/>
      <arg type="i" name="b" direction="in"/>
      <arg type="i" name="result" direction="out"/>
    </method>
    <method name="Sub">
      <arg type="i" name="a" direction="in"/>
      <arg type="i" name="b" direction="in"/>
      <arg type="i" name="result" direction="out"/>
    </method>
    <signal name="CalculationDone">
      <arg type="s" name="operation"/>
      <arg type="i" name="result"/>
    </signal>
  </interface>
</node>

 

 

2. server.c

#include <glib.h>
#include <gio/gio.h>
#include "calculator-generated.h"

static Calculator *calculator_skeleton = NULL;

static gboolean
on_handle_add(Calculator *object,
              GDBusMethodInvocation *invocation,
              gint a, gint b,
              gpointer user_data)
{
    gint result = a + b;
    calculator_complete_add(object, invocation, result);
    
    // Signal 발송
    gchar *msg = g_strdup_printf("Add(%d, %d)", a, b);
    calculator_emit_calculation_done(object, msg, result);
    g_free(msg);
    
    g_print("Add: %d + %d = %d\n", a, b, result);
    return TRUE;
}

static gboolean
on_handle_sub(Calculator *object,
              GDBusMethodInvocation *invocation,
              gint a, gint b,
              gpointer user_data)
{
    gint result = a - b;
    calculator_complete_sub(object, invocation, result);
    
    // Signal 발송
    gchar *msg = g_strdup_printf("Sub(%d, %d)", a, b);
    calculator_emit_calculation_done(object, msg, result);
    g_free(msg);
    
    g_print("Sub: %d - %d = %d\n", a, b, result);
    return TRUE;
}

static void
on_bus_acquired(GDBusConnection *connection,
                const gchar *name,
                gpointer user_data)
{
    GError *error = NULL;
    
    calculator_skeleton = calculator_skeleton_new();
    
    g_signal_connect(calculator_skeleton, "handle-add",
                     G_CALLBACK(on_handle_add), NULL);
    g_signal_connect(calculator_skeleton, "handle-sub",
                     G_CALLBACK(on_handle_sub), NULL);
    
    if (!g_dbus_interface_skeleton_export(
            G_DBUS_INTERFACE_SKELETON(calculator_skeleton),
            connection,
            "/org/example/Calculator",
            &error))
    {
        g_printerr("Failed to export object: %s\n", error->message);
        g_error_free(error);
    }
    else
    {
        g_print("Calculator service ready\n");
    }
}

static void
on_name_acquired(GDBusConnection *connection,
                 const gchar *name,
                 gpointer user_data)
{
    g_print("Name acquired: %s\n", name);
}

static void
on_name_lost(GDBusConnection *connection,
             const gchar *name,
             gpointer user_data)
{
    g_printerr("Name lost: %s\n", name);
}

int main(void)
{
    GMainLoop *loop;
    guint owner_id;
    
    loop = g_main_loop_new(NULL, FALSE);
    
    owner_id = g_bus_own_name(G_BUS_TYPE_SESSION,
                               "org.example.Calculator",
                               G_BUS_NAME_OWNER_FLAGS_NONE,
                               on_bus_acquired,
                               on_name_acquired,
                               on_name_lost,
                               NULL, NULL);
    
    g_main_loop_run(loop);
    
    g_bus_unown_name(owner_id);
    g_main_loop_unref(loop);
    if (calculator_skeleton)
        g_object_unref(calculator_skeleton);
    
    return 0;
}

 

 

3. client.c

#include <glib.h>
#include <gio/gio.h>
#include "calculator-generated.h"

static void
on_calculation_done(Calculator *proxy,
                    const gchar *operation,
                    gint result,
                    gpointer user_data)
{
    g_print("Signal received: %s = %d\n", operation, result);
}

int main(void)
{
    GError *error = NULL;
    Calculator *proxy;
    gint result;
    
    proxy = calculator_proxy_new_for_bus_sync(
        G_BUS_TYPE_SESSION,
        G_DBUS_PROXY_FLAGS_NONE,
        "org.example.Calculator",
        "/org/example/Calculator",
        NULL, &error);
    
    if (error)
    {
        g_printerr("Failed to create proxy: %s\n", error->message);
        g_error_free(error);
        return 1;
    }
    
    // Signal 연결
    g_signal_connect(proxy, "calculation-done",
                     G_CALLBACK(on_calculation_done), NULL);
    
    // Add 호출
    if (calculator_call_add_sync(proxy, 10, 5, &result, NULL, &error))
    {
        g_print("Add result: %d\n", result);
    }
    else
    {
        g_printerr("Add failed: %s\n", error->message);
        g_error_free(error);
    }
    
    // Sub 호출
    if (calculator_call_sub_sync(proxy, 10, 5, &result, NULL, &error))
    {
        g_print("Sub result: %d\n", result);
    }
    else
    {
        g_printerr("Sub failed: %s\n", error->message);
        g_error_free(error);
    }
    
    // Signal 수신 대기
    g_print("Waiting for signals (Press Ctrl+C to exit)...\n");
    GMainLoop *loop = g_main_loop_new(NULL, FALSE);
    g_main_loop_run(loop);
    
    g_object_unref(proxy);
    g_main_loop_unref(loop);
    
    return 0;
}

 

 

 

4. CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(dbus_example C)

find_package(PkgConfig REQUIRED)
pkg_check_modules(GLIB REQUIRED glib-2.0 gio-2.0)

## gdbus-codegen을 위해 GIO-UNIX 라이브러리가 필요하다
pkg_check_modules(GIO-UNIX gio-unix-2.0 REQUIRED)

find_program(GDBUS_CODEGEN gdbus-codegen REQUIRED)

set(DBUS_XML ${CMAKE_CURRENT_SOURCE_DIR}/example.xml)

set(GENERATED_SOURCES
    ${CMAKE_CURRENT_BINARY_DIR}/calculator-generated.c
    ${CMAKE_CURRENT_BINARY_DIR}/calculator-generated.h
)

add_custom_command(
    OUTPUT ${GENERATED_SOURCES}
    COMMAND ${GDBUS_CODEGEN}
        --interface-prefix org.example.
        --generate-c-code ${CMAKE_CURRENT_BINARY_DIR}/calculator-generated
        ${DBUS_XML}
    DEPENDS ${DBUS_XML}
    COMMENT "Generating D-Bus code"
)

add_executable(calculator_server
    server.c
    ${GENERATED_SOURCES}
)

target_include_directories(calculator_server PRIVATE
    ${GLIB_INCLUDE_DIRS}
    ${GIO-UNIX_INCLUDE_DIRS}
    ${CMAKE_CURRENT_BINARY_DIR}
)

target_link_libraries(calculator_server
    ${GLIB_LIBRARIES}
    ${GIO-UNIX_LDFLAGS}
)

add_executable(calculator_client
    client.c
    ${GENERATED_SOURCES}
)

target_include_directories(calculator_client PRIVATE
    ${GLIB_INCLUDE_DIRS}
    ${GIO-UNIX_INCLUDE_DIRS}
    ${CMAKE_CURRENT_BINARY_DIR}
)

target_link_libraries(calculator_client
    ${GLIB_LIBRARIES}
    ${GIO-UNIX_LDFLAGS}
)

 

 

빌드 방법

mkdir build && cd build
cmake ..
make

 

 

실행 결과

서버:
Name acquired: org.example.Calculator
Calculator service ready
Add: 10 + 5 = 15
Sub: 10 - 5 = 5

클라이언트:
Add result: 15
Sub result: 5
Waiting for signals (Press Ctrl+C to exit)...
Signal received: Add(10, 5) = 15
Signal received: Sub(10, 5) = 5

 

 

 

■ (수동으로 calculator-generated 코드를 생성하고 싶은 경우) gdbus-codegen 명령어

◈ cmake를 통해 gdbus-codegen이 호출이 될 것이므로 굳이 수동으로 gdbus-codegen을 호출할 필요가 없다

gdbus-codegen \
  --interface-prefix org.example. \
  --generate-c-code calculator-generated \
  example.xml

 

 

빌드 시 필요한 패키지들

- cmake (sudot apt get install cmake)

- pkg-config (PkgConfig)  (sudo apt-get install pkg-config)

- glib-2.0, gio-2.0 (sudo apt install libglib2.0-dev이 패키지 하나에 glib, gio, gio-unix-2.0가 포함됨)

반응형