한국어
Linux Programming
 

Linux에서 dbus-c++바인딩으로 서비스를 제공하는 프로세스 구현 방법을 소개한다.

참고로 dbus-c++ 바인더는 2011년 이후로 개발이 중지된 상태이지만 여기선 간단히 개념을 설명하기 위해 사용하였다. 현재 C++위한 바인더로 dbus-cxx, dbus-cpp, Qt D-Bus, GDBus 등이 있다.

 

먼저 아래명령으로 c++바인딩 패키지를 설치한다.

apt-get install libdbus-c++-bin libdbus-c++-dev

 

작업 폴더를 만들고 아래의 예제처럼 xml로 인터페이스를 작성한다.

아래의 예제에서 type="s" name="name" direction="in" 이것은 string타입의 입력 인자를 의미한다.

interface.xml

<?xml version="1.0" ?>
<node name="/org/freedesktop/DBus/Examples/Echo">
  <interface name="org.freedesktop.DBus.EchoDemo">
    <method name="HelloString">
      <arg type="s" name="name" direction="in"/>
      <arg type="s" name="greeting" direction="out"/>
    </method>

    <signal name="EchoCount">
      <arg type="y" name="count"/>
    </signal>
  </interface>
</node>

 

아래표는 D-Bus에서 사용하는 타입들이다.

 

Category

Conventional Name

Code

Description

reserved

INVALID

0 (ASCII NUL)

Not a valid type code, used to terminate signatures

fixed, basic

BYTE

121 (ASCII 'y')

8-bit unsigned integer

fixed, basic

BOOLEAN

98 (ASCII 'b')

Boolean value, 0 is FALSE and 1 is TRUE. Everything else is invalid.

fixed, basic

INT16

110 (ASCII 'n')

16-bit signed integer

fixed, basic

UINT16

113 (ASCII 'q')

16-bit unsigned integer

fixed, basic

INT32

105 (ASCII 'i')

32-bit signed integer

fixed, basic

UINT32

117 (ASCII 'u')

32-bit unsigned integer

fixed, basic

INT64

120 (ASCII 'x')

64-bit signed integer

fixed, basic

UINT64

116 (ASCII 't')

64-bit unsigned integer

fixed, basic

DOUBLE

100 (ASCII 'd')

IEEE 754 double

string-like, basic

STRING

115 (ASCII 's')

UTF-8 string (must be valid UTF-8). Must be nul terminated and contain no other nul bytes.

string-like, basic

OBJECT_PATH

111 (ASCII 'o')

Name of an object instance

string-like, basic

SIGNATURE

103 (ASCII 'g')

A type signature

container

ARRAY

97 (ASCII 'a')

Array

container

STRUCT

114 (ASCII 'r'), 40 (ASCII '('), 41 (ASCII ')')

Struct; type code 114 'r' is reserved for use in bindings and implementations to represent the general concept of a struct, and must not appear in signatures used on D-Bus.

container

VARIANT

118 (ASCII 'v')

Variant type (the type of the value is part of the value itself)

container

DICT_ENTRY

101 (ASCII 'e'), 123 (ASCII '{'), 125 (ASCII '}')

Entry in a dict or map (array of key-value pairs). Type code 101 'e' is reserved for use in bindings and implementations to represent the general concept of a dict or dict-entry, and must not appear in signatures used on D-Bus.

fixed, basic

UNIX_FD

104 (ASCII 'h')

Unix file descriptor

reserved

(reserved)

109 (ASCII 'm')

Reserved for a 'maybe' type compatible with the one in GVariant, and must not appear in signatures used on D-Bus until specified here

reserved

(reserved)

42 (ASCII '*')

Reserved for use in bindings/implementations to represent any single complete type, and must not appear in signatures used on D-Bus.

reserved

(reserved)

63 (ASCII '?')

Reserved for use in bindings/implementations to represent any basic type, and must not appear in signatures used on D-Bus.

reserved

(reserved)

64 (ASCII '@'), 38 (ASCII '&'), 94 (ASCII '^')

Reserved for internal use by bindings/implementations, and must not appear in signatures used on D-Bus. GVariant uses these type-codes to encode calling conventions.

 

작성된 xml을 service를 제공하는 쪽에서 사용 할 헤더파일로 변환한다.

dbusxx-xml2cpp interface.xml --adaptor=echo-interface-adaptor.h  

 

변환된 헤더파일은 대략 아래와 같다.

echo-interface-adaptor.h

/*
 * This file was automatically generated by dbusxx-xml2cpp; DO NOT EDIT!
 */

#ifndef __dbusxx__echo_interface_adaptor_h__ADAPTOR_MARSHAL_H
#define __dbusxx__echo_interface_adaptor_h__ADAPTOR_MARSHAL_H

#include <dbus-c++/dbus.h>
#include <cassert>

namespace org {
namespace freedesktop {
namespace DBus {

class EchoDemo_adaptor
: public ::DBus::InterfaceAdaptor
{
public:

    EchoDemo_adaptor()
    : ::DBus::InterfaceAdaptor("org.freedesktop.DBus.EchoDemo")
    {
        register_method(EchoDemo_adaptor, HelloString, _HelloString_stub);
    }

    ::DBus::IntrospectedInterface *introspect() const 
    {
        static ::DBus::IntrospectedArgument HelloString_args[] = 
        {
            { "name", "s", true },
            { "greeting", "s", false },
            { 0, 0, 0 }
        };
        static ::DBus::IntrospectedArgument EchoCount_args[] = 
        {
            { "count", "y", false },
            { 0, 0, 0 }
        };
        static ::DBus::IntrospectedMethod EchoDemo_adaptor_methods[] = 
        {
            { "HelloString", HelloString_args },
            { 0, 0 }
        };
        static ::DBus::IntrospectedMethod EchoDemo_adaptor_signals[] = 
        {
            { "EchoCount", EchoCount_args },
            { 0, 0 }
        };
        static ::DBus::IntrospectedProperty EchoDemo_adaptor_properties[] = 
        {
            { 0, 0, 0, 0 }
        };
        static ::DBus::IntrospectedInterface EchoDemo_adaptor_interface = 
        {
            "org.freedesktop.DBus.EchoDemo",
            EchoDemo_adaptor_methods,
            EchoDemo_adaptor_signals,
            EchoDemo_adaptor_properties
        };
        return &EchoDemo_adaptor_interface;
    }

public:

    /* properties exposed by this interface, use
     * property() and property(value) to get and set a particular property
     */

public:

    /* methods exported by this interface,
     * you will have to implement them in your ObjectAdaptor
     */
    virtual std::string HelloString(const std::string& name) = 0;

public:

    /* signal emitters for this interface
     */
    void EchoCount(const uint8_t& arg1)
    {
        ::DBus::SignalMessage sig("EchoCount");
        ::DBus::MessageIter wi = sig.writer();
        wi << arg1;
        emit_signal(sig);
    }

private:

    /* unmarshalers (to unpack the DBus message before calling the actual interface method)
     */
    ::DBus::Message _HelloString_stub(const ::DBus::CallMessage &call)
    {
        ::DBus::MessageIter ri = call.reader();

        std::string argin1; ri >> argin1;
        std::string argout1 = HelloString(argin1);
        ::DBus::ReturnMessage reply(call);
        ::DBus::MessageIter wi = reply.writer();
        wi << argout1;
        return reply;
    }
};

} } } 
#endif //__dbusxx__echo_interface_adaptor_h__ADAPTOR_MARSHAL_H

 

이제 위의 클래스를 상속받고 가상함수를 오버라이드하여 정의한다.

echo-server.h

#include "echo-interface-adaptor.h"

class EchoServer : public org::freedesktop::DBus::EchoDemo_adaptor,
                   public DBus::IntrospectableAdaptor,
                   public DBus::ObjectAdaptor
{
public:
    EchoServer(DBus::Connection * conn);
    virtual ~EchoServer();

public:
    virtual std::string HelloString(const std::string& name);
};

 

echo-server.cpp

#include "echo-server.h"
#include <stdio.h>

EchoServer::EchoServer(DBus::Connection * conn)
: DBus::ObjectAdaptor(*conn, "/org/freedesktop/DBus/Examples/Echo")
{

}

EchoServer::~EchoServer()
{

}

std::string EchoServer::HelloString(const std::string& name)
{
    printf("Input string : %s\n", name.c_str() );
    std::string ret = name + " world!!";
    return ret;
}

 

main함수에서는 service를 위한 SessionBus 연결 및 객체를 생성한다.

main.cpp

#include "echo-server.h"
#include <unistd.h>
#include <future>
#include <stdio.h>

DBus::BusDispatcher dispatcher;

int main()
{
    DBus::default_dispatcher = &dispatcher;
    DBus::_init_threading();
    DBus::Connection conn = DBus::Connection::SessionBus();
    conn.request_name("org.freedesktop.DBus.EchoService");

    EchoServer server(&conn);

    uint8_t count = 0;

    std::future<void>fut = std::async(std::launch::async, [=] {
        dispatcher.enter();
    });

    // emit EchoSignal every seconds.
    while ( true )
    {
        printf("emit signal 'EchoSignal' count : %d\n", count);
        server.EchoCount(count++);
        sleep(1);
    }
    return 0;
}

 

dbus-c++-1 라이브러리 및 INCLUDE Path를 컴파일 시에 인자로 넘겨준다.

 

이제 구현 테스트를 위한 방법으로 우분투 소프트웨어 센터에서 "D-Feet" 검색하고 설치한다.

어플리케이션을 실행한 상태에서 D-Feet을 실행하고 Session Bus 탭을 선택 후 "echo"를 검색하면 아래처럼 보일 것이다.

d-feet.png

 

오른쪽 Object path영역에서 인터페이스를 펼치면 아래처럼 서비스쪽의 Methods 및 Signals 등이 보인다.

Methods에서 HelloString을 더블클릭한다.

d-feet1.png

 

Input영역에 문자열 입력 후 Execute를 클릭한다.

d-feet2.png

 

그러면 service 쪽 출력내용이 다음과 같이 보일 것이다.

dbus_test.png

 

이 서비스 프로세스와 통신하는 클라이언트는 파이썬으로 구현, 테스트 예제: https://makersweb.net/python/6517

윈도우에서 Qt D-Bus를 사용한 예제: https://makersweb.net/qt/13174