2

I'm working on a piece of software which generates configuration data for certain hardware and currently needs to be adapted each time the hardware is released in a new version by an external company (this happens around twice a year). My current task is to introduce a plugin interface so the whole application doesn't have to be recompiled and re-released each time.

Currently, injecting new functionality - or replacing default functionality by adapted one - is already possible at runtime. However, there is one central enum which has all the different versions in it and prevents large parts of code from being separated from the executable.

The enum looks somewhat like this:

enum class HardwareVersion
{
    Stage1_Base,           // corresponds to version 5.11.37
    Stage1_Extensions,     // corresponds to version 5.12.01
    Stage2_Base,           // corresponds to version 8.00.00
    Stage2_ABC_Extensions, // corresponds to version 8.03.20
    Stage2_DEF_Extensions  // corresponds to version 9.01.00
}

Note: The names like "Stage 1 Base" are terms which are usually used when discussing with project management or the customer, hence they were used in code as well.

The users of the software need to view and modifiy data for every possible stage and generate the respective configuration data for the hardware version of interest (They are using various hardware versions simultaneously). Because of this, the software has several code parts similar to the following (theoretical code so you get the idea):

// This would be in a plugin
void addContent( HardwareVersion currentStage ) /* override */
{
    if (currentStage < Stage2_Base) return;

    // add stage 2 content
    ...
}

and

// This would be in a plugin as well
void initialize() /* override */
{
    _someFactory->register( HardwareVersion::Stage2_Base, new Stage2SubcontrolFactory() );
}

// This would be somewhere in the core application
std::shared_ptr<Subcontrol> createSubcontrol( HardwareVersion currentStage )
{
    return getFactory( currentStage )->createSubcontrol();
}

Now we could simply replace the enum by a typedef int and lose all type safety etc. We could also use GUIDs and lose all readability and make debugging really hard.

Both solutions feel really bad. This (finally) leads to the question:

What should you replace an enumeration with if values are to be provided by plugins?

[Edit:] To make this less subjective: The two major critera for solutions are high maintainability (this kinda implies readability for me) and high stability when adding new plugins.

  • Is it possible for you to create a central, managed registry for the codes? For instance, Ethernet addresses have to be registered by vendors with an assigned numbers authority. So the number codes are bound early in the life cycle. – BobDalgleish Sep 13 '16 at 16:49
  • I don't think the enum could be split up. – Gohan Sep 13 '16 at 17:05
  • Is it possible using xml/json file to configure the HardwareVersion, to make the version_id unique? in your plugin code, you can generate .h file from xml/json, which can make your code readability? – Gohan Sep 13 '16 at 17:11
  • Who owns the list of enums, and how does it get extended? – Erik Eidt Sep 13 '16 at 21:26
  • @ErikEidt The values are currently owned by the executable. In future, the executable shall only use, but not define the list, and the list will maybe have to be assembled dynamically – Tim Meyer Sep 14 '16 at 6:36
2

After sleeping over the question for a night and thinking about the various comments so far, I found the following solution to fit best for our needs:

Create a HardwareVersion C-style POD (*) struct like this:

struct IMPORT_EXPORT HardwareVersion
{
public:
    /// The first part of the version
    unsigned int Major;
    /// The second part of the version
    unsigned int Minor;
    /// The third part of the version
    unsigned int Release;
    /// A readable name mainly used for logging and debugging
    char* ReadableName;
}

Additionally, A Cpp Wrapper with functions like comparing, hash support, version tuple string ("XX.YY.ZZ" where XX = Major, YY = Minor and ZZ = Release; Note that by definition, none of the version parts can exceed 99 in this case) will be added, but not exposed through the plugin interface.

This has the following advantages:

  • The executable can be implemented to not allow plugins which provide HardwareVersion objects which are equal to already known ones OR it can be implemented to always use the newest one, so patches can be realized through plugins as well (This will most likely not be decided by me).
  • The plugins can specify which plugins they depend on through the version number (e.g. if Stage2_ABC_Extensions has a factory which provides a subclass of an object of Stage2_Base, the plugins could be initialized according to their dependency chain).
  • Code which shall be executed only above or below a certain stage is still readable
  • The application can be extended by only adding a plugin, without additional configuration required in the user environment

(*) POD to increase chances of being able to write a plugin with a different compiler (though I am aware this is not guaranteed)

  • 2
    You can avoid overloading all of those operators by #includeing the <utility> header. The Utility header includes generic overloads for operator!=, operator>, operator>= and operator<=. :-) – Karl Nicoll Sep 14 '16 at 8:58
  • @KarlNicoll As I'm planning to expose the class through a C Style interface: Will including utility break this somehow? – Tim Meyer Sep 15 '16 at 9:10
  • 1
    Yes is probably will, but C doesn't support operator overloading, so you'll need to extract the operators methods as non-member functions anyway :) – Karl Nicoll Sep 15 '16 at 9:13
  • @KarlNicoll Thanks Guess I'm gonna make use of <uitlity> in the CppWrapper I've created anyway (edited the answer a couple of minutes ago) and move the compare functions there (version comparing is done in C++ code anyway). – Tim Meyer Sep 15 '16 at 9:18
0

I would say that rather than use an actual enumeration, it would be advisable to have each plugin use a set of offsets from some base value. The base value for each plugin can be supplied when the plugin is loaded.

This makes it fairly easy to maintain a unique set of values across the entire system, while still allowing each plugin to be developed in isolation, without any development-time dependency.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.