I'm working on a library that is somewhat complex. It offers a bunch of DLL_EXPORT
ed functions which use some custom struct
s. At the moment, those structs are defined with the functions that use them, like so:
struct MyDataType
{
std::wstring name;
std::wstring purpose;
}
DLL_EXPORT int DoSomething(MyDataType instruction);
For maintainability purposes, I'm gradually switching that library to the more standard interface style:
struct MyLibraryInterface
{
virtual int DoSomething(MyDataType instruction) = 0;
}
To make the interface side of things a little easier, I'm working with a 3rd-party library. This library requires I do a little bit of setup work for each custom datatype I use, and this is where I'm uncertain as to how to proceed. I see a few different ways I could do it:
Method 1
//MyLibraryInterface.h
namespace MyLibraryInterface_v1_Types
{
struct MyDataType
{
std::wstring name;
std::wstring purpose;
}
}
#include "MyLibraryInterface_v1_Types_Backend.private.h" //this header would contain the required "paperwork" to make my datatype work with the 3rd-party lib
struct MyLibraryInterface_v1
{
virtual int DoSomething(MyDataType instruction) = 0;
}
Advantages:
Disadvantages:
The #include
halfway through the header looks sloppy and out-of-place (although I have noted that certain MS headers use this technique)
Is a custom namespace really that necessary, or am I just confusing users? (The encapsulation itself is definitely necessary, since I might change the definition of this datatype down the road. But I don't know if I need a custom namespace or if I should just put the datatype declaration in the declaration of the struct that will use it.)
Method 2
//MyLibraryInterface.h
namespace MyLibraryInterface_v1_Types
{
struct MyDataType
{
std::wstring name;
std::wstring purpose;
}
}
//3rd-party "paperwork" is directly placed here
struct MyLibraryInterface_v1
{
virtual int DoSomething(MyDataType instruction) = 0;
}
Advantages:
- No messy-looking
#include
halfway through my header
Disadvantages:
3rd-party paperwork now shows up in the header my users will be using (they don't need to see it and I'd rather they not, for cosmetic/ease-of-understanding reasons)
This feels like I'm not taking advantage of the datatype namespace's power at all, since the 3rd-party code is left free-floating in my library's code and not encapsulated
Method 3
//MyLibraryInterface.h
struct MyLibraryInterface_v1
{
struct MyDataType
{
std::wstring name;
std::wstring purpose;
}
virtual int DoSomething(MyDataType instruction) = 0;
}
#include "MyLibraryInterface_v1_Types_Backend.private.h" //this header would contain the required "paperwork" to make my datatype work with the 3rd-party lib
Advantages:
- Gets rid of the custom namespace
- Less confusion because users are now referring to
MyLibraryInterface_v1::MyDataType
instead of MyLibraryInterface_v1_Types::MyDataType
, which is more intuitive if they're calling a function in MyLibraryInterface_v1
anyway
Disadvantages:
Method 4
//MyLibraryInterface_v1_Types.h
namespace MyLibraryInterface_v1_Types
{
struct MyDataType
{
std::wstring name;
std::wstring purpose;
}
}
//3rd-party paperwork can be directly placed here, immediately following the definition of the custom datatype
//MyLibraryInterface.h
#include "MyLibraryInterface_v1_Types.h" /* this header, as defined above, holds the definitions of the custom datatypes this library will use. It also includes the 3rd-party paperwork required to make those datatypes work. It can't be a private header, though, because users will need to access it to use the custom types. */
struct MyLibraryInterface_v1
{
virtual int DoSomething(MyDataType instruction) = 0;
}
Advantages:
- 3rd-party paperwork goes directly with the declarations of the datatypes that need it
Disadvantages:
Users may have a hard time finding or using the custom datatypes
Feels pretty unintuitive, since the datatypes reside in both a separate header and a separate namespace
So which is best? Am I overlooking a different, better, method entirely? Or am I simply going to have to bite the bullet and accept that, no matter which way I decide to go with this, I'll have some problems to face.
Update with a bit more information:
The 3rd-party library I'm using wraps my interface in a struct
for me. So I'll be able to create an object of MyLibraryInterface*
, the 3rd-party library will let me access an implementation of that interface from a specified DLL, and then I can call MyLibraryObj->DoSomething()
. This essentially is a variant of pImpl.
This 3rd-party library also automatically wraps any STL types and any custom datatypes so they can be used across multiple compilers, so my std::wstring
usage is completely safe here. However, the library requires that I provide certain setup information for how to wrap custom types. I have to provide that setup information somewhere after each custom type is defined, which bars the "normal" pattern of putting an #include
with the private setup info at the top of my interface header. I also can't remove the private setup information from the interface header entirely; anyone who calls my library via this interface will have to use the 3rd-party library to do so, and they'll need to provide the declaration of the interface again so the library knows what it's looking for in a given DLL. All I can do is try to make the private setup work look as neat and unintrusive as possible, as well as ideally marking it as something my library's users will never need or want to work directly with.
Additionally, I have the option of putting my custom datatypes into the interface struct
or into their own namespace
. I toyed with putting them directly in the struct
at first, but since some of these datatypes are constant data (enum class
es) it seemed a bit sloppy to put them in the struct
with the function declarations. A namespace
"felt" cleaner, but with the downside that functions and datatypes would be treated differently (myLibraryObj->DoSomething()
vs MyLibraryInterface_v1_Types::MyDataType
) and therefore might be less intuitive than keeping everything in the struct
(myLibraryObj->DoSomething()
, MyLibraryInterface_v1::MyDataType
).