Q's and A's - January 1995

From EDM2
Jump to: navigation, search

by Amir Kolsky

Question

For some reason, my application won't shut down. Shutdown halts until I manually close the app. What am I doing wrong?

Answer

When PM is in the process of shutting down, it posts all the active message queues a WM_QUIT message. Shutdown cannot complete until all the posted messages are processed.

When WinGetMessage retrieves a WM_QUIT message from the message queue, it returns with FALSE, signaling the application that it should terminate itself. A similar mechanism is used to tell the application to terminate when Close is selected from the task list, and when the user selects Close from the system menu (unless the default behavior is altered).

However, it is also true that many PM functions, such as WinSendMsg and WinMessageBox, require a message queue for their operation. Hence, a thread that wants to call these functions must initialize PM and create a message queue even if no windows are created by it. Since no windows are created, there is no need for a message dispatch loop. But, if there is no message dispatch loop, there is no way to process the shutdown WM_QUIT, and the shutdown will halt.

So, we need a way to tell PM not to post the WM_QUIT to message queues that are not serviced. The way to do it is using WinCancelShutdown. WinCancelShutdown(hmq, TRUE) will disable the WM_QUIT posting. WinCancelShutdown(hmq, FALSE) will enable it. Threads that will never process WM_QUIT should have the following when they initialize:

hab = WinInitialize(0);
hmq = WinCreateMsgQueue(hab, 0);
WinCancelShutdown(hmq, TRUE);

Threads that want to protect themselves against shutdown at a given point in time, can call the function with TRUE, and when they are willing to accept a shutdown, call the function with FALSE. This can be useful if the thread is doing, say, some transaction processing and must wait for the transaction to complete before allowing a shutdown.

Question

I have an object window that needs to wait on a semaphore, should I use DosWaitEventSem or WinWaitEventSem in my window procedure?

Answer

Assuming there is no way around using the semaphore in the window procedure, use the WinXxx version. Sent messages are processed in the context of the thread that created the window (as are posted messages). Theoretically, they should be processed without delay (that's the whole point in sending them). However, if you are in the middle of executing some code, there is no way for you to magically start executing a different piece of code (in the same thread). So if PM is to allow a recursive window procedure invocation, it needs to regain control. This is done when you call a Win function. At that point PM can decide whether it needs to invoke a window procedure or continue with the API call.

So, how do you hang PM (or make it look like it's hung)? Simple, you block on something--a Dos call (semaphore, sleep) or in a loop. This will not allow you to get back to PM to process a sent message. If someone sends the window a message and the thread is blocked, the sender is blocked too (note, however, that the sender is blocked in such a way that PM can process messages sent to it--remember, it is blocked on a WinSendMsg call, so PM knows exactly what to do when a message is sent to it). One of the important messages that is sent is the focus change message, bad things happen if a window that is supposed to lose focus does not process focus change messages...

This is why the WinWaitEventSem was introduced. This way (naively!), PM waits on the semaphore on behalf of the thread so the thread can do two things--it can process sent messages and it can wait on the semaphore. When the semaphore is posted, the thread is off on its merry way. Remember, posted messages, especially user input will not be processed even when you wait in WinWaitEventSem. Caveat Emptor!

Question

I'm trying to debug a WinXXX call and WinGetLastError is returning an error code that cannot be found in any header file. For example, I just got 0x8100A from WinCreateStdWindow. Where can I find these new error codes?

Answer

The error ID returned by WinGetLastError has two parts: the high word designates severity and the low word has the actual error code. The severity codes and helper macros can be found in OS2DEF.H; the PM error codes can be found in PMERR.H:

typedef ULONG ERRORID;  // errid
typedef ERRORID *PERRORID;

// Combine severity and error code to produce ERRORID
#define MAKEERRORID(sev, error) (ERRORID)(MAKEULONG((error), (sev)))

// Extract error number from an errorid
#define ERRORIDERROR(errid)            (LOUSHORT(errod))

// Extract severity from an errorid
#define ERRORIDSEV(errid)              (HIUSHORT(errid))

// Severity codes
#define SEVERITY_NOERROR                               0x0000
#define SEVERITY_WARNING                               0x0004
#define SEVERITY_ERROR                                 0x0008
#define SEVERITY_SEVERE                                0x000C
#define SEVERITY_UNRECOVERABLE                         0x0010

So, 0x8100A has:

Severity: (0x0008) SEVERITY_ERROR

Error code: (0x100A) PMERR_RESOURCE_NOT_FOUND

Question

I'm trying to use the WinGetErrorInfo API to obtain more detailed error information. I see that the API returns a pointer to a block of information, but I don't see how to use the information in that block (for example, to retrieve the error text). Can you show me some sample code which accesses the information?

Answer

The WinGetErrorInfo API allocates a single (private) block of memory and copies all error information into that block, so all offsets within the ERRINFO structure are relative to the beginning of the block. The following example retrieves the ERRINFO information, and then accesses the error text contained within the allocated memory:

PERRINFO  pErrInfoBlk;
PSZ       pszOffSet, pszErrMsg;

pErrInfoBlk = WinGetErrorInfo(hab);  // Retrieve the error information
if (pErrInfoBlk != NULL) (           // IF information is available

  // Get error info and write to log. The "offaoffszMsg" field contains
  // the offset to the error message. Since the offset is relative to
  // the beginning of the ERRINFO block, we perform pointer arithmetic
  // to point to the beginning of the message.
  pszOffSet = ((PSZ)pErrInfoBlk) + pErrInfoBlk->offaoffszMsg;
  pszErrMsg = ((PSZ)pErrInfoBlk) + *((PSHORT)pszOffSet;

  // Write the error ID and the error message to stderr.
  fprintf (stderr, "Error ID: %X\n", ERRORIDERROR(pErrInfoBlk->idError));
  fprintf (stderr, "Error Msg: %x\n", pszErrMsg);

  WinFreeErrorInfo (pErrInfoBlk);    // Free allocated memory
}                                    // ENDIF information is available

Notice the WinFreeErrorInfo call! The memory allocated by the WinGetErrorInfo API is not freed until the returned pointer is passed to the WinFreeErrorInfo API.

Several of the samples in the Developer's Toolkits include code to access the ERRINFO information.

Question

How do you create a conditional cascade menu item? The resource compiler doesn't seem to know how...

Answer

The important thing to understand about the conditional cascades is that being a conditional cascade menu is a property of the submenu, rather than the menu item that opens it. This is evident when you consider the fact that the conditional cascade style is MS_CONDITIONALCASCADE and not MIS_CONDITIONALCASCADE. Until the resource compiler and WinCreateMenu are changed to allow setting the style on submenus, this will have to be done programmatically. Note that the default item to be selected is also a property of the submenu and must be defined before the conditional cascade menu can be used.

BOOL
SetConditionalCascade(HWND hwndFrame, USHORT idItem, USHORT idDefItem) {
  MENUITEM mi;

  // Get the submenu's window handle in mi.hwndSubMenu
if (WinSendDlgItemMsg (hwndFrame,
                        FID_MENU,
                        MM_QUERYITEM,
                        MPFROM2SHORT(idItem, TRUE),
                        PVOIDFROMMP(&mi))) {

    // Add conditional cascade capabilities to existing menu style
    WinSetWindowBits (mi.hwndSubMenu,
                      QWL_STYLE,
                      MS_CONDITIONALCASCADE,
                      MS_CONDITIONALCASCADE);

    // Set the default selection for the submenu - it must exist
    WinSendMsg(mi.hwndSubMenu,
               MM_SETDEFAULTITEMID,
               MPFROMSHORT(idDefItem),
               0L);
    return (TRUE);
  } else return (FALSE);
}
Question

I've noticed that certain applications dynamically modify their menu bars, either to add/delete menu items, or to modify the submenu items for one of the main items. How would I implement a feature like this in my own application; for example, how would I remove the "Move" and "Size" items from my application's system menu?

Answer

To delete an item that is available on a menu, you need to send the menu an MM_DELETEITEM message (if you wanted to add an item, you would use the MM_INSERTITEM message). These messages require the "menu ID" of the specific item to change, so you'll need to know how to retrieve the menu IDs for items on the application's system menu.

The first thing you need to do is find the window handle of the menu that you want to manipulate. In this case, since we will be manipulating the system menu, we can find that handle quite simply:

hwndSysMenu = WinWindowFromID
(WinQueryWindow(hwnd, QW_PARENT), FID_SYSMENU);

The next thing to do is get the information for the system menu...

WinSendMsg (hwndSysMenu, MM_QUERYITEM,
            MPFROM2SHORT (SC_SYSMENU, FALSE),
             (MPARAM)&mi);

...and then find out how many items are on the menu:

NumberOfItems = (INT) WinSendMsg (mi.hwndSubMenu,
                                  MM_QUERYITEMCOUNT,
                                  NULL, NULL);

Now that you know the total number of items in the system menu, query each item in turn, and delete the Move and Size items:

item = NumberOfItems;
while (item-) {
  // Retrieve the menu ID for this item.
  MenuID = (USHORT) WinSendMsg (mi.hwndSubMenu,
                                MM_ITEMIDFROMPOSITION,
                                MPFROM2SHORT(item, TRUE),
                                NULL);

  switch (MenuID)      {
    // Only delete the Move and Size items.
    case SC_MOVE:
    case SC_SIZE:
      WinSendMsg (mi.hwndSubMenu,
                  MM_DELETEITEM,
                  MPFROM2SHORT(MenuID, TRUE),
                  0);
      break;

    default:
      break;
  }
}