Working on the borders

| 4 Comments

As anyone who has downloaded any of my code from here, or my company site or CodeProject will know, I have a particular way of doing things. The code I write tends to follow a particular style and as such assumes particular dependencies. The approach that I tend to take when I'm making stuff available on the web is that if you want to use the code I'm giving away, then you need to adjust your code in whatever ways you need to be able to consume what I provide.

The code I'm currently working on is a bit different and that's led me to have to think about how my code interacts with the rest of the world.

A long time ago when I wrote my first couple of articles for CodeProject I was keen to make the code usable by the maximum number of people with the least amount of problems. Too much of the code that I'd seen online had unreasonable requirements (on CodeProject the big problem for me was that often code that used MFC when MFC wasn't actually required, I'm not a great fan of MFC so that meant that I'd often find code to do the job and then have to rewrite it so that it did the job without MFC...).

In my view, if your code depends on other code then to convince someone to use your code you also have to convince them to use the other code, your dependencies. I experimented with the idea that to make reusable code maximally reusable you need to be really careful about the interface between your code and the outside world. This resulted in my Registry classes which used only standard Windows types in the interfaces to the class. I deliberately avoided the use of STL because, back then, lots of people didn't want to use it.

This was probably the last block of code that I produced without embracing the STL. I have some vague memories of deciding that working without the luxury of a string class was just not worth the effort and finding myself in the position where my day job used STL so I began using it on my own projects.

These days you might think that having STL types in your class interface isn't such a big deal and it isn't, as long as the clients of your code want to use the same STL as you, or you ship source and your code builds with their preferred version of STL as well as it builds with yours. When clients began asking for STLPort support I made the required changes so that all of my code could build with either the STL that comes with Visual Studio or STLPort. This works well for products that ship in source form or where the border between your code and someone else's is an executable file. It works less well (not at all in fact) if the border is a library boundary.

The key point is that the border between your code and someone else's code is really important. The decisions that you make (or don't make) and the dependencies that you require at that border can determine if your code is usable by your clients. It's easy to ignore this if your code isn't currently used by anyone else. If your code will never be used outside of the team and environment that you're currently in then you can ignore border issues and just get on with your life. If that's not the case then you need to think about the issues and decide what's best for the piece of code in question.

My reusable code library is structured as several static libraries. Each of these shares the same expectations of code that it integrates with. The libraries all use a form of STL (the same one, but they can all be switched to use a different one by changing one place...) and they all make a lot of use of exceptions to report exceptional conditions. I tend to favour C++ types over Windows types where possible, hardly ever pass around raw pointers to dynamically allocated memory and don't tend to use any of the Visual Studio specific COM things. Unsurprisingly each of code libraries interoperates well with the other libraries.

When the time comes to use third-party code, such as OpenSSL, Expat, COM objects, etc, I manage the border crossing by writing a shim library that makes the external code conform to the expectations of my libraries. Only the shim library needs deal with the details of the external code. This is similar to what the Visual Studio #import directive does for COM object, only not as crap ;)

Up until recently most of my work has either ended up with its external border being either a COM object or the executable. For executables there's nothing to do; the border is between my code and the user. In the case of COM objects the translations required to cross from my style of code to the COM style is fairly well understood. There are lots of rules about what you can do with a COM interface and what you can't and, generally, it's just a case of inserting exception handlers and translating types.

In-process COM objects are, of course, just a special type of DLL which has a detailed and precise border protocol. When it comes to producing other libraries for consumption outside of your team you need to think about the border and define your own border protocol. You can use various tricks, such as the pimpl idiom, etc to hide the internals of your classes and therefore protect your clients from needing to know or care about how you implement things. This works nicely with DLLs as the DLL can link with whatever you depend on and the users of your DLL can simply consume your DLL by way of the interfaces that you define in your header files. With static libraries it's a bit different in that you force your clients to link to anything that you depend on using the same type of linkage that you require (which is usually problematic).

Memory management confusion, STL types, templates, etc all complicate the interface to a C++ DLL and make the border harder to cross. The easiest border to cross is one that places the least requirements on the user and thus the easiest code to use from a client perspective is code that has the simplest interface. This is where .Net wins big time as it standardises all manner of things that are complicated issues in C and C++ and provides the Common Language Specification, CLS, as a border protocol.

So, the easiest way to provide reusable code is in the form of a DLL, that's hardly news. The best way to make your code easy to use for others is to place as few restrictions on them as you can, again, hardly rocket science. All of this leads me towards a DLL interface that pretty much mimics the way the Win32 API works... Windows types only and error codes rather than exceptions and ideally C based rather than C++...

Luckily to get from where my code is now to where it needs to be to play well with others means providing a shim layer that 'does the right thing'. It needs to use the pimpl idiom to hide everything that might pollute our DLL interface and then translate between types and error handling techniques. Conveniently it seems that I can auto generate this shim layer. Right now I'm still exposing C++ objects in the interface - because that's how I'd prefer to use the interface - but it's no problem to generate a C version with opaque handles over the top and I expect I'll do that too.

At the start of the week I began to 'dog food' the new DLL and I consider it a success that the first thing I thought was, I need to wrap this into something that's more my style ;) and, of course, that was auto generated too.

4 Comments

Using *your* code in my applications, i am forced to include your Win32tools or ComTools libraries. Really in the beginning i really got upset doing so. But now, whether i use your code or not, i *always* include your Win32Tools OR CoMTools libraries. I can't live without these. I've learnt so much from these and today my own coding style is something like yours. So the moral of the story is 'Good things can be accompained anywhere anytime" ;)

:) That's nice to hear.

I think my current delivery style for the free source that I give away is OK. The problems come when you don't want to give the source away and don't want to impose your way on your clients.

It's funny reading an 18 month old post that looks like ancient history. MFC ? STL ? DLL ? What are these crazy acronyms ? Fossils from the neolithic age ? Diseases we have since conquered ? Yeah.. a little of both.

If you are not coding in a managed language by now I feel really sorry for you. Java or .NET, you have two choices, but please.. C++ ?? It's so... last millenium. At any rate, I'm glad I'm not a device driver programmer and get to use modern languages. 10x productivity increase, at least.

It's funny reading a comment from someone with such a narrow view of programming. There are lots of good reasons why various systems are written in unmanaged code. Sometimes managed code is appropriate, sometimes it's not; the bit where people really add value is deciding when to use each of the tools that's available to them.

Leave a comment