diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f364cb40..70be1015 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,19 +14,13 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04, ubuntu-22.04] - compiler: [g++] - build: [shared-libsystemd] + os: [ubuntu-22.04] + compiler: [g++, clang] + build: [shared-libsystemd, embedded-static-libsystemd] include: - - os: ubuntu-22.04 - compiler: clang + - os: ubuntu-20.04 + compiler: gcc build: shared-libsystemd - - os: ubuntu-22.04 - compiler: g++ - build: embedded-static-libsystemd - - os: ubuntu-22.04 - compiler: clang - build: embedded-static-libsystemd steps: - uses: actions/checkout@v3 - name: install-libsystemd-toolchain @@ -65,18 +59,18 @@ jobs: # cmake --build . -j4 # sudo cmake --build . --target install - name: configure-debug - if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-20.04' + if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-22.04' run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror $SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON .. - - name: configure-release - if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-22.04' + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror $SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_GOOGLETEST_VERSION=1.14.0 .. + - name: configure-debug-no-tests + if: matrix.os == 'ubuntu-20.04' run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror $SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_GOOGLETEST_VERSION=1.14.0 .. - - name: configure-with-embedded-libsystemd + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror $SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=OFF -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON .. + - name: configure-release-with-embedded-libsystemd if: matrix.build == 'embedded-static-libsystemd' run: | mkdir build diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index f8add929..52d780b7 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -1579,7 +1579,7 @@ For more information on basic D-Bus types, D-Bus container types, and D-Bus type ### Extending sdbus-c++ type system -The above mapping between D-Bus and C++ types is what sdbus-c++ provides by default. However, the mapping can be extended. You can implement additional mapping between a D-Bus type and their custom type. +The above mapping between D-Bus and C++ types is what sdbus-c++ provides by default. However, the mapping can be extended. We can implement additional mapping between a D-Bus type and our custom type, i.e. teach sdbus-c++ to recognize and accept our own C++ types. We need two things to do that: @@ -1645,7 +1645,11 @@ Then we can simply use `std::list`s, serialize/deserialize them in a D-Bus messa Similarly, say we have our own `lockfree_map` which we would like to use natively with sdbus-c++ as a C++ type for D-Bus dictionary -- we can copy or build on top of `std::map` specializations. -As another example, say we have our custom type `my::Struct` which we'd like to use as a D-Bus structure representation (sdbus-c++ provides `sdbus::Struct` type for that, but we don't want to use it because using our custom type directly is more convenient). Again, we have to provide type traits and message serialization/deserialization functions for our custom type. We build our functions and specializations on top of `sdbus::Struct`, so we don't have to copy and write a lot of boiler-plate. Serialization/deserialization functions can be placed in the same namespace as our custom type, and will be found thanks to the ADR lookup. The `signature_of` specialization must always be in either `sdbus` namespace or in a global namespace: +#### Using user-defined structs instead of `sdbus::Struct` + +Many times, we have our own structs defined in our business logic code, and it would be very convenient to pass these structs directly to or from the sdbus-c++ IPC API where a D-Bus struct is expected, without having to translate them to or from `sdbus::Struct`. + +Say we have our custom type `my::Struct`: ```c++ namespace my { @@ -1655,7 +1659,23 @@ namespace my { std::string s; std::list l; }; +} // namespace my +``` +We can teach sdbus-c++ about our struct type very easily with `SDBUSCPP_REGISTER_STRUCT` macro: + +```c++ +SDBUSCPP_REGISTER_STRUCT(my::Struct, i, s, l); +``` + +The macro must be placed in the global namespace. The first argument is the struct type name and the remaining arguments are names of struct members. Of course, struct members must be of types supported by sdbus-c++ (or of user-defined types that sdbus-c++ was taught to recognize). This also means that members can be other structs -- provided that sdbus-c++ was taught about them with `SDBUSCPP_REGISTER_STRUCT` prior to this one. + +The macro effectively generates the `sdbus::Message` serialization and deserialization operators and the type traits (the `sdbus::signature_of` specialization) for `my::Struct`. + +Alternatively, we can to provide the message serialization/deserialization functions and the type traits manually. We can build on top of `sdbus::Struct`, so we don't have to copy and write a lot of boilerplate. Serialization/deserialization functions can be placed in the same namespace as our custom type, and will be found thanks to the ADR lookup. The `signature_of` specialization must always be in either `sdbus` namespace or in a global namespace: + +```c++ +namespace my { sdbus::Message& operator<<(sdbus::Message& msg, const Struct& items) { // Re-use sdbus::Struct functionality for simplicity -- view of my::Struct through sdbus::Struct with reference types diff --git a/include/sdbus-c++/Types.h b/include/sdbus-c++/Types.h index c927903a..256e9e35 100644 --- a/include/sdbus-c++/Types.h +++ b/include/sdbus-c++/Types.h @@ -385,14 +385,120 @@ namespace sdbus { } +// Making sdbus::Struct implement the tuple-protocol, i.e. be a tuple-like type template struct std::tuple_element<_I, sdbus::Struct<_ValueTypes...>> : std::tuple_element<_I, std::tuple<_ValueTypes...>> {}; - template struct std::tuple_size> : std::tuple_size> {}; +/********************************************//** + * @name SDBUSCPP_REGISTER_STRUCT + * + * A convenient way to extend sdbus-c++ type system with user-defined structs. + * + * The macro teaches sdbus-c++ to recognize the user-defined struct + * as a valid C++ representation of a D-Bus Struct type, and enables + * clients to use their struct conveniently instead of the (too + * generic and less expressive) `sdbus::Struct<...>` in sdbus-c++ API. + * + * The first argument is the struct type name and the remaining arguments + * are names of struct members. Members must be of types supported by + * sdbus-c++ (or of user-defined types that sdbus-c++ was taught to support). + * Members can be other structs (nesting is supported). + * The macro must be placed in the global namespace. + * + * For example, given the user-defined struct `ABC`: + * + * namespace foo { + * struct ABC + * { + * int number; + * std::string name; + * std::vector data; + * }; + * } + * + * one can teach sdbus-c++ about the contents of this struct simply with: + * + * SDBUSCPP_REGISTER_STRUCT(foo::ABC, number, name, data); + * + * The macro effectively generates the `sdbus::Message` serialization + * and deserialization operators and the `sdbus::signature_of` + * specialization for `foo::ABC`. + * + * Up to 16 struct members are supported by the macro. + * + ***********************************************/ +#define SDBUSCPP_REGISTER_STRUCT(STRUCT, ...) \ + namespace sdbus { \ + sdbus::Message& operator<<(sdbus::Message& msg, const STRUCT& items) \ + { \ + return msg << sdbus::Struct{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(items, __VA_ARGS__))}; \ + } \ + sdbus::Message& operator>>(sdbus::Message& msg, STRUCT& items) \ + { \ + sdbus::Struct s{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(items, __VA_ARGS__))}; \ + return msg >> s; \ + } \ + template <> \ + struct signature_of \ + : signature_of> \ + {}; \ + } \ + /**/ + +/*! + * @cond SDBUSCPP_INTERNAL + * + * Internal helper preprocessor facilities + */ +#define SDBUSCPP_STRUCT_MEMBERS(STRUCT, ...) \ + SDBUSCPP_PP_CAT(SDBUSCPP_STRUCT_MEMBERS_, SDBUSCPP_PP_NARG(__VA_ARGS__))(STRUCT, __VA_ARGS__) \ + /**/ +#define SDBUSCPP_STRUCT_MEMBERS_1(S, M1) S.M1 +#define SDBUSCPP_STRUCT_MEMBERS_2(S, M1, M2) S.M1, S.M2 +#define SDBUSCPP_STRUCT_MEMBERS_3(S, M1, M2, M3) S.M1, S.M2, S.M3 +#define SDBUSCPP_STRUCT_MEMBERS_4(S, M1, M2, M3, M4) S.M1, S.M2, S.M3, S.M4 +#define SDBUSCPP_STRUCT_MEMBERS_5(S, M1, M2, M3, M4, M5) S.M1, S.M2, S.M3, S.M4, S.M5 +#define SDBUSCPP_STRUCT_MEMBERS_6(S, M1, M2, M3, M4, M5, M6) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6 +#define SDBUSCPP_STRUCT_MEMBERS_7(S, M1, M2, M3, M4, M5, M6, M7) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7 +#define SDBUSCPP_STRUCT_MEMBERS_8(S, M1, M2, M3, M4, M5, M6, M7, M8) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8 +#define SDBUSCPP_STRUCT_MEMBERS_9(S, M1, M2, M3, M4, M5, M6, M7, M8, M9) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9 +#define SDBUSCPP_STRUCT_MEMBERS_10(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10 +#define SDBUSCPP_STRUCT_MEMBERS_11(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11 +#define SDBUSCPP_STRUCT_MEMBERS_12(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12 +#define SDBUSCPP_STRUCT_MEMBERS_13(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13 +#define SDBUSCPP_STRUCT_MEMBERS_14(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13, S.M14 +#define SDBUSCPP_STRUCT_MEMBERS_15(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13, S.M14, S.M15 +#define SDBUSCPP_STRUCT_MEMBERS_16(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13, S.M14, S.M15, S.M16 + +#define SDBUSCPP_STRUCT_MEMBER_TYPES(STRUCT, ...) \ + SDBUSCPP_PP_CAT(SDBUSCPP_STRUCT_MEMBER_TYPES_, SDBUSCPP_PP_NARG(__VA_ARGS__))(STRUCT, __VA_ARGS__) \ + /**/ +#define SDBUSCPP_STRUCT_MEMBER_TYPES_1(S, M1) decltype(S::M1) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_2(S, M1, M2) decltype(S::M1), decltype(S::M2) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_3(S, M1, M2, M3) decltype(S::M1), decltype(S::M2), decltype(S::M3) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_4(S, M1, M2, M3, M4) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_5(S, M1, M2, M3, M4, M5) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_6(S, M1, M2, M3, M4, M5, M6) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_7(S, M1, M2, M3, M4, M5, M6, M7) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_8(S, M1, M2, M3, M4, M5, M6, M7, M8) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_9(S, M1, M2, M3, M4, M5, M6, M7, M8, M9) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_10(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_11(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_12(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_13(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_14(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13), decltype(S::M14) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_15(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13), decltype(S::M14), decltype(S::M15) +#define SDBUSCPP_STRUCT_MEMBER_TYPES_16(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13), decltype(S::M14), decltype(S::M15), decltype(S::M16) + +#define SDBUSCPP_PP_CAT(X, Y) SDBUSCPP_PP_CAT_IMPL(X, Y) +#define SDBUSCPP_PP_CAT_IMPL(X, Y) X##Y +#define SDBUSCPP_PP_NARG(...) SDBUSCPP_PP_NARG_IMPL(__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define SDBUSCPP_PP_NARG_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _N, ...) _N + #endif /* SDBUS_CXX_TYPES_H_ */ diff --git a/tests/unittests/Message_test.cpp b/tests/unittests/Message_test.cpp index 05e44686..dd22903e 100644 --- a/tests/unittests/Message_test.cpp +++ b/tests/unittests/Message_test.cpp @@ -89,40 +89,21 @@ namespace sdbus { template struct sdbus::signature_of> - : sdbus::signature_of> + : sdbus::signature_of> {}; namespace my { - struct Struct { int i; std::string s; std::list l; - }; - - bool operator==(const Struct& lhs, const Struct& rhs) - { - return lhs.i == rhs.i && lhs.s == rhs.s && lhs.l == rhs.l; - } - - sdbus::Message& operator<<(sdbus::Message& msg, const Struct& items) - { - return msg << sdbus::Struct{std::forward_as_tuple(items.i, items.s, items.l)}; - } - - sdbus::Message& operator>>(sdbus::Message& msg, Struct& items) - { - sdbus::Struct s{std::forward_as_tuple(items.i, items.s, items.l)}; - return msg >> s; - } + friend bool operator==(const Struct& lhs, const Struct& rhs) = default; + }; } -template <> -struct sdbus::signature_of - : sdbus::signature_of>> -{}; +SDBUSCPP_REGISTER_STRUCT(my::Struct, i, s, l); /*-------------------------------------*/ /* -- TEST CASES -- */