Q I'm trying to handle OnMenuSelect in my MFC app. The declaration for this function is:
void OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu);
When my handler is invoked, none of the menu functions, like GetMenuItemCount, work properly. I added an ASSERT to check for a valid menu handle:
ASSERT(::IsMenu(hSysMenu));It fails, so the handle isn't valid. How do I get the menu handle?
A Congratulations; it's not every day you uncover a bug in MFC! In fact, while hSysMenu is declared as HMENU, it isn't a handle at all. What is it, then? Answer: a pointer to an MFC CMenu object. You can prove this with the following code:
void CMainFrame::OnMenuSelect(... HMENU hSysMenu)
{
CMenu* pMenu = DYNAMIC_DOWNCAST(CMenu, (CObject*)hSysMenu);
TRACE("OnMenuSelect, hSysMenu=%p is %s\n", hSysMenu,
::IsMenu(hSysMenu) ? "HMENU" : pMenu ? "CMenu" : "unknown");
return CFrameWnd::OnMenuSelect(nItemID, nFlags, hSysMenu);
}
[
Editor's Update - 12/6/2004:
This bug was fixed in MFC 7.1.]
To understand how this can be, you have to delve into the mysterious workings of MFC message maps and message handler functions. When you use ON_WM_MENUSELECT, the macro expands, like so:
// ON_WM_MENUSELECT()
{ WM_MENUSELECT, 0, 0, 0, AfxSig_vwwh,
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< void (AFX_MSG_CALL CWnd::*)
(UINT, UINT, HMENU) > (OnMenuSelect)) }

// from afxmsg_.h
enum AfxSig
{
AfxSig_v_uu_M, // void (UINT, UINT, CMenu*)
AfxSig_b_W_uu, // BOOL (CWnd*, UINT, UINT)
... // etc, over 140 total
};
The naming convention is AfxSig_return type_WPARAM types_LPARAM types. For example, take a look at the declaration for OnSetCursor here:
BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);Thus, the symbol for the corresponding signature code is AfxSig_b_W_uu, which is what ON_WM_SETCURSOR uses. Here, b means the return type is BOOL, W means that WPARAM should be converted to CWnd*, and uu means that LPARAM should be converted to two UINTs from the low- and high-order words. When MFC's internal message-processing function CWnd::OnWndMsg sees a message map entry with signature code AfxSig_b_W_uu, it converts the arguments like so:
AFX_MSGMAP_ENTRY* lpEntry = // find it
switch (lpEntry->nSig) {
case AfxSig_b_W_uu:
CWnd* pWnd = CWnd::FromHandle(reinterpret_cast<HWND>(wp));
UINT arg1 = LOWORD(lp);
UINT arg2 = HIWORD(lp);
// call handler w/converted args
*pRes = (*lpEntry->pfn)(pWnd, arg1, arg2);
•••
The point of the signature code is that it tells OnWndMsg how to convert WPARAM and LPARAM without knowing the handler function at compile time. I've glossed over some gnarly details (MFC uses a giant union to cast the function pointer to a function with the appropriate signature), but you get the basic idea. The mechanics aren't important. What's important is that MFC uses an enum to identify the signature of each handler function; the ON_WM_XXX macros create message map entries with the proper enum codes; and CWnd::OnWndMsg uses the codes to convert WPARAM and LPARAM values to MFC objects before calling your handler function. The result is that your handler functions can deal with MFC objects instead of raw handles.
How does all this relate to your OnMenuSelect problem? Apparently one day the friendly Redmondtonians embarked on a code cleanup because many AfxSig codes are equated to other codes. For example:
// in afxmsg_.h
enum AfxSig {
•••
AfxSig_bWww = AfxSig_b_W_uu,
};
In other words, AfxSig_bWww is another name for AfxSig_b_W_uu. It appears the Redmondtonians adopted the underbar convention in a later release, no doubt because something like uuu is ambiguous (could be either u_uu or uu_u). To maintain backward compatibility, they kept the old names—like AfxSig_bWww. This is where the OnMenuSelect bug crept in. If you look in afxmsg_.h, you'll see the following lines:
enum AfxSig
{
AfxSig_vwwh = AfxSig_v_uu_M,
•••
AfxSig_vwwh means two WORDs and a HANDLE returning void. AfxSig_v_uu_M means two UINTs and a CMenu returning void. WORDs and UINTs are interchangeable, but a HANDLE is definitely not equivalent to a CMenu. ON_WM_MENUSELECT creates a message map entry with AfxSig_vwwh (LPARAM is HMENU), but CWnd::OnWndMsg reads it as AfxSig_v_uu_M and converts LPARAM to CMenu*. Oops. Whoever equated AfxSig_vwwh with AfxSig_l_uu_M forgot to change the declaration for OnMenuSelect. The code does actually run, but hSysMenu has the wrong type.
Now that I've explained the bug, what can you do about it? The easiest thing is to ignore the declaration and cast hSysMenu to a CMenu* in your handler.
void CMainFrame::OnMenuSelect(... HMENU hSysMenu)
{
CMenu* pMenu = (CMenu*)hSysMenu;
UINT nItems = pMenu->GetMenuItemCount();
// etc..
}
Finally, I should point out that despite its name, hSysMenu is not the system menu, but the menu that was clicked.
Q Your column in the August 2002 issue of MSDN® Magazine has been very helpful in converting from C++ to C#. However, I need to call a function in a C/C++ DLL that has a void* as one of its parameters. The void* can be a pointer to a short, long, or character array depending on the value of another parameter, the parameter ID. How should I write the C# wrapper to handle this? Here's an example of my code in C:
// In DLL's header (.h) file: GetParam(short param_id, void* param); // In my program: unsigned short width; unsigned long exp_time; char cam_name[50]; GetParam(PARAM_WIDTH,(void*)&width); GetParam(PARAM_TIME, (void*)&exp_time); GetParam(PARAM_CAM_NAME, (void*)&cam_name);
The DLL figures out the correct type for param based on param_id. It's a third-party DLL; I didn't write it, so I have no control over it. I can't figure out how to declare GetParam in C#. Do you have any suggestions?
A Man, your DLL is sooooo retro. Parameter IDs, void*s... It's like programming the 8080 (for those of us who remember what it was). And to think here we have entered the age of C# and the Web-programmable TiVo, and we're still dealing with grody code like this. Never fear, C# can handle any interop conundrum a legacy programmer can conjure. The reason you're having trouble figuring out how to declare GetParam is because one declaration isn't enough. You need three.
Figure 2 shows a DLL I wrote to simulate your situation. MyLib.dll has a single function, GetParam, that mimics what you've described. The first argument is an enum code that's 1, 2, or 3, depending on which parameter you want to obtain: width, time, or name. The value is returned through the second argument, a void*. The actual type depends on which parameter you request: short for width, long for time, and string (char*) for name.
Figure 3 shows how to call MyLib from C# in a bug-proof, type-safe manner. The trick is to use three overloaded import declarations, each with a different signature for the different parameter types. Don't forget that in C#, just like in C++, you can overload your functions. That is, you can write several functions with the same name but different argument types. This applies to interop/import declarations as well as ordinary functions—er, I mean methods. The compiler's IQ is high enough to figure out which overloaded method it should call based on the arguments you called it with. Once you realize you can write three declarations, each one uses straightforward rules from Basic Interop Programming 101. To marshal a pointer-to-short or pointer-to-long, use a reference; to marshal an in/out string, use StringBuilder. Figure 3 shows the exact syntax.
With the new declarations in hand, all you have to do is call GetParam with the proper argument type. C# knows which method to call and how to marshal the arguments. Amazing, isn't it? Of course, you still have to be careful to use the correct param_id/argument type combination. To avoid even the possibility of screwups, you can go one step further, as shown by the code in Figure 3, where the three GetParam overloads are declared private so only the wrapper class itself can call them. Instead of GetParam, programmers call new public methods GetWidth, GetTime, and GetName. For example:
// get "width" parameter short width = MyLib.GetWidth();MyLib.GetWidth hides param_id, thereby eliminating the possibility of an accidental param_id/type mismatch.
public static short GetWIdth() {
short wid=0;
GetParam(PARAMID.WIDTH, ref wid);
return wid;
}
Any time you import retro DLLs into your C# application, it's always a win to spend a few minutes doing it right. If you wrap the DLL in type-safe declarations, you'll spare yourself the headache of chasing bugs later.
Q I'm trying to write a custom file open dialog that filters a certain file name from the dialog. I use the OFN_ENABLEINCLUDENOTIFY style and handle the CDN_INCLUDEITEM notification. Unfortunately, the dialog box always ignores my return value if the item has the SFGAO_FILESYSTEM and SFGAO_FILESYSANCESTOR attributes. In other words, if I'm dealing with normal files and directories, or folders that are the parent of a file system folder (such as My Computer), then by design the OFN_ENABLEINCLUDENOTIFY style doesn't apply. My question is, how can I work around this problem? How can I remove certain files from the listview box?
A Gosh, will there ever be an end to questions about the file open dialog? I guess since almost every application opens files, customizing file open is natural. And what's more natural than filtering the file names in the list? The most common way to filter files is by the file name extension—.txt, .doc, or .whatever—but what if you want to filter based on some other criterion, such as the last-modified date, owner, or whether the file name rhymes with "dimple"?
At first glance, Windows® seems to offer exactly what you want. If you create your dialog with OFN_ENABLEINCLUDENOTIFY, Windows sends your hook procedure a CDN_INCLUDEITEM notification for every item it adds to the open list. If you return FALSE, Windows excludes the item. The problem is, Windows doesn't notify you for ordinary files, only pseudo-objects like namespace extensions. When you read the documentation through a magnifying glass, the print is perfectly clear: "The dialog box always includes items that have both the SFGAO_FILESYSTEM and SFGAO_FILESYSANCESTOR attributes, regardless of the value returned by CDN_INCLUDEITEM." Apparently the Redmondtonians added CDN_INCLUDEITEM for their own purposes, which didn't include filtering ordinary file names. Sigh.
There's no need to reach for your hanky. What the Redmondtonians taketh away, you can always add on your lonesome. I modified last month's OpenDlg program to add a file filter that works for ordinary files (see C++ Q&A: Retrieving Hidden Path Names, Mouse Events in C#). Faithful readers will recall that last month I showed how to get the true path name of files listed in the open dialog's list view. I wrote a class, CFileDlgHelper, with a function GetItemPathName to get the true path name of any item in the open dialog's list view. This month I'll use CFileDlgHelper to filter the file names.
Figure 4 shows the code. CMyOpenDlg::FilterFiles is the function that does the work. It iterates the file names in the list, calling a virtual function IncludeFile for each one. If IncludeFile returns TRUE, FilterFiles does nothing. If IncludeFile returns FALSE, FilterFiles deletes the file name from the list view. Here's what it looks like in pseudocode:
void CMyOpenDlg::FilterFiles()
{
for (CString pathname = /* each listview file */) {
if (!IncludeFile(fn)) {
// delete it
}
}
}
For my test program, CMyOpenDlg::IncludeFile removes file names that begin with _ (underbar) or live in a folder whose name begins with _. FilterFiles and IncludeFile are straightforward. The only mystery is: who calls FilterFiles? You have to call FilterFiles any time the open dialog populates its listview with file names. This can happen when the user navigates to a new folder, or changes the file name or type (for example, by selecting "*.bmp" instead of "*.jpg", or typing "foo*" in the file name field). In the first case, Windows sends CDN_FOLDERCHANGE; in the second, CDN_SELCHANGE. So CMyDlg calls FilterFiles whenever it gets one of these notifications. Figure 4 shows the details.
If you're thinking CDN_INCLUDEITEM is kind of lame, be glad you're not programming in the Microsoft® .NET Framework. There, you can't customize the file open dialog at all because OpenFileDialog is sealed, which is a fancy way to say the Redmondtonians won't let you derive from it. As soon as I'm appointed Grand Programming Poobah, I promise one of the first action items on my legislative agenda will be to remove "sealed" from the C# vocabulary. The keyword will be shipped to the Museum of Restrictive Programming Techniques where it will be displayed in a glass case. Alas, my appointment may not be soon forthcoming, so until then, happy programming!
Send your questions and comments for Paul to cppqa@microsoft.com.