flipCode - Tech File - Toby Jones [an error occurred while processing this directive]
Toby Jones
Click the name for some bio info

E-Mail: tjones@hot-shot.com



   09/21/2000, Introduction


Since this is my first article, I'll begin by introducing myself and talking a bit about what I'm doing here. My name is Toby Jones and I work for Sonic Foundry. Sonic Foundry is located in Madison, Wisconsin, which is also home to Raven Software and Human Head Studios. Sonic Foundry is known for its unparalleled sound and music software, but more recently we have moved into the video market. I'm part of that move. My current focus is writing the video capture portion of our Video Factory and Vegas Video software.

I must say, working for a real company is a lot different from writing software out of my dorm room. For one, I get to work with people that are a lot smarter than me. This is important because there is a lot of room to grow. Another thing is the access to a huge library of code, from which I can draw ideas and learn from. It's amazing.

One of the coolest things that happens when you first start is how efficient you become. Your IDE is your best friend, and it pays to know it well. So, today I'm going to talk about some of the cool features I use daily, and some features that we use at Sonic Foundry to make our products better. It's amazing that a number of the features I take for granted every day, many people don't even know about. I have a lot I'd like to talk about, so I'll move quickly. If you have a question (or criticism :) email me at tjones@hot-shot.com. So lets begin.



Tip 1) Reduce indentation to enhance readability.


First, lets talk about coding style. About a month ago, there was a tip on flipcode on how to reduce code indentation. The problem was multiple nested 'if' statements (though any flow control construct has the same problems). So you have:

if(this)
   if(that)
      if(the other thing)
The solution basically transformed it into:

if(!this)
   return;
if(!that)
   return;
etc...
The author did recognize that multiple exit points from a function are worse than goto's. Here's a method that I use regularly that eliminates this situation:

do {
   if(!this)
      break;
   if(!that)
      break;
   if(!the other thing)
      break;
} while(0);
Yes, that is a 'while(0)' which always falls through. So now you have the original benefit without multiple exits from the function. Some may say that this is a trick. I had a teacher once who said "Do something once, and its a trick. Do something more than once, and it becomes a technique." So there you have it. Note that this technique doesn't work well with nested while or for loops, but the most common nested problem is with 'ifs'



Tip 2) Move lvalues to the right side when possible


Do you ever have code that looks something like:

if(var == MAXSIZE)
and accidentally type:

if(var = MAXSIZE)
There can be much pain and suffering trying to track this down. Well, there is a simple way to fix this. Always, when possible, put lvalues on the right hand side of an expression. So if you did this:

if(MAXSIZE = var)
then the compiler won't compile it. It seems awkward at first to write something like:

if(MAXSIZE == var)
but it will save you a lot of grief in the future.



Tip 3) Use character set independent code


Another thing we do is code for multiple character sets. Our code will compile clean under Unicode, ASCII, or even multi-byte character sets. Some say that this is not important. The fact is, code that can do this is better abstracted, and generally, just better code than code that is character set specific. So how do you do this you say?

An ASCII string is declared in C like:

char sz[] = "my string";
while a Unicode string would be declared like:

short sz[] = L"my string";
(optionally, you could use the C++ type wchar_t). The difference is, of course, the size of each character. ASCII is one byte, and Unicode is two bytes. The way to abstract this is to use some Windows defined types and macros. A character set independent string would look like:

TCHAR sz[] = _T("my string");
The character type becomes TCHAR and the string is wrapped with a _T("") macro, which converts your string to a TCHAR array. To show how this abstraction works well, I'll introduce some simple string copy functions.

char * StrCpy(char * pszDest, const char * pszSrc) {
    char * psz = pszDest;

    while(*psz++ = *pszSrc++)
        ;

    return pszDest;
}

unsigned short * StrCpy(unsigned short * pszDest, const unsigned short * pszSrc)
{
    unsigned short * psz = pszDest;

    while(*psz++ = *pszSrc++)
        ;

    return pszDest;
}
While C doesn't have overloaded functions, C++ does, and you should use them. In this case, the character type is completely abstracted. This is a pretty simple example, so it is tough to see the power of this. Some watchful readers may already know that the Visual C++ library already comes with a function called _tcscpy that is character set independent. But that function can't do this:

// convert wide character to character
unsigned short * StrCpy(unsigned short * pszDest, const char * pszSrc) {
    int cch = MultiByteToWideChar(CP_ACP, 0, pszSrc, -1, pszDest, 0);
    MultiByteToWideChar(CP_ACP, 0, pszSrc, -1, pszDest, cch);
    return pszDest;
}

// convert character to wide character
char * StrCpy(char * pszDest, const unsigned short * pszSrc) {
    int cch = WideCharToMultiByte(CP_ACP, 0, pszSrc, -1, pszDest, 0, NULL, NULL);
    WideCharToMultiByte(CP_ACP, 0, pszSrc, -1, pszDest, cch, NULL, NULL);
    return pszDest;
}
Now, I have a StrCpy that will not only copy a string, but it can convert one character set into another while copying. This way, you don't have to care about the specific character set of your source and destination. To you, the programmer, they are just strings. So, if you build Unicode and you need to access some DirectPlay method that is ASCII, you can do this:

void func(TCHAR *szSrc) {
    char szDest[MAX_BUFFER];

    StrCpy(szDest, szSrc);
    DirectPlayFunctionA(szDest);
}
Bam! Now you will always call that DirectPlay ASCII function, even if you are providing Unicode data. As a side note, Windows ME has a bug in MultiByteToWideChar (and probably WideCharToMultiByte, but I haven't checked) that won't copy a zero length string. So that will have to be done by hand.



Tip 4) Have your compiler generate browse information


Enough code tricks for one day. Lets talk about your IDE. I use Visual C++ daily, and I wouldn't switch for the world. It is very good, if you know how to use it. One feature that I cannot live without is browse information. Browse information is not turned on by default, so you will have to turn it on manually. If you can afford a slightly longer build time (you can, its worth it), then enable browse information by going to Project->Settings, and checking the "Generate Browse Info" box under the C/C++ tab.

Next time you compile, you will have all sorts of cool features. Right click on a function name, and you will have the option of going right to where that function is declared. This is a _big_ help in multi-module projects. Alt-F12 on a function name can bring up a graph of functions that call or are called by the highlighted function.



Tip 5) Use macros in your Watch window


Sometimes we call Windows APIs, but don't call GetLastError to check the error code. If we are debugging this code, we have no way to know why a function failed.

Type "@err,hr" in your Watch window (without the quotes). @err is a macro that calls GetLastError. The "hr" formats the macro as an HRESULT. Now your error strings will appear in your Watch window.





Tip 6) More Watch window fun


Another mistake I make is not checking my return codes. I hate to have to recompile just to test one while debugging. The Watch window can help here too.

Besides @err, there are several processor registers and pseudo registers you can use in the Watch window. Since most C functions return their result in the EAX register, I can type @eax in the Watch window. So if I don't assign the function to a variable, I can still view the value this way.

Any processor register can be viewed in the Watch window by prefixing it with @. VC6 also has a few pseudo registers that you can access. One of them is @err, which we already saw. Others include @clk (a clock register of sorts) and @tib (thread information block, NT/Win2K only).



Tip 7) Learn your keyboard shortcuts


Keyboard shortcuts will cut down your development time significantly. Here is one that I haven't seen documented.

If you have large blocks of #ifdef code, you can jump between them with Ctrl+J (jump forward) and Ctrl+K (jump backward). Positioning the cursor on an #ifdef/#if, and pressing Ctrl+J will take you to the corresponding #else/#endif. This is very handy when you have multiple nested #ifdefs.



Tip 8) The undocumented autoexp.dat file


An undocumented feature is the autoexp.dat file. In your MSDev directory is a file that holds all kinds of internal configuration parameters. Jay Bazuzi of Microsoft calls the file self-documenting, but he gives some great uses for it in his Power Debugging seminar. One of these seminars can be found at http://msdn.microsoft.com/library/mmedia/9-324.asx



Tip 9) Rewrite DebugBreak to make life easier


Other handy devices are to rewrite your DebugBreak and assert macros. Since Windows is now completely x86 dependent, there is little reason not to do this. DebugBreak is a Windows API that stops execution when called. This is fine, except DebugBreak is a function call. This means that you have to step out of the function before you can start working. What would be nice is if we could break at the point of execution of DebugBreak instead of inside the function.

Under x86, we can replace the DebugBreak function with a macro:

#define DebugBreak() _asm { int 3 }
Interrupt 3 is the breakpoint interrupt under x86 architectures. Now, when DebugBreak is called, we stop immediately and we don't need to step out of the function to begin work.



Tip 10) Roll your own asserts


Asserts come in two flavors: runtime and compile time. Most people are familiar with runtime asserts. These asserts simply stop execution when an expression is false. By redefining your runtime assert, you can make it a bit more powerful. Here's one:

#define rassert(expn) \
    if(!expn) { \
        static bool fDontTellMeAgain = false; \
        if(!fDontTellMeAgain) { \
            MessageBoxA(NULL, #expn, \
                              "Assertion Failed!", \
                              MB_OK | MB_ICONERROR); \
            DebugBreak(); \
        } \
    }
If you have an assert that is fired regularly, you may want to ignore it for the duration of a test session. This assert can be turned off. The possibilities are endless. Assert boxes could give all kinds of debugging information, if you take the time to write it. John Robbins describes a superassert in the February 99 issue of MSJ magazine.



Tip 11) Compile time asserts


Most people aren't as familiar with compile time asserts. Compile time asserts evaluate a constant expression to make sure it is valid. If the expression is invalid, the module won't compile. This might be useful if, say, you had a structure that you wanted to assure was a certain size when compiled. Something like this:

struct mystruct_t {
   ... some definitions here
};

void myfunc() {
    cassert(sizeof(mystruct_t) == 32)
}
How could something like this be defined? Well, compile time asserts operation on the principle of a construct that is either valid or invalid depending on the Boolean truth of an expression. At Sonic Foundry, we depend on specialized templates to do this, but John Robbins described a far simpler cassert in MSDN magazine:

#define cassert(expn) typedef char __C_ASSERT__[(expn)?1:-1]
Clever. If true, the assert expands to __C_ASSERT__1, which is valid. If the expression is false, the assert expands to __C_ASSERT__-1 which is not a valid typedef, hence a compile error.

I originally wanted to cover some thoughts on optimization as well as memory leak tracking. I'm still researching some of the material I wanted to discuss, and we are getting a bit long today, so we'll wrap it up. My main point is that most people don't come close to taking advantage of the inherent power of their IDE and support tools. Everything I've discussed today I use regularly, and the people I work with do too. It's amazing what tools are at your disposal. Some of my favorite tools I use I only learned about in the last year: Spy++ (VC6), Depends (VC6), GraphEdit (DirectX Media), DXETool (DirectX Media), and others. Play around with these things. You may be surprised at the power you have.

"If you know your compiler, and you know yourself, you need not fear the outcome of a thousand battles." -Sun Tsu





  • 01/23/2001 - Why I Trust My Compiler
  • 12/06/2000 - Latest News And A Simple Memory Tracker
  • 09/21/2000 - Introduction

  • This document may not be reproduced in any way without explicit permission from the author and flipCode. All Rights Reserved. Best viewed at a high resolution. The views expressed in this document are the views of the author and NOT neccesarily of anyone else associated with flipCode.

    [an error occurred while processing this directive]