May/June 1999
Dr. GUI's disposition is much sunnier now that the days are longer and brighter and the good doctor is planning on enjoying a nice spring and summer. He's especially happy to see that the April (and March, and February, and … ) showers have brought not only lots of beautiful flowers but also a fresh crop of challenging questions.
Dr. GUI is also tickled pink about the new reorganization at Microsoft—especially the new (or renewed) Developer Division. The good doctor hopes you're pleased too: Having a division of Microsoft focused on your needs can't be anything but good for you. You can read more about it at www.microsoft.com/presspass/features/1999/03-29reorg.htm.
Dr. GUI's glad—really glad—to finally be running Microsoft® Windows® 2000 (beta 3). The best thing about it is that it's considerably faster than Windows NT® version 4.0. Most of Dr. GUI's software and hardware run fine, with two exceptions: the mouse driver doesn't yet support some of the cool capabilities of the good doctor's Microsoft Intellimouse® Trackball (such as click and hold for dragging and a-page-per-click scrolling with the wheel), and there's no driver yet for the good doctor's SyQuest SparQ parallel port disk drive, even though the previous driver worked under versions 3.51 and 4.0. (That could be a good porting project—it's been a while since Dr. GUI has done intricate surgery on device drivers and other such patients.)
Dr. GUI's favorite feature, though, is that Windows 2000 addresses one of Dr. GUI's pet peeves about Windows: applications that steal focus from the app you're working on. (Longtime readers will remember Dr. GUI complaining bitterly about this in the past.)
An app can try to steal focus, but Windows 2000 doesn't let it. Instead, the app's button on the taskbar starts flashing to let you know that an app other than the foreground app wants attention.
It's brilliant. And it works, making Windows 2000 much more productive than previous versions.
Dr. GUI's also upgraded to Microsoft Office 2000, which, in subtle ways, makes getting work done much easier. It's especially nice that drawings made in Word can be saved as .gif files automatically when you select Save As Web Page.
Last, but certainly not least, Dr. GUI is very pleased, as are most reviewers (see www.microsoft.com/windows/ie/Reviews/default.asp), with Microsoft Internet Explorer 5.0. It's faster, easier to use, and crashes very infrequently. What more could you want?
Having all these upgrades is almost like having a new computer. Almost.
Be sure to check out the developer's pages for these products, too: the Windows 2000 readiness program at http://msdnisv.microsoft.com/msdnisv/win2000/, the Office 2000 developer's home page at http://msdn.microsoft.com/officedev/, and Internet Explorer 5 developer's information at www.microsoft.com/Windows/ie/Developers/.
Dear Dr. GUI,
I've been developing a DLL (first.dll) with some COM objects with VB 6.0 for accessing them from ASP pages. Then, I create a new DLL (second.dll) which uses some objects of the first.dll. Everything looks ok. The problem came when I made some minor changes on the first.dll, and rebuilt it. Although I register it again with the same version number, the second.dll could not recognize it. As I open the second.dll project, the reference to the first.dll said it was missing, so I had to reference it again and recompile the second.dll. Am I doing something wrong? Should I always rebuild the second.dll each time I rebuild the first.dll?
Claudio Fernandez
It sounds like you have a case of binary incompatibility. But don't worry; this is not a serious problem—and it mainly tends to affect Microsoft ActiveX® components that are created in Microsoft Visual Basic®.
You could rebuild the second DLL every time you rebuild the first DLL. But in these days of increasing health care costs, Dr. GUI doesn't like to operate unless he needs to. To fix the problem, you will need to set the version compatibility for your object to "Project Compatibility." Set this on the Component tab in the Project Properties dialog box. The choices are:
Remember to always increment the file version number for release versions of your file so Setup will know which version of the file is newer.
If you'd like to learn more about version compatibility so you can talk about it at your next office party, check out the following: "Maintaining Version Compatibility.txt" (http://msdn.microsoft.com/vbasic/downloads/download.asp?ID=012), and Chapter 5, "COM Servers," in Programming Distributed Applications with COM and Microsoft Visual Basic 6.0 by Ted Pattison, published by Microsoft Press (http://mspress.microsoft.com/books/2137.htm).
Dear Dr. GUI,
I'm using the TLI (type library information) objects within VB to examine component interfaces. I've run into a couple of problems where I hope you can help.
Any ideas?
Ken Elston
Good question. Dr. GUI is good at examinations, even of type libraries. The TLI (type library information) object model gives you several ways to browse a type library. (The TLI object is implemented in the tlbinf32.dll, which can be found in the Microsoft Visual Studio® version 6.0 and Visual Basic 6.0 CDs.) Based on the description of your problem, the good doctor assumes you've already retrieved a MemberInfo object for the property Get. For this question, it doesn't matter if you retrieved your MemberInfo via Members.Item, TypeInfo.GetMember, or TypeLibInfo.GetMemberInfo.
What's hard is interpreting the VarTypeInfo object returned by the MemberInfo.ReturnType property. (You can also get a VarTypeInfo object from MemberInfo.Parameters(n).VarTypeInfo.) VarTypeInfo plays the role of representing an arbitrary type. In the case of a simple variant type (VT_I4, VT_DISPATCH, and so on), you can simply look at the VarType property (the default value of a VarTypeInfo object) to determine the type. However, if the VarType comes back as 0 (VT_EMPTY), you're looking at a type library-defined type instead of a simple variant type. You can get the type information for this type from the TypeInfo object returned by the VarTypeInfo.TypeInfo property. Matters are complicated slightly if you're looking at an array type, in which case VarType will be VT_ARRAY | VT_I4 for an array of longs, but just VT_ARRAY for an array of TypeInfo types. Note that you'll usually want to set TLI.ResolveAliases to False before looking at VarTypeInfo objects.
With the TypeInfo object you can use the Name, GUID, and TypeKind properties to find out about the type, and the Parent property to get a TypeLibInfo object with information about the containing typelib.
As for the second question, if the TypeKind property is TKIND_COCLASS, the object may implement additional interfaces. Simply walk the TypeInfo.Interfaces collection to search for the interface in question. The GUID property, or a combination of the Name and Parent.Name, can be used to identify the interface. When searching for implemented interfaces, you'll want to ignore any items in the Interfaces collection with an AttributeMask containing IMPLTYPEFLAG_FSOURCE. Your function might look like this:
Function IsInterfaceSupported(VTInfo As TLI.VarTypeInfo, strGUID As String) As Boolean
Dim VTBase As Integer
Dim TI As TypeInfo
With VTInfo
VTBase = .VarType And Not (VT_ARRAY Or VT_VECTOR)
If VTBase = VT_EMPTY Then
With .TypeInfo
Select Case .TypeKind
Case TKIND_INTERFACE , TKIND_DISPATCH
If .Guid = strGUID Then
IsInterfaceSupported = True
End If
Case TKIND_COCLASS
For Each TI In .Interfaces
If 0 = (TI.AttributeMask And IMPLTYPEFLAG_FSOURCE) Then
If TI.Guid = strGUID Then
IsInterfaceSupported = True
Exit For
End If
End If
Next
Case TKIND_ALIAS
'See help file. This won't happen
'if TLI.ResolveAliases was True when
'VTInfo was retrieved.
End Select
End With
End If
End With
End Function
I'd recommend studying the PrototypeMember function in the Help file, which you can download from http://msdn.microsoft.com/vbasic/downloads/addon.asp. (In the Enterprise Development Tools Partner Downloads section, see the link marked "Visual Studio 6.0 Typelib Information Object Library HTML Help file.")
Dear Dr. GUI,
How do I execute a SQL 7 DTS package from VB code? The package is already defined, and executes without errors using Enterprise Manager as well as "dtsrun." I found some sample code, but it wanted steps and tasks, etc. I just want to run the package. Using the "shell" command works, but I'd like something more elegant.
Thanks!
Randy Geater
Dr. GUI's style isn't usually to participate in executions, but because this execution won't kill anyone, Dr. GUI will oblige. If you're installing the Microsoft SQL Server™ 7.0 client components on the machines on which you're going to install your Visual Basic application, take advantage of the DTSPackage object library. Simply reference the "Microsoft DTSPackage Object Library" from your Visual Basic application and you can use code as simple as this:
Set dtsPackage = New DTS.Package
dtsPackage.LoadFromSQLServer ServerName:="MyServer", _
ServerUserName:="MyUserID", _
ServerPassword:="MyPassword", _
PackageName:="MyPackage"
dtsPackage.Execute
The DTSPackage object library is extremely powerful. Not only can you run packages stored on a SQL Server, in the Repository, or from a file, but you can also create and modify packages using this library.
To install just the SQL Server client components, select Install SQL Server 7.0 Components, select either the Standard or Desktop Edition, and then select Client Components when you get to the Select Components screen.
If you don't plan on installing the SQL Server 7.0 client components, you can still run the DTS package on your server by using the xp_cmdshell stored procedure:
strShell = "dtsrun /S MyServerName /U MyUserID /P MyPassword /N MyPackage"
strSQL = "{CALL xp_cmdshell('" & strShell & "', 'no_output')}"
cnSQLServer.Execute strSQL, , adCmdText + adExecuteNoRecords
Dear Dr. GUI,
I have a problem with the size_is modifier. I think it doesn't work properly.
I tried to pass a variable size array of bytes from a client to a local server without success. Only the first byte of the array is transferred to the server.
Here is the method declared in my server IDL:
HRESULT Transfer([in] long cbSize,
[in, size_is(cbSize)] unsigned char cBuffer[])
Here is my client code:
ITargetObj * pITargeObj ;
HRESULT hRC = ::CoCreateInstance( CLSID_TargetObj, NULL, CLSCTX_SERVER,
IID_ITargetObj, (void **)&pITargetObj ) ;
If FAILED( hRC )
{
AfxMessageBox( "Failed to make target object!" ) ;
return FALSE ;
}
BYTE * pBuffer = (BYTE *)::CoTaskMemAlloc( 15 ) ;
CopyMemory( pBuffer, "HELLO WORLD!!!!", 15 ) ;
pITargetObj->Transfer( 15, pBuffer ) ;
::CoTaskMemFree( pBuffer ) ;
pITargetObj ->Release() ;
Thanks for your help.
Best regards,
David Lindemann
Every once in a while, Dr. GUI is asked whether size_of matters. (Not often, though—after all, he's Dr. GUI, not Dr. Ruth.) Your code is proof positive that size_of does matter—if it isn't working properly, you crash.
The good doctor tested your code and it seems to work fine in an Active Template Library (ATL) local server, with an object supporting a custom interface (after building and registering the proxy/stub). The only possible explanation is that you used an ATL object supporting a dual interface. In this case, Microsoft Interface Definition Language (MIDL) should have given you the following warning:
Target.idl(18) : warning MIDL2039 : interface does not conform to [oleautomation] attribute : [ Parameter 'cBuffer' of Procedure 'Transfer' ( Interface 'ITargetObj' ) ]
The problem is that the size_is attribute is incompatible with dual and oleautomation attributes. In spite of the warning, MIDL will still go ahead and create the type library. Looking at the type library using OLE COM Object Viewer you will find Transfer has the following prototype:
HRESULT Transfer([in] long cbSize, [in] unsigned char* cBuffer);
Because the size_is attribute is stripped out (it's not supported by Automation), only the first character is transferred from client to server because the type library is used to marshal the interface. Obviously, this will not do. By the way, one good way to pass an array that does work for dual and dispatch interfaces is to use a SAFEARRAY.
Dear Dr. GUI,
We have a number of small communications programs written in VB3 that work well on Win3.1 and Win95 systems but fail on WinNT. We found that we could fix the problem by setting the priority of NTVDM.EXE to "High" from "Normal." The problem is that this setting doesn't stick. Every time we reboot NT we have to reset the priority. Is there a way to tell NT to store this priority as default? Failing that, is there a way to programmatically change the priority setting? We tried the Start command but it didn't seem to work.
Thanks,
Bill Soo
As hard as Dr. GUI tries, he still gets these psychology questions. Oh, wait—this one is about operating system priorities, not personal priorities. Well, okay. Here goes:
There is no way to set a default priority level for an application. However, as you guessed, this can be done programmatically. It is a matter of calling SetPriorityClass and passing HIGH_PRIORITY_CLASS in the second parameter. The more difficult problem is getting the first parameter, which is the handle to the ntvdm.exe process. On Windows NT, the Process Status APIs (PSAPI) can be used to enumerate processes running on the system. The following steps can be used to find the ntvdm.exe process.
Note There may be more than one ntvdm.exe. The preceding steps will affect all of them.
Dear Dr. GUI,
I'm trying to write an ATL control using CContainedWindow to wrap a standard windows control. It happens to be a CBS_DROPDOWNLIST ComboBox, and here's the rub: it's owner-draw [CBS_OWNERDRAWFIXED, CBS_HASSTRINGS, CBS_SORT].
The control draws okay at first glance: I get the "edit" control, and the dropdown arrow. When I drop the list, the owner draw callbacks happen, and the items draw as I expect. But when I select an item in the list, the "edit" control does NOT draw with the selected item; I'm looking for the ODS_COMBOBOXEDIT itemState flag in the OnDrawItem callback and I never get it.
So what do I have to intercept and pass along to the CContainedWindow to get the "edit" control to call my DrawItem callback function? Is this problem specific to combo boxes (being a list <-> edit interaction) or is it possibly a general owner-draw problem in ActiveX controls?
Kevin Routley
Getting the right messages to the right window can be tricky—and in this case, a wizard-created ATL ActiveX control based on the window's drop-down list combo-box control, there are three levels of windows: the ATL control window, which is the parent of the combo box; the combo box itself, consisting of the static portion that displays the selected text; and a child list box that gets dropped down upon selecting the drop-down arrow.
There are two sets of WM_DRAWITEM messages—from the list box to the combo box and from the combo box to the ATL window. WM_DRAWITEM messages are sent by the owner-drawn controls to their owners. If not handled, the message sent from the list box to the combo box window gets sent by the combo box to the ATL window, after changing the control type from ODT_LISTBOX to ODT_COMBOBOX. From your description it looks like you are handling the WM_DRAWITEM messages sent by the list box to its parent, the combo box—that is probably the reason you do not see ODS_COMBOBOXEDIT state notification. It's a whole lot easier to handle the message in the ATL window. The following message map from a wizard-generated ATL control project shows the two possible places where you place handlers for WM_DRAWITEM:
BEGIN_MSG_MAP(CODCombo)
// Correct place for WM_DRAWITEM handler
MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItemFromCombo)
CHAIN_MSG_MAP(CComControl<CODCombo>)
ALT_MSG_MAP(1)
// Replace this with message map entries for superclassed ComboBox
// handler here for WM_DRAWITEM is premature, so we comment out
//MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItemFromListBox)
END_MSG_MAP()
Now, with the single handler in place, the code to correctly handle text selection and focus can be incorporated from the SDK sample OWNCOMBO, which ships with Microsoft Visual C++®. Specifically, look at the WM_DRAWITEM handling in the OwnerComboBoxExample() function in owncombo.c.
Dear Dr. GUI,
I'm trying to build a wizard using the new IE 5.0 common controls and I'm having problems with trying to set the watermark property, because MFC 6.0 (CPropertySheet) uses an old version of the Property Sheet Header, i.e. _AFX_OLDPROPSHEETHEADER. Is there a way to use MFC to access the new wizard 97 features or is there an alternative way to do this with ATL? I also need this to be a stand alone EXE.
Any help will be appreciated!
Barb Regnier
Usually people want to get watermarks off their property, not on them. Martha Stewart may be able to help you get the watermarks off, but Dr. GUI can help you put them on.
To access the new Wizard 97 features using MFC version 6.0 is just too simple. MFC provides you a class CpropertySheetEx, which uses the new PROPSHEETHEADER. There is also a corresponding CPropertyPageEx class that uses the new PROPPAGEHEADER. However, the Class Wizard does not provide you the option to derive from these classes.
But not to worry, it does not require much code. Derive your classes from CPropertySheet and CPropertyPage. But now, in your CWizardSheet (let's call your CPropertySheet-derived class by this name), replace the parameters of the constructor with the parameters of the CPropertySheetEx and change all instances of CPropertySheet in your code to CPropertySheetEx. Remember to add the flag PSH_WIZARD97 instead of PSH_WIZARD to the m_psh.dwFlags member of your CPropertySheetEx class.
It is even simpler with the CPropertyPageEx (assuming you use the default constructor), because CPropertyPage and CPropertyPageEx default constructors take the same parameters. Simply replace all instances of CPropertyPage in your code with CPropertyPageEx.
The CPropertySheetEx constructor takes the handle to the bitmap (fourth parameter) you want to use as the watermark on your property page. It also takes an HPALETTE. Pass this as NULL if you want to use the default palette to paint the watermark. (The watermark is not always displayed. We'll see that later.) Finally, the last additional parameter is the HBITMAP you might want to use for painting the header of your property page. Yes, CPropertyPageEx lets you have a header at the top of every page. If you choose to display the header, this bitmap is used to paint the header, if this is NULL, it uses the handle to the watermark bitmap (fourth parameter) to paint the header.
There are some additional options. You can choose to pass a title and subtitle for your property page. Look at the CPropertyPageEx constructors. The title appears as the caption of your page and the subtitle over the bitmap in the header. You also have the option of hiding the header. Add the PSP_HIDEHEADER flag to the m_psp.dwFlags member of your CPropertyPageEx. In this case, the watermark is displayed on the property page.
Everything else is the same as in the CPropertySheet and CPropertyPage classes.
Dear Dr. GUI,
I am dynamically creating dialogs using the Win32 API. I have a base template that I use with DialogBoxIndirect(). In my WM_INITDIALOG I dynamically create additional controls.
The problem is, the dynamically created controls are not part of the tab order, even though I have included the WS_TABSTOP style.
Any idea what's wrong? And how can I change the tab order at runtime?
Thanks!
Dave
It's been a while since Dr. GUI looked at these issues. The last time the phrase "tab order in a dialog" came up, it had something to do with talking about who owed what at a restaurant.
To understand the problem you're having, let's throw some light on how the keyboard navigation takes place in a dialog. In both modal and modeless dialogs, keyboard navigation is provided by a function called IsDialogMessage. This function provides an enormous amount of keyboard navigation within the dialogs for free. (It also makes dialog behavior consistent across Windows apps.)
When the user presses a TAB key, IsDialogMessage makes a call to the GetNextDlgTabItem API to get the next tabable item and sets focus to it. The item will not be part of the tab order if it is a static, hidden, or disabled control, or if it does not have the WS_TABSTOP style set. But you're using WS_TABSTOP, so that's not the issue for you.
Another reason the controls might not get focus is if the dynamically created controls are present in a parent window inside the dialog that does not have the WS_EX_CONTROLPARENT style set. Finally, note that the modal dialogs (DialogBox() and DialogBoxIndirect()) have the IsDialogMessage inside their message loop, but modeless dialogs (CreateDialog() and CreateDialogIndirect()) must call IsDialogMessage explicitly inside the main message loop. Except for these cases, the dynamically created controls should be part of the tab order.
To change the tab order at run time you would need to call the SetWindowPos API. Microsoft Windows maintains all the windows in the system in a list. It uses this list for purposes like painting the windows, maintaining the z order, and so on. The order in which the child controls (in a dialog) are created determines their position in the list and, hence, the tabbing order. By changing the position of the windows in this list, we can change the tabbing order. Change the window list order by calling SetWindowPos and passing the relevant windows handles in the first parameter (handle of the window to insert in order) and the second parameter (handle of the window to insert after). The window handle specified in the first parameter would come after the second in the tab order.
Dear Dr. GUI,
I have recently started a project in DirectX, and have been suffering from considerable performance problems in an area of the program which has to lock and unlock the entire back buffer every frame.
I think I would gain a considerable benefit from putting the back buffer in system memory, instead of video memory where it is currently located, so I don't have to copy all the data twice per frame (from and to video memory). It was implied that I could do this in the Microsoft Press book on DirectX, but I can find no examples on how to do this, and find myself struggling.
Any ideas you have would make my life much more bearable.
Thanks,
Simon Detheridge
Dr. GUI doesn't know if buffered graphics reduce stomach upset, but he does know that if you buffer well, you can gain a substantial performance increase.
If your current rendering system uses a complex flipping chain (a primary buffer with an attached back buffer), you cannot explicitly create a primary buffer in video memory and have the back buffer in system memory. The only way this can happen is when video memory is scarce. If you create a complex surface with one attached surface and the back buffer cannot fit into display memory, it is allocated in system memory. A call to Flip in this case is emulated with a Blt.
If your current rendering system uses a primary surface and an offscreen surface for a back buffer, you can create the back buffer in system memory. To do this, you will need the DDSCAPS_SYSTEMMEMORY flag. For example:
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
ddsd.dwHeight = 100;
ddsd.dwWidth = 100;
In addition to creating the back buffer in system memory, you need to make sure the hardware can perform blitting to and from system memory. Use IDirectDraw4::GetCaps and check for the following flags:
Creating a back buffer in system memory instead of video memory to avoid the Win16 mutex may not gain you any performance. If your rendering system copies the back buffer to the primary using Blt or BltFast, you are trading video-to-video hardware-assisted memory copy to system-to-video across the bus with or without Direct Memory Access (DMA) memory copy.
You need to keep in mind that each technique needs to be profiled on different video systems to determine the best solution for you.
Dear Dr. GUI,
In your response to Kit Ruparel on the question of the pronunciation of CIM, you mentioned that this is the 2,050,467,329th three-letter-acronym (TLA) in use at Microsoft. You then say you're not going to claim that there are more TLAs than GUIDs or particles in the universe, but you didn't explain how you resolve the meanings of all the redundant TLAs. There are only 17,576 combinations of the 26 uppercase letters in the English alphabet. This means that, on average, each combination of three letters is used in something over 100,000 TLAs. If you allow case-sensitivity, the problem is reduced somewhat (140,608 combinations, average overloading less than 15,000), but this seems unlikely, as Microsoft has remained largely insensitive to case at least since MS-DOS 1.0. (Doesn't the Justice Department or EEOC or somebody get on your case, so to speak, about this?)
So, how do you tell the overloaded TLAs apart? Munge (ok, decorate) their names a la C++? Somehow, this seems less than helpful.
A Concerned Citizen,
Jesse Pelton
Your concern is commendable. And maybe the good doctor was exaggerating, or using a little poetic license. On the other hand, remember that the good doctor lives in a Unicode world. And with tens of thousands of possibilities for each character, it's not hard to come up with two billion acronyms.
Dr. GUI wants to take this opportunity to thank his team of specialists: Ranga Kalyanasundaram, Brian Combs, Israel Burman, David Sceppa, Jaganathan Thangavelu (who took TWO questions!), Carlos Lopez, Arun Kumar, Hung H. Nguyen, and Tom Moran. The good doctor also appreciates Tom Moran in his role as specialist coordinator.