Parks Computing
Pedagogy for the Autodidactic Programmer
Welcome to the personal web site of Paul M. Parks. This is where I keep my web development skills current while sharing snippets of code, utilities, libraries, and what little development wisdom I've gathered in my career. It's also where I show off my family, talk about my hobbies, or just pontificate from time to time.
If you find the articles interesting, or if you'd like more information on a particular topic, let me know [paul@parkscomputing.com].
It's Not a DOS Prompt!
I've been hearing this a lot lately (you know who you are), so rather than pull all of you aside privately and give this lecture, I thought I'd do it once, publicly. You're not running a DOS prompt.
When you click on that shortcut that says, "Command Prompt" in Windows XP, or you run
So, please, next time don't tell me to run your favorite utility "at the DOS prompt." Let DOS rest in peace.
The first person to call Powershell a DOS prompt will get the lecture in real time. Posted November 13, 2009 0 Comments
When you click on that shortcut that says, "Command Prompt" in Windows XP, or you run
cmd.exe from the "Run" box, you're not starting a "DOS prompt." What you are starting is a command line interface, or just "command line" if you prefer. If you haven't actually run COMMAND.COM, it's not DOS!.So, please, next time don't tell me to run your favorite utility "at the DOS prompt." Let DOS rest in peace.
The first person to call Powershell a DOS prompt will get the lecture in real time. Posted November 13, 2009 0 Comments
An API is Forever
An API is an interface. Those of you that have worked with COM already know that once an interface is published, it can never, ever change. Ever. Not until the end of time. The reason is that some bit of code somewhere is going to be using that interface, and if you change it you've just broken that code. Of course, not changing an interface also means not deleting a portion of it.
At my job I have worked on, and currently maintain, a few APIs implemented in Windows DLLs, some of which have been around for a long time. I had to rewrite significant portions of the internal implementation of one particular DLL in order to add some new features, fix several bugs, and improve the overall performance of the DLL. The old API exposed from the DLL wasn't able to take advantage of the new features, and it wasn't designed all that well to begin with. Since nearly all of the use of that API was through a C++ template wrapper implemented a header, I just added a new set of API functions and changed the wrapper to use the new API.
I did some digging around in the code base (it's a huge code base) to see what else was using the class wrapper around the DLL, and I found out that the component was in far more widespread use than I had anticipated. There were components in all corners of the code base using it now.
The component was still desperately in need of a new API, so what I did was write a new one and forward the old API to the new API. I changed the header file that declared the API functions to look like this:
I hoped that the comments would make clear that the use of the old API is discouraged, and that any clients should move to the new API. Likewise, in the implementation, I forwarded the old API functions to the new API functions:
In some cases, forwarding the old API to the new API meant that I had to make the new API implement the semantics of the old API whenever it was being called through the old API functions. For example, the old API had a nasty habit of writing sub-keys all over the company's registry key under HKLM\SOFTWARE. I made the new API put these settings into a tidy sub-key that belonged to the API, like HKLM\SOFTWARE\ComanyName\Fictional. It would even hunt down the all of the old keys created by the old API and move them into the new sub-key. In a later story I'll explain why I had to preserve that behavior in the old API, but for now just remember that I did.
Recently, a co-worker changed a customer-specific branch of the archive so that it would point to the newest implementation of this component. He was writing an application using some libraries that called this component's API. He asked me to help with at a problem he was having, and when I went to look in the registry at some of the settings the API had written, I was dismayed to find that the settings were in the old location, all over the root of the company's HKLM\SOFTWARE key. At first, I thought that he was still somehow using the old DLL, but when I checked the version information on the DLL it was, indeed, the new one. How could this be?
As I mentioned above, most clients use this DLL through a C++ wrapper, since most of the archive is now C++. Only a handful of older C modules call this API directly. The wrapper is a C++ template class, which means it is implemented in a header file. This customer archive was still pointing at the old version the header that called the old API. As I mentioned above, the new API preserves the semantics of the old API when it is called through the old API, and that's what we were seeing here.
My first suggestion, without really thinking about it, was to change the archive to point at the newer header. My co-worker pointed out that this would mean rebuilding all of the components in the archive that used the header, not just the one he was working on.
We left it alone, and everything is working as it should be. At some point in the future we'll move all of the other components up to the newer version of the wrapper, and they'll start seeing the behavior associated with the new API. In the meantime, the customer still benefit from a less buggy and more efficient component.
At my job I have worked on, and currently maintain, a few APIs implemented in Windows DLLs, some of which have been around for a long time. I had to rewrite significant portions of the internal implementation of one particular DLL in order to add some new features, fix several bugs, and improve the overall performance of the DLL. The old API exposed from the DLL wasn't able to take advantage of the new features, and it wasn't designed all that well to begin with. Since nearly all of the use of that API was through a C++ template wrapper implemented a header, I just added a new set of API functions and changed the wrapper to use the new API.
I did some digging around in the code base (it's a huge code base) to see what else was using the class wrapper around the DLL, and I found out that the component was in far more widespread use than I had anticipated. There were components in all corners of the code base using it now.
The component was still desperately in need of a new API, so what I did was write a new one and forward the old API to the new API. I changed the header file that declared the API functions to look like this:
FICTIONAL_API void NaDoStuff(
HANDLE hFict,
DWORD flags,
LPCSTR name,
LPCVOID bytes,
DWORD byteCount,
LPCSTR desc);
// The rest of the new API was declared here...
// ...
// ...
/************************************************************************
Old, deprecated API. This API is still supported, but calls to all of
these functions are now forwarded to the new API described above.
Please use only the new API in new code.
************************************************************************/
/* DEPRECATED: Use NaDoStuff instead. */
FICTIONAL_API BOOL OaDoStuff(
HANDLE hFict,
DWORD flags,
LPCSTR desc,
LPCVOID bytes,
DWORD byteCount);
// The rest of the old API was declared here...
// ...
// ...
I hoped that the comments would make clear that the use of the old API is discouraged, and that any clients should move to the new API. Likewise, in the implementation, I forwarded the old API functions to the new API functions:
FICTIONAL_API void NaDoStuff(
HANDLE hFict,
DWORD flags,
LPCSTR name,
LPCVOID bytes,
DWORD byteCount,
LPCSTR desc)
{
// Go do stuff
}
// Elsewhere in the code..
FICTIONAL_API BOOL OaDoStuff(
HANDLE hFict,
DWORD flags,
LPCSTR desc,
LPCVOID bytes,
DWORD byteCount)
{
NaDoStuff(hFict, flags, "", bytes, byteCount, desc);
return TRUE;
}
In some cases, forwarding the old API to the new API meant that I had to make the new API implement the semantics of the old API whenever it was being called through the old API functions. For example, the old API had a nasty habit of writing sub-keys all over the company's registry key under HKLM\SOFTWARE. I made the new API put these settings into a tidy sub-key that belonged to the API, like HKLM\SOFTWARE\ComanyName\Fictional. It would even hunt down the all of the old keys created by the old API and move them into the new sub-key. In a later story I'll explain why I had to preserve that behavior in the old API, but for now just remember that I did.
Recently, a co-worker changed a customer-specific branch of the archive so that it would point to the newest implementation of this component. He was writing an application using some libraries that called this component's API. He asked me to help with at a problem he was having, and when I went to look in the registry at some of the settings the API had written, I was dismayed to find that the settings were in the old location, all over the root of the company's HKLM\SOFTWARE key. At first, I thought that he was still somehow using the old DLL, but when I checked the version information on the DLL it was, indeed, the new one. How could this be?
As I mentioned above, most clients use this DLL through a C++ wrapper, since most of the archive is now C++. Only a handful of older C modules call this API directly. The wrapper is a C++ template class, which means it is implemented in a header file. This customer archive was still pointing at the old version the header that called the old API. As I mentioned above, the new API preserves the semantics of the old API when it is called through the old API, and that's what we were seeing here.
My first suggestion, without really thinking about it, was to change the archive to point at the newer header. My co-worker pointed out that this would mean rebuilding all of the components in the archive that used the header, not just the one he was working on.
We left it alone, and everything is working as it should be. At some point in the future we'll move all of the other components up to the newer version of the wrapper, and they'll start seeing the behavior associated with the new API. In the meantime, the customer still benefit from a less buggy and more efficient component.
Labels: C, C++, software, Windows
Posted May 8, 2009 2 CommentsSubscribe to Posts [Atom]