Ask Dr. GUI #43

November/December 1998

What's Up with the Doc?

Not much, as it turns out. Summer's just over as the good doctor's writing this, so that's sad. About the only thing Dr. GUI really hates about living in Seattle is that it gets dark really early in the wintertime—and it's made even worse by standard time. (Changing the time back to standard time is clearly the work of morning people. Dr. GUI believes firmly in the moral necessity of being up at the crack of noon.)

But all that darkness gives the good doctor time to play with some new toys—the latest of which is a nifty little new Windows CE device. It's one of the new Handheld PC Pro edition devices, so it has a big keyboard and screen—but it's still very light and small. Dr. GUI thinks it'll make a good machine for e-mail and for writing this column. And the new tools are due out for these machines (and Visual Studio version 6.0) soon, so perhaps it's time for the good doctor to start writing some Windows CE applications again.

Speaking of time, be sure to check out the information about the "year 2038" problem faced by C and C++ programmers in the Dr. GUI Online columns (see http://msdn.microsoft.com/developer/news/drgui/093098.htm).

So without further ado, your questions:

No MessageBox from J/Direct? Dear Dr. GUI,

I tested the sample Java code you wrote that calls a Win32 DLL to generate a message box. When I try to compile the code, I receive a J0049 error. Why isn't the code compiling?

Henry

Dr. GUI replies:

Dr. GUI had to check his eyes to see what was going on. Once he did he found that MessageBox is there all right—but you've got to know how to ask for it. You have a number of choices on how to use the MessageBox function from Java via J/Direct.

The original code looked like this:

class ShowMsgBox {
   public static void main(String args[])
   { 
MessageBox(0, "It worked!",
   "This message box from Java", 0);
   } 

   /** @dll.import("USER32") */
   private static native int
   MessageBox(int hwndOwner, String
      text, String title, int fuStyle);
}

This code works: When you run the application, a message box appears. Yet it's not totally useful. Because MessageBox is declared private, it can't be used from any other class. Doh!

This code, when fixed by replacing private with public, illustrates the first method of using J/Direct: You can declare each function yourself by preceding it with the special @dll.import comment. With the fix, you can call MessageBox from any class by preceding it with the class name ShowMsgBox, as in:

   ShowMessageBox.MessageBox(0, "It worked!", "From Java", 0);

But, rather than declaring each function you want to call, there's an easier way: Just import the com.ms.win32 package, which includes all the declarations you need to use Win32 functions, constants, and structures from J/Direct.

Specifically, in order to call the MessageBox function, you import the package and precede the function call with the class name User32, as follows:

import com.ms.win32.*; 
class ShowMsgBox
{
   public static void main(String args[])
   {
      User32.MessageBox(0, "It worked!", 
         "This message box from Java", 0);
   }
}

You can't get away with leaving the class name off unless you've declared the method in your immediate class. By the way, Dr. GUI has noticed that on some machines, the com.ms.win32 package isn't installed. If this is the case on your machine, install the latest SDK for Java from http://microsoft.com/java/.

What Version, Again? Dear Dr. GUI,

I have on my machine the following DLLs and TLB. Are these the latest versions? If not, where, please, do I find the updated copies? I am reasonably certain that there is a later version of the first DLL, which is why I am asking the question.

The files are as follows:

MSVBM50.DLL 1,347,344 07-19-97 4:55p
STDOLE2.TLB 16,896 11-18-97 12:00a
OLEAUT32.DLL 490,256 11-18-97 12:00a
OLEPRO32.DLL 32,528 11-06-96 1:27a
COMCAT.DLL 6,144 11-18-97 12:00a

Thanks in advance—and I look forward to hearing from you shortly.

Regards,

Peter Fox

Dr. GUI replies:

Dr. GUI hates version problems … but he wants to commend you for asking and bringing up this issue.

What versions you should have depends on what applications/development products you have installed on your PC, but you are correct that there are newer versions of these files. If you're using Visual Basic 5.0, check out the following Knowledge Base article, which contains information about the latest core OLE files and Visual Basic 5.0 run-time files: http://support.microsoft.com/support/kb/articles/q180/0/71.asp.

This article explains the usage of Msvbvm50.exe, a self-extracting file that will install the following file versions.

File Version
Msvbvm50.dll 05.00.4319
Oleaut32.dll 2.20.4118
Olepro32.dll 5.0.4118
Stdole2.tlb 2.20.4118
Asycfilt.dll 2.20.4118
Comcat.dll 4.71

(Note that we're using the more exact version numbers rather than the file sizes and dates you're using.)

Another self-extracting file, VBRun60.exe, is located at the following address: http://support.microsoft.com/support/kb/articles/q192/4/61.asp.

The article describes the usage of VBRun60.exe, but notice that it installs the run-time library for Visual Basic 6.0, not 5.0.

File Version
Msvbvm60.dll 06.00.8176
Oleaut32.dll 2.30.4261
Olepro32.dll 5.0.4261
Stdole2.dll 2.30.4261
Asycfilt.dll 2.30.4261
Comcat.dll 4.71

One last point is your version of comcat.dll. The 5.0 version shipped with Microsoft Internet Explorer and is fine for use with Visual Basic. But this could get sticky for deployment, because neither Visual Basic 5.0 Application Setup Wizard nor Visual Basic 6.0 Package and Deployment Wizard can correctly register the 5.0 version of Comcat.dll. Users who've installed Internet Explorer will be fine.

Help! What Happened to Media Viewer? Dear Dr. GUI,

In my early medical school days (16-bit days), I remember coming across an SDK-like ointment for the creation of "Media Viewer" applications. The product provided practicing physicians with two development avenues: C++ and Visual Basic. The VB version had either VBX or OCX controls (I don't remember—it was so long ago). For some unknown psychoanalytical reasons my memory has suppressed the whereabouts of that utility.

Recently in the course of treating a new paranoid-schizophrenic patient, I realized that an "InfoViewer" would be the only cure to his problem. Where can I find that utility—or better yet, is there anything out there that will allow me to develop a "Tech Net"-like application using VB4 or VB5-6 as the development language (mind you, the string search capability is very important)?

Thanks for the help,

Apelbaum

Dr. GUI replies:

Dr. GUI remembers those bygone days well—programming for Win16, and curing patients with leeches. But he wouldn't prescribe using MediaView any longer—even MSDN no longer uses it. Instead, you can use HTML Help, which is the new InfoViewer. Start up Internet Explorer 4.01 and from the Help menu click Contents and Index. This is an example of a finished HTML Help system. (For that matter, all of Visual Studio 6.0 Help is in HTML Help, as well as in the MSDN Library October release.)

HTML Help has a lot of advantages over MediaView/InfoViewer: Because it uses HTML as its primary authoring language, there are many tools you can use to write content. And the content can be much richer: DHTML is much more powerful than the MediaView format was, even if you don't count scripting, ActiveX controls, and Java applets!

To create your own HTML Help system, use the HTML Help Workshop. It is available for free at msdn.microsoft.com/workshop/author/htmlhelp/default.asp. Using the workshop, you can create an HTML file for each topic, add an index (with full text search capability) and a table of contents, and compile it all into one compiled Help module or .chm file.

To hook up your .chm file to your Visual Basic program, use the HTML Help API. (The HTML Help API has many commands and is explained in detail in the HTML Help Workshop online Help.)

Here is an example of how to display an HTML Help file called "myfile.chm" from a Visual Basic program in three easy steps:

  1. Declare the constant.
    Const HH_DISPLAY_TOPIC = &H0
    
  2. Declare the HTML Help API function.
     Declare Function HtmlHelp Lib "hhctrl.ocx" Alias "HtmlHelpA" _
          (ByVal hwndCaller As Long, ByVal pszFile As String, _
          ByVal uCommand As Long, ByVal dwData As Long) As Long
    
  3. Display "myfile.chm" in response to a button click.
    Private Sub HH_DISPLAY_Click()
    'hWnd is a Long defined elsewhere to be the window handle that will be the parent to the 'help window
          Dim hwndHelp As Long
    'The return value is the window handle of the created help window
          hwndHelp = HtmlHelp(hWnd, "myfile.chm", HH_DISPLAY_TOPIC, 0)
     End Sub
    
How Am I Performing? Dear Dr. GUI,

Good Doctor,

Windows NT now has pdh.dll for easy reading of performance data. Is there such a thing for Windows 95/98?

Thanks,

Eric Renish

Dr. GUI replies:

Dr. GUI agrees with you. Wouldn't it be nice if there were? Unfortunately, there is no equivalent pdh.dll (Performance Data Helper Library) for Windows 95/98 as there is for Windows NT. The PDH Library was designed to make it easier for software developers to retrieve performance data on Windows NT. An application can retrieve performance data on Windows NT using RegQueryValueEx with the HKEY_PERFORMANCE_DATA built-in registry key. However, formatting the retrieved data to be readable information is very complex—thus the need for a helper library.

Windows 95/98 does provide a way to collect its performance data using RegQueryValueEx using HKEY_DYN_DATA. Retrieving the data and formatting it is relatively simple compared to the process for Windows NT. More information about retrieving Windows 95/98 performance data can be found in the following Microsoft Knowledge Base article, Q174631: "HOWTO: Access the Performance Registry Under Windows 95," located at http://support.microsoft.com/support/kb/articles/q174/6/31.asp.

Stringing Along Your Dates Dear Dr. GUI,

I need to create a BSTR inside an ATL object without using MFC. Unlike the COleDateTime class, DATE does not seem to have a way to format it into a string. Any help?

Steve Loethen

Dr. GUI replies:

For a second there, Dr. GUI thought you were asking him a question that should have gone to Dear Abby—or Dan Savage. But because it has to do with programming, this is a relatively easy problem: You can use VarBstrFromDate() to convert a DATE to a BSTR.

For example:

STDMETHODIMP CBstrDATETest::Test()
{
  DATE d = 2.00; //1 January 1900, midnight
  BSTR b;
  LCID lcid = ::GetUserDefaultLCID();
  VarBstrFromDate(d, lcid, 0, &b); //see oleauto.h for prototype
  ::MessageBoxW(NULL, b, NULL, MB_OK);
  return S_OK;
}

For a description of the parameters, search for "Data Type Conversion APIs" in the Visual C++ Help index. Note that the DATE data type is year 2000- and year 2038-compliant.

Rolling Your Own IDL Dear Dr. GUI,

In "Dr. GUI on components, COM, and ATL," you mention that we do not have to create our own header files and such, because we just use the ones created by the MIDL compiler when we compile our IDL files.

My question is, what if we didn't create the COM object but want to use it? For example, MS Winsock Control has the type library information built into it so I can use it in VB. It can also be inserted into my VC++ project (wrapped inside function call to invoke), but if I want to use vtable interface calls, I must have a header file to do so. How does one get this information on all of the free-to-use controls out there?

Thanks for your time,

Kable Wilmoth

Dr. GUI replies:

Dr. GUI has a couple of ideas for you. You can pick your own medicine.

If you are using Visual C++ 5.0 or later, Dr. GUI prescribes the native COM support built into the compiler. For a detailed explanation of how this feature works, search for "#import" in the MSDN Library Visual C++ documentation. There are also several samples that show how this new feature works. Search for "LABRADOR," "CONNECT sample (ATL)," and "MFCCALC sample (COM)" in the Visual C++ documentation for a look at some of the samples available. Basically, #import makes using COM objects as easy in C++ as in Visual Basic. Check out Dr. GUI's article about C++ smart pointers and #import at http://msdn.microsoft.com/developer/news/drgui/072798.htm.

Or, if you really want to have the interface definition language (IDL) and the headers, you can generate IDL from the type library that's built into the objects. To do this, you'll use the OLE Viewer (oleview.exe) to display the IDL for the type library, and then cut and paste it into a new IDL file. You may have to rearrange the file in order for MIDL to compile it. This is all described in an article about Java and type libraries at http://msdn.microsoft.com/developer/news/feature/vjjuly98/vjtypelib.htm; see the section titled "Starting with an existing object."

List Box, Draw Thyself Dear Dr. GUI,

Please forgive me if I am suffering from amnesia and have already bugged you about this.... I remember I was going to send you e-mail, I just don't remember if I actually sent it. So on with my question...

I need to have individual lines inside a CListBox to possibly be different colors. I know that I can change the color for all of the text but that really doesn't help me that much. I have searched and searched for something that would help me do this and all I can find is how easy it is to do in Visual Basic. Is there any solution in VC++ that will give me this functionality?

Thanks!

Dr. GUI replies:

The good doctor notes that this won't be trivial, but your problem gives him a great chance to show how to use "owner-draw" list boxes. "Owner-draw" is a common Windows technique whereby Windows asks you to draw the item that needs to be drawn. Many Windows controls, including buttons and menus, also support owner-drawing.

So, for this example, we'll use an owner-drawn list box. The entire process involves just four simple steps:

  1. Derive a class from CListBox.

  2. Override the CListBox::MeasureItem virtual function to specify the height for each item.

  3. Override the CListBox::DrawItem virtual function to perform the painting.

  4. Override the CListBox::CompareItem virtual function to determine the order in which the a string has to be added. This is necessary only if you wish to have a sorted list.

Note that the list box should have the LBS_OWNERDRAWVARIABLE and LBS_HASSTRINGS styles set. You need to select these styles for the list box when you create it using a resource editor. Otherwise, if you create the list box dynamically, specify these styles during creation.

The following CLineListBox class provides an implementation. Here the color for the text is stored as item data and retrieved during painting. First, we implement the AddItem function, which adds the string to the list box and stores the color in the 32-bit item data associated with the string:

void CLineListBox::AddItem(const CString& str, COLORREF rgbText)
{
   int   nIndex;
   nIndex = AddString(str);
   if( CB_ERR != nIndex )
      SetItemData(nIndex, rgbText);
}

Next, we override DrawItem to draw the string in the color stored in the item data:

void CLineListBox::DrawItem(LPDRAWITEMSTRUCT lpDIS) 
{
   CDC      dc;
   CRect      rcItem(lpDIS->rcItem);
   UINT      nIndex = lpDIS->itemID;
   COLORREF   rgbBkgnd = ::GetSysColor(
         (lpDIS->itemState & ODS_SELECTED) ?
               COLOR_HIGHLIGHT : COLOR_WINDOW);
   dc.Attach(lpDIS->hDC);
   CBrush br(rgbBkgnd);
   dc.FillRect(rcItem, &br);
   if( lpDIS->itemState & ODS_FOCUS )
      dc.DrawFocusRect(rcItem);
   if( nIndex != (UINT)-1 )
   {
      // The text color is stored as the item data.
      COLORREF   rgbText = (lpDIS->itemState & ODS_SELECTED) ?
            ::GetSysColor(COLOR_HIGHLIGHTTEXT) : GetItemData(nIndex);
      CString str;
      GetText(nIndex, str);
      dc.SetBkColor(rgbBkgnd);
      dc.SetTextColor(rgbText);
      dc.TextOut(rcItem.left + 2, rcItem.top + 2, str);
   }
   dc.Detach();
}

Then, we have to override MeasureItem to tell Windows how high each item is. (Note that the items could each be of different height if we wanted.)

void CLineListBox::MeasureItem(LPMEASUREITEMSTRUCT lpMIS) 
{
   // Set the item height. Get the DC, select the font for the
   // list box, and compute the average height.
   CClientDC   dc(this);
   TEXTMETRIC   tm;
   CFont* pFont = GetFont();
   CFont* pOldFont = dc.SelectObject(pFont);
   dc.GetTextMetrics(&tm);
   dc.SelectObject(pOldFont);
   lpMIS->itemHeight = tm.tmHeight + 4;   
}

Finally, we support sorting by overriding CompareItem:

int CLineListBox::CompareItem(LPCOMPAREITEMSTRUCT lpCIS) 
{
   CString str1, str2;
   GetText(lpCIS->itemID1, str1);
   GetText(lpCIS->itemID2, str2);
   return str1.Compare(str2);
}

All that is needed to see the new list box in action is to create a variable of class CLineListBox, associate it with a list box window (perhaps using Class Wizard), and set the color for each text entered using SetItemData. If no color is specified for a string, it gets displayed in black when it is not selected. Suppose m_listBox is a variable of type, ClineListBox. In that case you may use:

   m_listBox.AddItem("Hello", RGB(255, 0, 0));  // Add string, "Hello", and display it in red.
   m_listBox.AddItem("Windows", RGB(255, 0, 255));  // Add string, "Windows", and display it in magenta.
   m_listBox.AddItem("Color listbox");      // Add string, "Color listbox", and display it in default black."
Has the Compiler Gone Loopy? Dear Dr. GUI:

Our company produces CAD/CAM applications running on Win32. For a few years now, we've been using a compiler from one of your competitors (I don't want to name names, so I won't mention that they used to be "Borland") and we've built up a large code base. A while ago, we decided to check out Visual C++ 5.0 as a possible additional development platform.

We quickly ran into a pretty serious incompatibility... Visual C++ 5.0 still seems to use the old scoping rules for "for" loops; the loop variable remains in scope until the enclosing brace, contrary to the language definition. The following valid C++ code fragment doesn't compile.

      for (int i = 0; i < 10; i++) {
      }
      for (int i = 0; i < 10; i++) {  
            // the old i is still in scope here !?!?
      }

Also, the compiler doesn't allow for variables declarations in the condition expression of a while loop. The following (also legal) C++ code doesn't compile too...

   while (int nErrorCode = GetNextError ()) {
     ... handle the error ...
   }

I don't want to even think about working through half a million lines of code re-structuring loops, so...

1. Is there a workaround?

2. Why does Visual C++ still have such anachronisms?

I guess there's a lot of code out there that uses the old style scoping rules, and you don't want to break them. But I would expect a compiler switch that I can set to revert the compiler to the old style behavior.... By the way, this is what your "unnamed" competitor's compiler does.

Arvind Venkataraman

Dr. GUI replies:

Scope? Have you mistaken Dr. GUI for a dentist? Never mind.

As you likely know, the scope of variables declared in a loop has long been a topic of discussion. And different languages do it differently. For instance, the language that shall remain unnamed (but whose name begins with "J") has behavior that matches the new C++ rule.

First, let's address the inability to declare variables in the expression of a while loop.  The good news is that beginning with Visual C++ version 6.0 this is supported. In addition, other legal declarations in expression, if, and switch statements are also supported in version 6.0.

The scoping problem is a little more complex. As you no doubt are aware, this scoping issue represents a change to the American National Standards Institute (ANSI) spec that the committee made in the eleventh hour. It is one of the most controversial because it does not preserve backward compatibility. In the original Annotated Reference Manual (ARM), which is the base document for the ANSI C++ specification, Section 6.5.3, "The for statement," contains the following:

"There is no special scope rule for a name declared in the initializing statement in a for-statement. This implies that the scope of such a name extends to the end of the block enclosing the for-statement. The absence of a special rule implies that the same name cannot be used to control two for loops in the same scope."

     for(int i = 0; i < 100; i++)  {
          // ...
     }
     for(int i=0; i < 100; i++) {     // error: 'i' defined twice
          //...
     }

So, originally declaring a variable of the same name in two different for loops within the same scope block was specifically illegal. Turning to the recently ratified ANSI C++ specification, we will find that the preceding section has been removed from Section 6.3, and the question is addressed in the scoping section. Section 3.3.2, item #4 of the ANSI specification reads:

"4- Names declared in the for-init-statement, and in the condition of if, while, for, and switch statements are local to the if, while, for, or switch statement (including the controlled statement), and shall not be redeclared in a subsequent condition of that statement nor in the outermost block (or, for the if statement, any of the outermost blocks) of the controlled statement;..."

As you correctly speculated, implementing this feature would unfortunately break many existing applications. Microsoft has always done its best to maintain backward compatibility with new software releases, and Visual C++ is no different. The default behavior for Visual C++ can be overridden by using the /Za compile option (disable MS language extensions). You can quickly confirm that the preceding example will indeed compile without error if the /Za switch is used.

If disabling MS extensions is not an option (unfortunately, some of Microsoft's own SDK headers will not compile with /Za), another workaround is to force each for loop to be in its own scope block. One of the easiest and least invasive ways to do this is to define the following macro:

     #define for if(0) ; else for

Replacing "for(int i = 0, etc.)" with "if (0); else for(int i = 0; etc.)" causes the for loop to be enclosed in the else part of the if statement, giving the desired scoping.

The only disadvantage of this technique is that debug builds using this macro will pay the minor performance and size penalty of having the expression if(0) evaluated before executing each for loop. Files compiled with global optimization enabled (/Og or #pragma optimize("g",on) will have the evaluation of if(0) removed by the optimizer, which will note that the condition is never true.

I Want My Controls to Be Bound—Data-Bound, That Is Dear Dr. GUI:

My company sells an MFC Forms/Grid Extension class that we would also like to make available as an ActiveX control. We would also like to make the control a data-bound control. I saw some information that seemed to imply that ADO and/or OLE DB might replace the old unsupported data binding model, but it did not go into any depth. Please help!!! Where can I get information about writing data-bound controls for ADO/OLE DB data?

Regards,

Thomas Knorst

Dr. GUI replies:

You're in luck, Thomas. As Auntie Mame says, "Help is on the way!"

In the Visual Studio version 6.0 package you will find the new Data Access SDK. This SDK provides information on ActiveX Data Objects (ADO) and OLE DB. It also contains the ActiveX Control Writer's Kit. You will find documentation under the topic "Microsoft Data Access SDK->OLE DB ->ActiveX Control Writer." This kit talks about writing simple and complex data-bound controls for use with an OLE DB provider.

You will typically use the new Microsoft ADO Data Control that comes with Visual Basic 6.0 and Visual C++ 6.0 as the data source. If you have a form or dialog box that contains the ADO Data Control and want to create ActiveX controls that bind to the data control, the ActiveX Control Writer's Kit is precisely what Dr. GUI prescribes. Visual C++ 6.0 also contains a new sample called COMPLEXDB that demonstrates writing a complex data-bound control. You can use it as a good starting point.