Ask Dr. GUI #49

November/December 1999

Dr. GUI's Back…

...and had a nice vacation this fall—the colors in Michigan were spectacular! (It's been over eleven years since Dr. GUI, a native Michigander, has seen them.)

Windows 2000: Even Closer

With any luck, by the time you read this Microsoft® Windows® 2000 will be ready -- or very near ready -- for you. You've heard this already, but the good doctor really believes it: If you've got a fast enough processor and enough RAM, it's a great upgrade. You're really gonna love it. (Do check to make sure the devices you need are supported, though.)

And you'll really need to test your apps to make sure they install and run on Windows 2000. There have been a lot of changes designed to make the system more robust, and some of those changes might "bite" you. The only way to find out is to test.

If you own Microsoft Visual Studio® version 6.0 or any of its Professional or Enterprise tools, there are two free offers you should take advantage of:

First, you can get a free copy of the Windows 2000 Developer's Readiness Kit, which includes training on Windows 2000 issues for Microsoft Visual Basic® and Microsoft Visual C++®, training on COM+, the new Windows 2000-compliant Visual Studio Installer, and the very important Windows 2000 Applications Specifications and Compatibility Guide. Information is at http://msdn.microsoft.com/vstudio/prodinfo/datasheet/winkit.asp. If you bought Visual Studio recently, you already have it.

Second, you can get the MSDN Library on CD or DVD free for a year. Check it out at http://msdn.microsoft.com/subscriptions/offer/ and use the money you save to get a DVD drive.

Remember that the most up-to-date and best-organized Windows 2000 Web page is the Windows 2000 Developer Center at http://msdn.microsoft.com/windows2000/.

The Windows 2000 Developer Center is also your place to get all the latest information on the new APIs in Windows 2000 that can make programming easier and your programs much more robust and scalable.

Making the New Web

If you're doing Web programming -- and who isn't nowadays (or so it seems) -- you'll want to keep track of what Microsoft's doing to make Web programming easier and more powerful. You'll be seeing more about this all over the place, including MSDN.

And Now, Your Questions…

You've Blown Your Stack

Dear Dr. GUI:

We've written an ISAPI module in C. We are now interested in protecting it against access violations and stack overflows, and we've tried using the __try / __except framework. We conducted a few tests, and while we managed to recover easily from access violations, it seems that stack overflow recovery doesn't work too well.

Here's a small example that illustrates the problem (tested under Visual C++ 6.0):

void stack_overflow()
{
   char speed_up_the_overflow[1000];
   stack_overflow();
}

int main()
{
   for(;;) {
      __try {
      stack_overflow();
      } __except(1) { /* EXCEPTION_EXECUTE_HANDLER */
         printf ("Executing exception handler\n");
      }
   }
   return 0;
}

In the first iteration of the loop, the exception handler code is executed properly and the "Executing exception handler" message is displayed. But in the second iteration, the program dies silently. Running the program through a debugger reveals that in the second iteration, the program is hit with an access violation, instead of another stack overflow.

If we change stack_overflow() to perform a simple access violation (e.g. memcpy(NULL, "foo", 3);) we get the expected behavior, an endless loop of 'Executing exception handler' messages.

We also tried playing with longjmp()s and even gotos to no avail.

What are we doing wrong? Is there any way to cleanly recover from stack overflows?

Regards.

Andi Gutmans and Zeev Suraski

Dr. GUI replies:

Infinite recursion! That's a way to make a process blow its stack -- or a sure-fire way to demonstrate a problem.

Let me and my emergency response team clarify what happens in cases like this:

When your thread causes an EXCEPTION_ACCESS_VIOLATION or an EXCEPTION_INT_DIVIDE_BY_ZERO, you probably have a bug in your code, but it is one that you can commonly ignore by just backing out of the current function, or returning some failure code. However, if your thread causes an EXCEPTION_STACK_OVERFLOW, then your thread has left its stack in a damaged state.

Let me explain. The thread's stack grows to meet the needs of the thread. This is implemented by placing a page at the end (or at the bottom) of the current stack that is set to PAGE_GUARD access. When your code causes the stack pointer to point to an address in this page, an exception occurs and the system does three things:

  1. Removes the PAGE_GUARD protection on the "guard page" so that your thread can read and write data to the memory.

  2. Allocates a new guard page one page below the last one.

  3. Reexecutes the instruction that raised the exception.

In this way, the system can grow your stack for your thread, and your thread is never the wiser.

Each thread in your process does have a maximum stack size, however, which is set at compile time by the linker switch /STACK:reserve[,commit] or the STACKSIZE statement in the .def file for the project. But in your case, you are developing code in an ISAPI DLL and therefore have no control over the maximum stack size set by IIS -- IIS sets the maximum stack size for all threads it runs. So, what happens when your stack has grown to its maximum size? Well, the system receives an exception just like normal, and this is how it handles it:

  1. It removes the PAGE_GUARD protection on the guard page, just like normal.

  2. It attempts to allocate a new guard page below the last one, but fails!

  3. It raises a Microsoft Win32® exception so your thread can handle it in the exception block.

Notice an important point here: Your stack now has no guard page. It is broken! So, the next time your application grows the stack all the way to the end (where there should be a guard page) it just writes right off the end of the stack, causing the access violation that you see in your sample application.

Windows exception handling is great, and it does perform some "magic" so that your code can remain robust. However, it will not automatically fix your stack for you. If you don't mind doing some down-and-dirty programming (including a little in-line assembly), you can fix it yourself. Note that because of the inline assembly, this is an x86-only solution.

All you really need to do is reintroduce a guard page to your stack. However, in the interest of being tidy, you might as well free up the abandoned pages of your stack as well. I have modified your sample to do just this (as well as added an iteration count to show that each subsequent pass through the stack_overflow() function digs just as deep as the last), and you will find the code following this text.

Now you know how to fix a broken stack in Windows. But like any good doctor, it is my duty to prescribe preventative medicine as an alternative to this digital bandage:

#include <windows.h>
#include <stdio.h>
 
void stack_overflow( DWORD dwCount )
{
    char speed_up_the_overflow[10240];
    printf("Iteration count: %d\n", dwCount) ;
    stack_overflow( dwCount+1 );
}
 
int main()
{
    for(;;) {
        __try {
            stack_overflow(0);
        } __except(1) { /* EXCEPTION_EXECUTE_HANDLER */
            LPBYTE lpPage;
            static SYSTEM_INFO si;
            static MEMORY_BASIC_INFORMATION mi;
            static DWORD dwOldProtect;
 
            // We do this to get the page size of the system
            GetSystemInfo(&si);
            // We want the address that the stack pointer is // pointing to _asm mov lpPage, esp;
            // We wanna learn the allocation base of the stack
            VirtualQuery(lpPage, &mi, sizeof(mi));
            // go to the page below the current page.
            lpPage = (LPBYTE) (mi.BaseAddress) - si.dwPageSize;
            // Free the portion of the stack that you just abandoned
            // If it looks like you are freeing the portion 
            //of the stack that you still need,
            // Remember that stacks grow down.
            if (!VirtualFree(mi.AllocationBase,
               (LPBYTE)lpPage - (LPBYTE) mi.AllocationBase, 
               MEM_DECOMMIT))
            {
                // if we get here, exit
            }
            // Reintroduce the guard page
            if (!VirtualProtect(lpPage, si.dwPageSize, 
               PAGE_GUARD | PAGE_READWRITE, &dwOldProtect))
            {
                // if we get here, exit
            }     
            printf ("Executing exception handler\n");
            Sleep(2000) ;
        }
    }
    return 0;
}

For more detailed information, please see Advanced Windows, 3rd Edition, Chapter 7 and Chapter 16, by Jeffrey Richter, available through Microsoft Press (http://mspress.microsoft.com/books/1292.htm).

More Co-Dependency Issues

Dear Dr. GUI:

Hi Doctor,

I am having a severe problem while I am trying to deploy my Visual Basic project. I have used the DB Library to connect with my database and am using some of its administrative functions. Everything is working fine but the project will only run on those computers that already had the SQL Server Client Utility or SQL Server. While creating the setup from the Visual Basic Package and Deployment Wizard, the dependency information was not found and when the application runs on the computers that don't have SQL Server on them, the application will give the Error No. 429 "Active X object can't be created."

Hoping for a swift reply from your desk.

Armughan Rafat (MCSD)

Dr. GUI replies:

Dr. GUI has never claimed to be a psychiatrist, but you must trust that he does know about dependency and co-dependency files. Here's the situation:

The vbsql.ocx Microsoft ActiveX® control, which communicates with SQL Server, is not shipped with a dependency file (.dep) that the Microsoft Visual Basic® Package and Deployment Wizard uses to determine which files the control needs. Luckily, it's possible for you to add your own dependencies to overcome this shortcoming. Because vbsql.ocx encapsulates the functionality of DB-Library into a control, all you need to do is add the DB-Library files as dependent files to the Package and Deployment Wizard.

On the Package and Deployment Wizard, "Included Files" step, you need to add the following files to your package. These should be located with the SQL Server Programmers Toolkit (PTK) or in the \mssql\binn directory:

NTWDBLIB.DLL' DB-Library component, should be included by default with

                              VBSQL.OCX

DBMSSOCN.DLL' for TCP/IP Socket Connections

DBNMPNTW.DLL' for Named Pipe Connections

DBMSSPXN.DLL' for IPX/SPX Connections

DBMSADSN.DLL' for Apple Talk Connections

DBMSDECN.DLL' for Dec-Net Connections

DBMSRPCN.DLL' for Multi-protocol Connections

DBMSVINN.DLL' for Banyan Vines Connections

If You're MAPI, I'm MAPI

Dear Dr. GUI:

I'm attempting to write an e-mail program that pulls e-mail from at (the very) least three different mail servers using MAPI. How do I change the SMTP and POP settings for MAPI through Visual Basic to do this? This is very important, so could you please respond as quickly as possible?

Thank you for your help!

Clifford Oravec

Dr. GUI replies:

Dear Clifford,

Now that we've solved the co-dependency problems, it's time to put on a MAPI face, right?

Dr. GUI is assuming that because you are using Visual Basic you are using the Collaboration Data Objects (CDO) library. To accomplish this, you simply need to log on to the separate servers/mailboxes, rather than changing the SMTP and POP settings of the mailboxes themselves. The doctor thinks this sounds like a lot more work. Surely there are some "Simpsons" reruns that are crying out to be watched! But here's how to do it:

Your code could create three separate e-mail profiles, one for each server/mailbox connection. Your code can accomplish the same feat by specifying the particular profile information with the ProfileInfo parameter of the Session object Logon method as follows:

objSession.Logon ProfileInfo:="<server A>" & vbLf & "<mailbox A>"

If your application does three separate and distinct operations in sequence, you can log on to a single MAPI Session object several times. Of course, an existing log on would have to be logged off before the next log on could occur, and each log on would use different ProfileInfo data.

Try this on for size:

Dim objSession as Mapi.Session
Set objSession = CreateObject("MAPI.Session")
'Logon to first server/mailbox
   objSession.Logon ProfileInfo:="<server A>" & vbLf & "<mailbox A>"
   '... do first task
   objSession.Logoff

'Logon to other server/mailbox
   objSession.Logon ProfileInfo:="<server B>" & vbLf & "<mailbox B>"
   '... do next task
   objSession.Logoff

'repeat as necessary
'release objects
   Set objSession = Nothing

I would advise you to have a single Session object global to your whole application. However, if you absolutely must have three separate Session objects active at the same time, log on to each with unique ProfileInfo settings.

Don't forget that, for these techniques to work, the user running the code has to have permissions to log on to the mailboxes; your e-mail administrator can provide more information.

Check out these Microsoft Knowledge Base articles for lots of cool info on CDO:

Good luck with your MAPI development!

Mutex Madness

Dear Dr. GUI:

I'm a French engineer and my English isn't perfect so I hope that you understand my question.

I have a DLL that creates a mutex. I have some problems synchronizing all processes that use my DLL. I create the mutex with code that looks like the following:

ghMutexExe = CreateMutex(NULL, FALSE , "APPLICOM_IO_MUTEX");

When I use my DLL with an application that is running in real-time priority, I can't run another application that uses the same DLL. When I use the function:

ghMutexExe = CreateMutex(NULL, FALSE , "APPLICOM_IO_MUTEX");

it returns NULL (ghMutexExe = NULL), and GetLastError returns 5 (Access is denied. ERROR_ACCESS_DENIED).

Can you help me?

Bertrand Lauret

Dr. GUI replies:

Your English is just fine. (Dr, GUI is just glad you didn't ask to get my reply in French.) Most people do not know this, but the good doctor is bilingual. He speaks English and C++.

It is certainly possible to have problems with thread synchronization due to differing priorities of threads. However, it is very unlikely that you would receive an ERROR_ACCESS_DENIED when trying to obtain a handle to an existing mutex because of the priority class of the creating process.

Typically, ERROR_ACCESS_DENIED is returned as a result of failure because of the security implications of the function being called. Let me describe a scenario where this could happen with Microsoft Windows NT® or Windows 2000:

Process A is running as a service (perhaps in real time, perhaps not) and creates a named mutex passing NULL as the first parameter indicating default security for the object.

Process B is launched by the interactive user and attempts to obtain a handle to the named mutex through a similar call to CreateMutex. This call fails with ERROR_ACCESS_DENIED because the default security of the service excludes all but the local system for ALL_ACCESS security to the object. This process does not have access even if it is running as an administrator of the system.

This type of failure is the result of a combination of points:

Most services are installed in the local system account and run with special security rights as a result.

Processes running in the local system account grant GENERIC_ALL access to other processes running in the local system, and READ_CONTROL, GENERIC_EXECUTE, and GENERIC_READ access to members of the Administrators group. All other access to the object by any other users or groups is denied.

All calls to CreateMutex implicitly request MUTEX_ALL_ACCESS for the object in question. An interactive user does not have the rights required to obtain a handle to an object created from the local system security context as a result.

There are several solutions to this problem:

You can set the security descriptor in your call to CreateMutex to contain a "NULL DACL." An object with NULL-DACL security grants all access to everyone, regardless of security context. One downside of this approach is that the object is now completely unsecured. In fact, a malicious application could obtain a handle to a named object created with a NULL DACL and change its security access such that other processes are unable to use the object, effectively ruining the object. PLEASE NOTE: The doctor seriously advises against this "solution."

A second option would be to create a security descriptor that explicitly grants the necessary rights to the built-in group: Everyone. In the case of a mutex, this would be MUTEX_ALL_ACCESS. This is preferable because it will not allow malicious (or buggy) software to affect other software's access to the object.

You can also choose to explicitly allow access to the user accounts that will need to use the object. This type of explicit security can be good but comes with the disadvantage of needing to know who will need access at the time the object is created.

My preference in cases of creating objects for general availability to users of the system is the second. Regardless of which approach you choose, you have the additional choice of whether to apply the security descriptor to each object as it is created, or to change the default security for your process by changing the token's default DACL. In this case, you would continue to pass NULL for the security parameter when creating an object.

Dr. GUI thinks that in your case you should only set the security for specific objects because you are developing a DLL, which may not want to change the security for every object in the process.

The following function wraps CreateMutex with the additional functionality of creating security that is more relaxed than is the default for a service:

HANDLE ObtainAccessableMutex( BOOL bInitialOwner, LPTSTR szName )
{
    SID_IDENTIFIER_AUTHORITY siaWorld = SECURITY_WORLD_SID_AUTHORITY;
    PSID psidEveryone = NULL; 
    HANDLE hMutex = NULL  ;
    int nSidSize ; 
    int nAclSize ;
    PACL paclNewDacl = NULL; 
    SECURITY_DESCRIPTOR sd ;
    SECURITY_ATTRIBUTES sa ; 
    
    __try{
        // Create the everyone sid
        if (!AllocateAndInitializeSid(&siaWorld, 1, SECURITY_WORLD_RID, 0,
                                           0, 0, 0, 0, 0, 0, &psidEveryone))
        {            
            psidEveryone = NULL ; 
            __leave;
        }
 
        nSidSize = GetLengthSid(psidEveryone) ;
        nAclSize = nSidSize * 2 + sizeof(ACCESS_ALLOWED_ACE) + sizeof(ACCESS_DENIED_ACE) + sizeof(ACL) ;
        paclNewDacl = (PACL) LocalAlloc( LPTR, nAclSize ) ;
        if( !paclNewDacl )
            __leave ; 
        if(!InitializeAcl( paclNewDacl, nAclSize, ACL_REVISION ))
            __leave ; 
        if(!AddAccessDeniedAce( paclNewDacl, ACL_REVISION, WRITE_DAC | WRITE_OWNER, psidEveryone ))
            __leave ; 
        // I am using GENERIC_ALL here so that this very code can be applied to 
        // other objects.  Specific access should be applied when possible.
        if(!AddAccessAllowedAce( paclNewDacl, ACL_REVISION, GENERIC_ALL, psidEveryone ))
            __leave ; 
        if(!InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION ))
            __leave ; 
        if(!SetSecurityDescriptorDacl( &sd, TRUE, paclNewDacl, FALSE ))
            __leave ; 
        sa.nLength = sizeof( sa ) ;
        sa.bInheritHandle = FALSE ; 
        sa.lpSecurityDescriptor = &sd ;
 
        hMutex = CreateMutex( &sa, bInitialOwner, szName ) ;
        if( !hMutex )
            hMutex = OpenMutex( SYNCHRONIZE, FALSE, szName ) ;
    }__finally{
        if( !paclNewDacl )
            LocalFree( paclNewDacl ) ;
        if( !psidEveryone )
            FreeSid( psidEveryone ) ;

    }
 
    return hMutex ; 
}

See the following Knowledge Base articles for further discussion of these topics:

Add Two Teaspoons of Vanilla and Just a Pinch of Raid…

Dear Dr. GUI:

Our app makes use of the Internet Explorer WebBrowser control and MFC's CHtmlView to add custom status information and user-defined views. The app is a process monitoring program that runs scripts that we call recipes. If one of these recipes continually chains to other recipes then we cannot run for more than a few hours before the system becomes unstable because of some kind of resource problem.

I have written a small MFC AppWizard app (attached) that uses the CHtmlView and, after a short time, closes the currently active document and creates a new one. If I run this app overnight then the system becomes pretty unstable and this and other apps start doing weird things!

I have run the app and used the Windows NT performance monitor to look at what's leaking and it seems that the major problem is that the Pool Paged Bytes are getting eaten at quite a rate. Am I doing something wrong or is this a bug with Internet Explorer? If so, how do I report it and hopefully get it fixed?

My system is running NT4 SP5 with IE5 and has 128MB RAM. We also see the problem on Windows 98 but haven't tested with Windows 95.

Thanks in advance.

Matt Stephens

Dr. GUI replies:

Thanks, but I'll pass on that cake for now. I don't think that I can take it, 'cause it took so long to bake it. But soon we'll get your recipes running again. (But it won't be as nice a solution as the good doctor would like.)

Actually, according to our omniscient support engineers here in MacArthur Park, the problem you describe is indeed a known bug in Internet Explorer. We're working on fixing it in a future version of Microsoft Internet Explorer or an Internet Explorer service pack. For the time being, there is not much of a workaround. The best thing to do is to shut down your application periodically to free those resources. Dr. GUI is sorry he doesn't have better news.

Take a look at this Knowledge Base article for some more information:

Open the Screen and Get That Bug Outta Here

Dear Dr. GUI:

I am designing a Web page to be browsed using Internet Explorer 5.0. This page needs to be:

However, there seems to be no way to get rid of the vertical scrollbar in IE when a window is spawned full screen. Any help would be greatly appreciated. Thanks!!

Dan Chiao

Dr. GUI replies:

Dear Dan,

A personally autographed Dr. GUI fly swatter is winging its way to you even as we speak. (Notice the subtle pun!) Actually, you'll be getting a t-shirt like everyone else, but you get the idea.

You have found a bug that exists in Internet Explorer 4.x and Internet Explorer 5. The scrollbars attribute has a reverse effect when opening a window in the full screen mode (that is, scrollbars=yes does not display the scrollbar and scrollbars=no does display scrollbars). Another aspect of this bug is that the scrollbar *will* appear if the page content does not fit in the available screen area, irrespective of what you specify for the scrollbar property. This will not display a scrollbar even if the content does not fit in the available screen area.

As a workaround, you can specify the scroll attribute in the body tag of the child page, and this takes precedence over the window.open's scrollbar attribute. This attribute does work correctly if you are opening the child window in the normal mode.

Here's some sample script to demonstrate the workaround:

main.html
<HTML>
<BODY><input type=button value="Open full screen without scrollbars" onclick="window.open('child.html', ", 'fullscreen=yes')"></BODY>
</HTML>
child.html
<HTML>
<BODY scroll=no>This is the child page</BODY>
</HTML>

Getting Types Cross-Matched Right

Dear Dr. GUI:

I need to create MAPI resources at run time and the documentation says it's better to use the right type of variable.

Why is this not working?

   Dim tempo(32)
   Dim map(32) as object
   Dim maptyped(32) as msmapi.mapisession
 
   Set tempo(1) = CreateObject("MSMAPI.MAPISession") ' ok 
   Set map(1) = CreateObject("MSMAPI.MAPISession") ' ok
   Set maptyped(1) = CreateObject("MSMAPI.MAPISession") ' failed

Thanks for your help.

Lionel Pichon

Dr. GUI replies:

When you give a blood transfusion, you have to get the types matched correctly or you'll injure (or even kill) the patient. While people don't often die as a result of parameter type mismatches, processes often do as they GPF. So, it is important to match your types properly for parameters, too.

Although the first two CreateObject() lines just shown do execute, they are not creating the objects you want. So, it is better to use exact types—only when you did was the error caught.

You can verify this by trying to retrieve a property of a Session object, such as Version; it will fail. The correct object class is MAPI.Session, as shown here:

   Dim map(32) as object         'example of late binding
   Dim maps(32) as MAPI.Session  'example of early binding
   Dim tempo(32)                 'late binding
 
   Set tempo(1) = CreateObject("MAPI.Session")   ' ok 
   Set map(1) = CreateObject("MAPI.Session")   ' ok
   Set maps(1) = CreateObject("MAPI.Session")   'works too

All of these methods work. When declaring objects, early binding is preferred because it prevents type mismatches that can be fatal, at least to your program.

Your question did not indicate why you are creating an array of Session objects; it is generally advisable to use a single session object global to your entire application.

The good doctor hopes this helps with your MAPI development!

Order, Order in the Strings

Dear Dr. GUI:

I have a sorting problem on CListBox in Visual C++ 6.0. Here are the strings that I put in the listbox using the AddString member function:

"a",  "b", "-a", "-b"

The list box displays the strings in this order:

"a", "-a", "b", "-b"

The "Sort" property on that list box is checked. Now, if I use CListBox from Visual C++ 1.52c, the same strings are displayed in this order:

"-a", "-b", "a", "b"

What is the order for the strings above to be displayed at CListBox?

Regards.

XiaoPing Sun

Dr. GUI replies:

Actually, the Win16 and Win32 sorting algorithms are almost identical. In general, the order is this:

  1. Non-alpha-numeric (punctuation) characters in ASCII or ANSI order

  2. Numeric characters in numeric order

  3. Alphabetic characters in case-insensitive alphabetic order

For Win32 it is the same, with two exceptions: the hyphen or minus (-) symbol and the single-quote or apostrophe ( ' ). These two characters are ignored when sorting strings because they are allowed to be embedded in English-language words. For example, "its" and "it's" and "co-op" and "coop." The presence of these characters embedded in words causes certain searches to break incorrectly, so they are changed to be treated just like other diacritical marks embedded in text strings; that is, they're ignored.

Thanks…

Dr. GUI once again wants to spread the word about the wonderful folks on his team of technical surgeons. This time, kudos go to Jeff Baughman (two kudos), Jason Clark (also two kudos), Steve Dybing, Jason Strayer, Richard Van Fossan, and Kusuma Vellanki. And multiple kudos to Tom Moran and Penny Riker for stitching the column all together.

Somehow we missed acknowledging one of the team members in the last edition, so our apologies and our thanks to Vidyanand Rajpathak.