A Simple Method for Obtaining Version Information from System DLLs
Dan Shappir
[email protected]
In his May 2001 article DLL Versions from C and VB, Jim Gentilin described a method to obtain DLL version information using C or Visual Basic. This method involved using the FileVersion API, a somewhat complex and confusing collection of functions and data structures. The Visual Basic implementation is especially complicated because Visual Basic doesnt directly support the memory manipulations required to access the version information.
While the method described in that article is guaranteed to work for all modules that contain version information, there is a much simpler alternative that works for some system DLLs. It turns out that several system DLLs export a function named DllGetVersion() that returns a structure containing the version information. This structure is much simpler than those used by the FileVersion API and can be easily parsed in Visual Basic.
Example 1 shows how to use DllGetVersion() to obtain the version information for the Common Controls DLL (comctl32.dll). Both the Common Controls and the Shell DLLs expose this service starting with version 4.71. Additional DLLs that expose it include mfc42.dll, msi.dll, and msjava.dll. Note that DllGetVersion() has an HRESULT return value, thus success is indicated by a value greater than or equal to 0.
If you want to use this service from C++, you can save yourself a bit of work by using AtlGetDllVersion() defined in atlbase.h. ATL provides two flavors of this function: one that accepts a module instance handle and another that takes a file path. ATL also implements two higher-level functions named AtlGetCommCtrlVersion() and AtlGetShellVersion(), which provide the version of the Common Controls and the Shell appropriately. The prototypes for these functions are:
HRESULT AtlGetDllVersion(HINSTANCE hInstDLL, \ DLLVERSIONINFO *pDllVersionInfo); HRESULT AtlGetDllVersion(LPCTSTR lpstrDllName, \ DLLVERSIONINFO *pDllVersionInfo); HRESULT AtlGetCommCtrlVersion(LPDWORD pdwMajor, \ LPDWORD pdwMinor); HRESULT AtlGetShellVersion(LPDWORD pdwMajor, \ LPDWORD pdwMinor);
Comments in the ATL source code (atlbase.h) indicate which values are returned for the various Windows and browser versions.
Universal Data Links
Michael D. Potter
[email protected]
Sometimes, one of the most difficult aspects of ADO database programming is establishing the connection. You fiddle through MSDN and the data providers documentation trying to locate the proper parameters that are needed to talk to a particular database. It ends up being a time-consuming effort filled with frustration. A couple of years ago, I ran across a free tool that comes with Microsoft Data Access Components (MDAC) that simplifies this effort. I am surprised by the number of programmers (most with better skills than I) that have no knowledge of this tool.
Microsoft has a special file format called a Universal Data Link. Any file with a .udl extension is embodied with data source connection capabilities. What makes these files so powerful is their self-defining nature. Double clicking on a .udl file or choosing properties from Windows Explorer initiates a set of property sheets that allow you to establish database connections in a prompted nature. This helps to keep you out of the reams of documentation.
To create a Universal Data Link, first create an empty text file anywhere on your hard drive. Rename the extension to .udl. When you execute this file (double click, choose properties, and so on), you will be presented with a Data Link Properties dialog box that contains the necessary property pages to make an ADO connection. This includes a Test Connection button for your trial and error work. The fields presented in the dialog box should be very familiar to an ADO programmer. Of special interest is the All tab. This contains all of the properties that your chosen provider supports what a time saver.
After you have defined your UDL file via the property pages and tested your connection, go ahead and hit OK to save your changes. There are two ways to make use of this UDL file. One way is to open it directly with any text editor. Inside, you will find a fleshed out connection string that you can copy and paste directly into your source code. The other way to make use of a UDL file is by referencing the file directly in a connection string:
"File Name=C:\MyUDLS\MyDataLink.UDL"
The space between File and Name is required. I especially enjoy this method because I have found it makes changing a data source a breeze. If you can secure the UDL files from external access, you can even safely encapsulate the user names and passwords. They are wonderful for use in ASP and MTS objects. I hope you will find this UDL tidbit useful. I know it has saved me a tremendous amount of time and frustration.
The Ins and Outs of ATLs FinalConstruct() and FinalRelease()
Dan Shappir
[email protected]
An aspect of the Microsoft Active Template Library (ATL) that may initially surprise C++ programmers is the use of the FinalConstruct() and FinalRelease() methods for object initialization and finalization. After all, that is precisely the purpose of C++ constructors and destructors. While ATL does not preclude the use of constructors and destructors, it does deprecate them in favor of FinalConstruct() and FinalRelease().
There are two reasons why ATL was designed this way. The first has to do with object creation failure. Because constructors do not have a return value, they can only indicate failure by throwing an exception or by setting a designated member variable. ATL cannot use the first method because in release mode it does not link with the C/C++ Run Time Library, and exception handling in VC++ relies on this library. The second method, allocating and setting an error flag, is obfuscated, error-prone, and wastes memory. FinalConstruct(), on the other hand, has an HRESULT return value, so it can easily indicate success or failure. In fact, it is this value that is returned to CoCreateInstance(). It is important to note that ATL considers any value other than S_OK to indicate failure in this case, even S_FALSE. See Example 2 for the ATL object instantiation code.
The second reason ATL uses FinalConstruct() and FinalRelease() is because it is implemented in such a way that an objects COM interfaces cannot be used until the object is fully constructed. When you define a C++ class that implements a COM object, ATL actually creates an object instance from a class that derives from it. It is this ATL class that implements the IUnknown methods. As a result, these methods cannot be used until after your constructor finishes. In addition, in release mode, ATL uses an optimization that prevents the creation of a virtual function table for classes that are not the most derived. Because of this, no interface can be used before the object is fully constructed. This significantly limits which COM operations can be performed in constructors and destructors. ATL invokes FinalConstruct() after the object has been constructed. Likewise, FinalRelease() is activated before the object is destroyed. As a result, they can both safely use the objects own interfaces.
There are several downsides to ATLs use of FinalConstruct() and FinalRelease(). The first problem has to do with the difference in behavior between constructors and destructors and the other methods. A C++ constructor automatically invokes the default constructor of any base class or data member. If a default constructor does not exist, and you neglect to provide a call to some other constructor in the initialization list, the compiler will generate an error. Other C++ methods do not automatically invoke the base methods they overload; instead, they simply hide them. As a result, FinalConstruct() and FinalRelease() must explicitly call the implementations of the functions in the base classes and data members. If you forget to make these calls, the program will compile with no errors, but will probably malfunction at runtime. ATL attempts to prevent this problem by simply not using these functions in its classes. In those cases where these functions are used, such as the OLE DB Provider templates, the ATL wizard generates a skeleton for your class with the appropriate implementations of FinalConstruct() and FinalRelease(). You must exercise care if you modify these functions.
There are two additional problems with FinalConstruct() that have to do with reference counting, and unfortunately, ATL only provides a solution for one. When entering FinalConstruct(), an objects reference count is equal to zero; it is only incremented to 1 after FinalConstruct() returns successfully. As a result, if the reference count is incremented and then decremented by the same amount, the object will self-destruct. This is in contrast to normal COM scenarios, where incrementing the reference count and then decrementing it by the same amount has no effect. To guard against this, ATL provides the DECLARE_PROTECT_FINAL_CONSTRUCT macro. If this macro is placed in the class definition, ATL increments the objects reference count before calling FinalConstruct() and decrements it afterwards, without destroying the object if the count goes to zero. I recommend that you always include this macro because its overhead is minuscule.
The second reference count problem can occur if FinalConstruct() fails (returns a value other than S_OK.) ATL attempts to handle this case like a failed constructor: it destroys the object and frees its memory. The problem is that this will happen even if the objects reference count is greater than zero. For example, if during initialization the object registers itself in the systems Running Object Table (ROT) or a process Global Interface Table (GIT), or simply hands over an interface to another object, and then fails, it will have created a dangling interface pointer. Any call through that interface may result in a crash. Unfortunately, ATL does not provide any safeguards against this type of bug. Looking at the ATL object instantiation code, I would have placed an assertion prior to the delete to verify that the reference count is indeed zero. Regardless, you must verify that any operation that incremented an objects reference count is undone in the FinalConstruct() prior to returning an error value.
SetupApi Source Path Problems
Richard Renzetti
[email protected]
If you use the SetupApi functions to install files, you may find that you get different results depending on whether you are running under NT 4.0 or Windows 2000. The problem occurs when you attempt to install a CAB file using its associated INF file if the CAB file is located in a subdirectory on a CD-ROM. Example 3 shows a code fragment that demonstrates the problem. Under NT 4.0, SetupApi will attempt to access the CAB file in the root directory of the CD-ROM instead of the subdirectory you specify. Since the CAB file is not available in the root directory, the installation will fail. Windows 2000 does not exhibit this problem, so its important to test your code under both platforms.
Microsoft knows about this problem; it works around it by copying the CAB and INF to a temporary directory on your hard drive and installing the files from the temporary directory. For some reason, the bug doesnt occur when the files are on a hard drive.
About the Author
Chris Branch is a software engineer at FSCreations Inc. in Cincinnati, Ohio. He can be reached at [email protected].