Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tech Tips


July 2001/Tech Tips


Using Namespaces for Constants
Michael Kennedy
[email protected]

Most Windows programs make extensive use of symbolic constants, such as COM result codes (S_OK, E_FAIL, etc.), and values returned from GetLastError() (e.g., ERROR_SUCCESS). When writing C or C++ code, these constants are often defined using the #define preprocessor directive. For example, a quick look at winerror.h shows the COM constants defined this way:

#define S_OK     ((HRESULT)0x00000000L)
#define S_FALSE  ((HRESULT)0x00000001L)

A slightly better way to define constants is to use const int (or const <type>). For example:

const int MERGE_ERROR_NO_DATA = 20;
const int MERGE_ERROR_INCOMPATIBLE = 25;

These are two standard ways programs use symbolic constants. However, a slight modification to the second method turns out to offer some significant advantages. Suppose you change the second example to:

namespace MERGE_ERRORS
{
   const int NO_DATA = 20;
   const int INCOMPATIBLE = 25;
};

You get the obvious advantage of bundling your constants into a defined group so you can reuse the member names for other types of errors. But, there’s an additional advantage for Visual C++ v6.0 users. Creating a namespace for your constants allows the IntelliSense feature in Visual C++ v6.0 to assist you in locating the correct name while you write code. When you type “MERGE_ERRORS::”, Visual C++ will pop up an auto complete box listing all of the available constants in that namespace! If you add some comments, like:

namespace MERGE_ERRORS
{
   const int NO_DATA = 20;      // No data in either file
   const int INCOMPATIBLE = 25; // The files are incompatible
};

the auto complete box even displays your comment as the description for the constant. See Figure 1 for a screen shot taken from the example project included in this month’s code archive (kennedy.zip inside techtips.zip).

Another Look at RecycleFile()
Stephen Schumacher
[email protected]

A Tech Tip in the January 2001 WDJ presented source code for a function named RecycleFile() that is intended to be used in place of DeleteFile(). RecycleFile() calls SHFileOperation() to move the file to the recycle bin. Unfortunately, the call to SHFileOperation() is missing a flag that should be included to prevent Windows from displaying a message box in the event of an error (such as file in use). To more accurately mimic DeleteFile(), you should include FOF_NOERRORUI to prevent annoying message boxes. For example:

Info.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT;
...
Result = SHFileOperation(&Info);

The FOF_SILENT flag is not as important, but prevents the appearance of progress bars. An additional problem is that, unlike DeleteFile(), the current implementation of RecycleFile() will delete folders. To prevent this behavior, the if statement checking for (Status != 0) should be changed to:

if ((Status != 0) && ((GetFileAttributes(DeletePath) & FILE_ATTRIBUTE_DIRECTORY) == 0))

Debugging Windows 98 Tooltips
Chris Branch
[email protected]

It’s common to hear about bugs that cause some type of failure in the release build of an application, but don’t show up in the debug build. I recently ran into the opposite situation. One day a co-worker asked me to help find the cause of an access violation that would occur repeatedly on his Windows 98 machine anytime our application tried to display a tooltip. At first, I suspected a problem with the code I had written to handle TTN_GETDISPINFO notifications from the tooltip. After reading over the code several times and stepping through the code with the Visual C++ debugger, everything seemed correct, but the crash persisted. Then we noticed that other applications were also having trouble displaying tooltips. In particular, Explorer would crash when trying to display a tooltip for any of the taskbar buttons.

At this point, we decided it was time to reboot Windows. After the reboot, we couldn’t repeat the problem. No matter what we tried, tooltips worked great, so we reluctantly gave up. About a week later, the same problem showed up again. This time the crash occurred on a different machine where SoftICE was available. With a little detective work, I determined that the problem is caused by a conflict between comctl32.dll and the Visual C++ debugger. Specifically, when you run your application under the control of the Visual C++ debugger, function addresses obtained via GetProcAddress() are redirected through a thunk created on the fly. For example, if you call GetProcAddress() to retrieve the address of LineTo() in gdi32.dll, the address returned is actually a 16-byte code fragment generated by GetProcAddress(). When you use this address to call what is supposed to be LineTo(), you’re really executing code that looks like this:

push  gdi32!LineTo          ; push address of LineTo()
jmp   kernel32!DebugThunk   ; jump to thunk code in kernel32

DebugThunk() updates context information for the debugger located at offset 0x20 in the TIB (Thread Information Block) and then transfers control to the real function (i.e., LineTo() in this case). These miniature code fragments are allocated when you call GetProcAddress() and freed when your application terminates. Under certain conditions, this can cause trouble for comctl32.dll.

The tooltip control implemented in comctl32.dll uses AnimateWindow() exported from user32.dll (if available). AnimateWindow() was introduced starting with Windows 98, so it’s not available on older versions. For this reason, comctl32.dll calls GetProcAddress() to check for the availability of AnimateWindow() the very first time AnimateWindow() is needed (e.g., the first time a tooltip is displayed). The value returned from GetProcAddress() is cached in a global variable so that future calls to AnimateWindow() will not require additional calls to GetProcAddress().

The problem is that the global variable used to store the function address is shared by all processes. If the very first request to display a tooltip comes from an application that happens to be running under control of the debugger, comctl32.dll calls GetProcAddress() to retrieve the address of AnimateWindow(), but actually ends up with a pointer to the temporary thunk described earlier. As a result, tooltips will work fine for all applications system-wide until the application running under control of the debugger terminates. Once the debuggee application terminates, and the temporary 16-byte thunk is freed, any application that attempts to display a tooltip will likely cause an access violation. The access violation occurs when comctl32.dll attempts to call AnimateWindow() with the pointer that is no longer valid.

One non-programming solution for this problem is to make sure that you force at least one tooltip to appear between the time that you start Windows and you debug your application. For example, when you first boot the machine, allow the mouse to hover over the Start button until the “Click here to begin” tooltip appears. Fortunately, the problem doesn’t occur on Windows NT or Windows 2000 because GetProcAddress() always returns the real address of a function regardless of whether the application is being debugged. And it shows up on Windows 98 only when running under a debugger, so end users of your application are not likely to encounter it. If you ever run into this problem with Windows 98 tooltips, I hope that this information will save you from wasting time trying to find a non-existent problem in your code.

Trouble with COM Resource IDs on Win98
Mark Erdrich
[email protected]

If your ATL COM server runs fine under Windows 2000, but gives an error indicating the .exe is corrupted under Windows 98, check your resource ID values. When creating an ATL COM server, you typically include in the resource file a reference to the .rgs file that contains registry information for a COM interface. For example:

IDR_SomeInterface  REGISTRY  DISCARDABLE "SomeInterface.rgs"

The resource ID is stored in resource.h:

#define IDR_SomeInterface     22000

The resource ID is used when calling UpdateRegistryFromResource() in your object’s initialization code:

//...
_Module.UpdateRegistryFromResource(IDR_SomeInterface, FALSE);
//...

This works great under Windows 2000. However, when running under Windows 98, our application would fail at startup with an error message indicating the .exe was corrupted (ERROR_FILE_CORRUPT). Even if I built the application under Windows 98, we would still get the same message. I finally traced the problem to the resource ID used for the registry entries. If the ID was greater than 32,767, then we would get the error message indicating a corrupted .exe. However, if the ID was less than 32,768, the application would run fine. This was an extremely difficult problem to track down, and I hope this tip will help others from falling into the same hole.

Using the Running Object Table
Dan Shappir
[email protected]

While COM is still relevant, I would like to point out a very useful, yet often overlooked COM feature: the ROT (Running Object Table). The ROT is a system-wide object that acts as a directory of active COM object instances. When a COM object is created, it is not automatically placed in the ROT; instead, it must be registered explicitly. However, once registered, such objects are very easy to locate. This is a very useful feature for several reasons. First, it makes it possible for a COM client to determine if a COM server is currently running. The client can then decide whether or not to instantiate the server. Second, it allows a COM client to locate an active server instance and connect to it instead of creating a new instance.

One reason that the ROT is not as commonly used as it could be may have to do with the fact that it uses monikers as keys. Monikers are a topic most COM programmers tend to shy away from, even the more experienced ones. It helps to understand that, despite the complexity of their implementation, monikers are simply COM objects that point to other objects or data. There are several types of monikers, such as file monikers, class monikers, and item monikers, but they can all be accessed through the IMoniker interface. Because of this, using monikers as a key means that the ROT can be indexed not only by values but also by value types.

If you just want to index an object using a simple string key, you can use the item moniker. An item moniker was originally intended to identify objects within containers. For that purpose, the item moniker contains a text string, and this string is used by a container to distinguish the contained item from the others. Luckily, there is nothing preventing the use of an item moniker by itself as a key value for the ROT. Following is a code snippet that shows how a COM object, implemented in C++ using ATL, can register itself in the ROT:

CComPtr<IRunningObjectTable> pRot;
HRESULT hResult = GetRunningObjectTable(0, &pRot);
if ( FAILED(hResult) )
     return hResult;
CComPtr<IMoniker> pMk;
hResult = CreateItemMoniker(L"!", sKey, &pMk);
if ( FAILED(hResult) )
     return hResult;
hResult = pRot->Register(0, GetUnknown(), pMk, &m_dwROT);
if ( FAILED(hResult) )
     return hResult;

sKey in this case is simply a pointer to a wide-character string (LPCOLESTR) that will be used as the key for this instance. Note IRunningObjectTable::Register() returns a DWORD value that identifies this registration. This value will later be used to remove this object from the ROT. Why the ROT needs such a value instead of using the moniker throughout is beyond me, and as I will show, results in some very undesirable behavior. Once the object has been registered, locating it is very simple. Just use the following code:

CComPtr<IRunningObjectTable> pRot;
HRESULT hResult = GetRunningObjectTable(0, &pRot);
if ( FAILED(hResult) )
     return hResult;
CComPtr<IMoniker> pMk;
hResult = CreateItemMoniker(L"!", sKey, &pMk);
if ( FAILED(hResult) )
     return hResult;
CComPtr<IUnknown> pUnk;
HResult = pRot->GetObject(pMk, &pUnk);
if ( hResult == S_OK ) {        // Object exists
     CComQIPtr<IMyInterface> pObj = pUnk;
     ...
}

If the sKey value used in this case matches the value used in the registration, the object will be located and retrieved. To remove an object from the ROT, use IRunningObjectTable::Revoke():

CComPtr<IRunningObjectTable> pRot;
HRESULT hResult = GetRunningObjectTable(0, &pRot);
if ( FAILED(hResult) )
     return hResult;
pRot->Revoke(m_dwROT);

There are some additional things to note about the ROT:

1. When you place an object in the ROT, you can use either strong or weak registration using the ROTFLAGS_REGISTRATIONKEEPSALIVE flag. When this flag is specified, the ROT will keep the object from being destroyed until it is revoked.

2. It is possible to identify a particular instance as the active instance for a particular class. This is done using RegisterActiveObject(). Internally this function uses the ROT, and indeed it returns a DWORD value that must be later used with RevokeActiveObject(). Such an active object can be easily located using the GetActiveObject() function. The reason this mechanism is so useful is that the VB function GetObject() is a wrapper for GetActiveObject(), so this functionality is also available for VB and for VBA and VBScript.

The ROT does have some limitations that make it less useful than it could have been:

1. You cannot register an object using a proxy; you must use a raw interface value in the same apartment the object was created in. Not only does this limitation violate COM’s abstraction of remote invocations, but it also limits the ROT’s usability. This means that in many cases an object must register and revoke itself, instead of allowing its clients to perform this operation.

2. You must revoke an object that you registered using the DWORD registration value. This is because the ROT is not cleaned until the system is reset (not even by a logoff). As a result, if you neglect to revoke an object, for example because of an application crash, the ROT will continue to hold the zombie reference. If you then try to register again using the same moniker value, Register() returns MK_S_MONIKERALREADYREGISTERED, and there is no way to determine which object will be returned by GetObject().

Because of this last limitation, you may want to use the GIT (Global Interface Table) for a process-wide (instead of a system-wide) object directory. Because the GIT is process specific, when the process goes down so does this table. As a result, you won’t have zombie references hanging around to haunt you. Note that using the GIT requires at least Windows NT 4.0 SP3 or Windows 95 with DCOM 1.2.

Chris Branch is a software engineer at FSCreations, Inc. in Cincinnati, Ohio. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.