5

A C# .NET application talks to an external component by calling a known API and marshalling interop structures from the component's response.

This is already implemented and working well. However, once versioning comes into the equation, things get a bit more complicated: as the component will evolve over time thus requiring interop structures be kept in-sync. The application must still be able to talk to components on older versions, so that it has to pick the right interop structs at run-time for a given version of the component.

I've been throwing this problem around in my head for a couple days and haven't come up with anything I'm particularly happy about. Google hasn't been much help, either, so I thought I'd try writing this up and posting it here in an effort to get some feedback. This is what I have so far.

Package all versions of the structs in a library, have a registry and decide at runtime which structs to use

Here, every new version of the structs is compiled into the new version of the .NET application, and some kind of registry is responsible for mapping API versions to interop struct versions in the library.

Class/struct naming becomes an issue, since Class1 would now have to be called Class1_v1_1 or some other way to disambiguate based on the name (either that, or use the same name and put them in a separate namespace).

Load versions of the interop structures from a versioned DLL

Here, every version of the interop structs would be compiled into an individual DLL and loaded dynamically based on the API version. A mapping would still need to be created between API version and DLL name.

The naming problem disappears, but certainly there are a lot more moving pieces for deployment and consequently things that can go wrong at runtime.


As I said, I'm not happy with either one and feel that there's a much obvious/cleaner/elegant solution which is escaping me. I can't imagine that I'm the first person who's had to support changing versions of a dependent component, so is there some known pattern which can provide some guidance for scenarios such as this?

Thanks in advance for any feedback &/or possible alternatives!

  • 1
    Hello downvoter, let me know how the question can be improved. Thanks! – user5877732 Aug 03 '17 at 11:18
  • Can you be more specific: what is your role in the problem? are you the component developer that makes it as easy as possible to use the component? Are you the component-consumer whou have certain problems with evolving components? Are we talkning about microsoft-ole-automation-apis (com)? dotnet-dlls only? are the dll-signed? (I am not the downvoter but for me the question apers to be too broad) – k3b Aug 09 '17 at 11:10
  • @k3b I'm not the component developer; I'm on the application side which consumes/interacts with the component and must support different versions of the component. Not OLE; I'm not sure this matters much as I have no control over what its written in –it's just a component with which I communicate based on an API gets versioned over time. The question is how to best deal with that evolution. I'll be happy to narrow down the scope of the question if you have suggestions – user5877732 Aug 17 '17 at 08:26
  • can you give concrete examples how interop structures and classes change? if the component developper uses "only append new fields/methods but never modify/delete existing methods/fields) then link your system against the oldest api that does the job. This worked good for me when programming in visual-basic-6 against the microsoft-internet-explorer. Without knowing how the changes are it is difficuild to answer your question. – k3b Oct 17 '17 at 10:00

2 Answers2

1

The number one priority should be to minimize the changes to the interop API, if you have any control over the "known API". Good API design minimizes changes over time.

Both solutions would work and the versioned dll solution might be cleaner, but more bulky, since you probably have much of the API that does not change between versions. You will have to manage the code reuse in some way, perhaps by using a common library.

I would detect the version using a call to the known API, if you can, instead of using a registry. That way you are not forced to use the same version across the whole machine.

Frank Hileman
  • 3,922
  • 16
  • 18
  • Thanks. I have no control over the API. I can indeed query for its version, but I must still load the appropriate data structures for that version, so my thinking was that I'd need some map between version and a collection of structs, both of the examples in the original question being possible implementations of that mapping; they just differ in how the collection of structs gets packaged. – user5877732 Aug 17 '17 at 08:38
  • C# does not have macros, but that is how we handled similar situations in C and C++: macros that expand conditionally depending on version number preprocessor definitions, to include or exclude fields depending on the version. The macros would actually be the field definitions. You might want to develop a small language and code generator. – Frank Hileman Aug 17 '17 at 18:48
0

If possible, introducing a version number to each structure is one approach, which is particularly popular with REST APIs and file formats (both binary and plain text). Your application then has the ability to upgrade each version to the next. That means you only need to worry about one upgrade path, too, and can just chain upgrades together as necessary. If desired, it's possible to add other upgrade paths to speed things up and you can even have downgrade paths (these are often visible in programs where multiple versions are maintained at the same time and there's usually a cost preventing many customers from upgrading immediately).

So for example, maybe you have an Employee struct. Perhaps version one is really simple:

{
    '_version': '1.0'
    'first_name': 'Amelia',
    'last_name': 'Bedelia',
    'employee_id': 123,
    'salary': 65000
}

But then later on, suppose we really wanna combine that name field. Let's just make that change cause presumably there's a good business reason for it! We have a new version to our structure:

{
    '_version': '1.1'
    'name': 'Amelia Bedelia',
    'employee_id': 123,
    'salary': 65000
}

Now, we could just have our codebase branch on the version number, but it's much more future proof and often cleaner to simply update the older structure to something compatible with the new one. This might involve filling in reasonable defaults, reading in additional information from elsewhere to fill in blanks, asking users for that information, or whatever. The idea, however, is to ensure that your codebase only needs to deal with the most recent version of the structure.

So we'd have some upgrade function that has some branch like:

if employee['_version'] == '1.0':
    # Let's just make an assumption about names and combine it blindly
    employee = {
        '_version': '1.1',
        'name': employee['first_name'] + ' ' + employee['last_name'],
        'employee_id': employee['employee_id'],
        'salary': employee['salary']
    }
# And so on for other version upgrades, returning the final object
# at the end.

And then we'd just keep doing this until we have the current version (which could then be converted to a stronger type). This pattern also works well for database updates (it's how Android's recommended handling for their SQLite databases works).

Now, this answer has been pretty high level so far. But it's not unique to where the data structure can be represented as a generic dictionary (although it certainly is a versatile and adaptable data structure for exchange of information across system boundaries). If data can be stored in a raw binary format and we always have the version in a certain position, we can easily choose which low level structure is used to interpret the data (and then apply updating from there). In which case we'd still pretty much need to store said structures somewhere, though (but the value comes from not having to have many code paths for these different versions of structures).

Kat
  • 330
  • 1
  • 10
  • 1
    -1 i think this doesnt answer the question. If im Not mistaken, OPs Software has to deal with all possiblwle Versions of the component, Not only with the newest one – marstato Aug 09 '17 at 07:24
  • @marstato is correct. The question is not about strategies to implement the changing interop structs, but how to have an architecture which can support that evolution, namely different versions of the interop structs on the consumer side (no control over the API or the structs it returns or changes to those structs) – user5877732 Aug 17 '17 at 08:30