-
Notifications
You must be signed in to change notification settings - Fork 25
Custom types
While cppcomponents provides for a wide range of types that can be used across compiler boundaries, you may want to define your own structure and use that with cppcomponents. This page will show you how to do that.
First a note about namespaced. namespace cppcomponents is the high-level namespace that you will use 95% of the time. namespace cross_compiler_interface deals with the low-level details in making cppcomponents work and includes such things as conversions, error mapping, and interface and function introspection.
There a 3 required steps and 2 option steps to take after defining your type
- Define a trivial type that is packed
- Specialize cross_compiler_interface::cross_conversion<> for your type
- Specialize cppcomponents::uuid_of<> for your type
Optionally
- Specialize cross_compiler_interface::cross_conversion_return for your type. Note a default implementation is already provided. Customize if you can increase efficiency
- Specialize cross_compiler_interface::type_name_getter<> for your type and the corresponding trivial type. You only need this if your value is a return type or parameter type in method of an interface where an attempt is made to get the signature of the methods as strings. (You will probably never do this).
Here is our example struct
struct Command{
std::int32_t value;
std::wstring name;
std::wstring function;
};
Now we define our trivial type. The tricky part is making sure all compilers
will agree on the packing. Fortunately #pragma pack(push,1) and #pragma pack(pop) are supported by MSVC, GCC, and Clang
Also we need to make sure are members get converted to a trivial type. The templated classcross_compiler_interface::cross_conversion<T> helps us with this. It defines 2 typedefs original_type which is the original type, and converted_type which is the trivial type that is suitable for passing across compiler boundaries. Therefore, to have a member of type T, in our trivial type we have a member of cross_compiler_interface::converted_type. Below is code to illustrate this
// Define packing 1 so we dont have inconsistencies between compilers
#pragma pack(push,1)
struct CommandTrivial{
// int32_t is already trivial
std::int32_t v;
// cross_compiler_interface::cross_conversion<> converts to and from a trivial type
cross_compiler_interface::cross_conversion<std::wstring>::converted_type n;
cross_compiler_interface::cross_conversion<std::wstring>::converted_type f;
};
#pragma pack(pop)
Now we need to specialize the cross_compiler_interface::cross_conversion template for our type. Here is code below
// cross_compiler_interface is where all the low-level conversion stuff live
namespace cross_compiler_interface{
template<>
struct cross_conversion<Command>{
// Required typedefs
typedef Command original_type;
typedef CommandTrivial converted_type;
// static functions to do the conversion
static converted_type to_converted_type(const original_type& o){
converted_type ret;
ret.v = o.value;
ret.n = cross_conversion<std::wstring>::to_converted_type(o.name);
ret.f = cross_conversion<std::wstring>::to_converted_type(o.function);
return ret;
}
static original_type to_original_type(const converted_type& c){
original_type ret;
ret.value = c.v;
ret.name = cross_conversion<std::wstring>::to_original_type(c.n);
ret.function = cross_conversion<std::wstring>::to_original_type(c.f);
return ret;
}
};
}
Finally to support the type being used with cppcomponent::delegate or cppcomponent::Channel we need an uuid for the type. We provide it thus
// For definition of uuid_of
#include <cppcomponents/implementation/uuid_combiner.hpp>
namespace cppcomponents{
template<>
struct uuid_of<Command>{
typedef cppcomponents::uuid<0x2841caf9, 0x6526, 0x4936, 0x8222, 0xc8d58582a927> uuid_type;
};
}Below is information on the optional things you can do
namespace cross_compiler_interface{
// Define the name of the structure for introspection
template<>
struct type_name_getter<Command>{
static std::string get_type_name(){ return "Command"; }
};
template<>
struct type_name_getter<CommandTrivial>{
static std::string get_type_name(){ return "CommandTrivial"; }
};
}Optionally if you want to customize how the type is returned you can do the following. Note: This is optional, if you do not do this, a default implementation will be provided. No example is provided, as the default works well for the case of command. Here is the template that needs to be specialized.
namespace cross_compiler_interface{
template<>
struct cross_conversion_return<MyStruct>{
typedef MyStruct return_type;
typedef <How to represent the return value to abi> converted_type;
// Note converted type will not necessary be the same as cross_conversion::converted_type
static void initialize_return(return_type&, converted_type&){
// Initialize the return type prior to function call
}
static void do_return(const return_type& r, converted_type& c){
// In function call, convert the return value to converted_type.
}
static void finalize_return(return_type& r, converted_type& c){
// After the call, convert the converted type to the high level return type
}
};
}