Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ
August 1995


Code for this article: activexcode0895.exe (18KB)
Don Box is a co-founder of DevelopMentor where he manages the COM curriculum. Don is currently breathing deep sighs of relief as his new book, Essential COM (Addison-Wesley), is finally complete. Don can be reached at http://www.develop.com/dbox/default.asp.

Q Is it possible to enumerate all of the interfaces that a COM object supports? What is the easiest way to map the binary GUID of an interface to a printable string?

A While the ability to map a GUID (specifically an IID) to a printable string would be extremely useful in debugging, enumerating all supported interfaces (specifically ones that you know nothing about) serves almost no purpose, since there's no consistent way of discovering the type signatures of the interface member functions at run time. The only use I can think of is for building run time object browsers (such as Ole2View). That stated, enumerating interfaces is an interesting challenge and reveals some of the glue that holds COM together. I'll solve the enumeration problem first, and wrap up by building a run-time object inspector to test the solution.
Since there is no API for enumerating interfaces, you need to write one. Let's take the simple approach and just fill a caller-supplied array with the IIDs of all of the supported interfaces.


 DWORD GetSupportedInterfaces(IUnknown *punk,
                              IID *iids,
                              DWORD nArraySize);
To implement this function, you'll need a simple helper function to verify whether or not an interface is supported:

 BOOL IsInterfaceSupported(IUnknown *punk, 
                           REFIID riid)
 {
 // try the interface
   IUnknown *punkIf;
   HRESULT hr = punk->QueryInterface(riid, 
                                     (void**)&punkIf);
 // clean up
   if (SUCCEEDED(hr))
       punkIf->Release();
                                      
   return SUCCEEDED(hr); 
 }
Now implementing GetSupportedInterfaces is a matter of enumerating the appropriate IIDs.
One possible technique for enumerating all supported interfaces would be to iterate across all legal IIDs and test each one. Since GUIDs can be thought of as extremely long integers (sizeof(GUID) == 16), you could generate all possible GUIDs by generating all 2128 values in a loop, trying each one out in succession. Because such a loop would literally take decades to complete, even on an in-proc object, some other approach is in order.
The best implementation would test only the IIDs that actually correspond to real interfaces. The IIDs for the standard interfaces are listed in the system header files, but the IIDs for custom interfaces are not, so statically building the list at compile time won't work. One way to dynamically find all bound IIDs is to look at a section of the registry that lists the Proxy/Stub implementations used in standard marshalling. Interfaces are registered by GUID under the HKEY_CLASSES_ROOT\Interface key (see Figure 1). By definition, all remotable (marshallable) interfaces must be listed here; many non-remotable interfaces are listed as well. Now finishing the implementation of the function is a simple matter of using RegEnumKey to find each registered IID and only test for these (see Figure 2).
Figure 1 Finding interface IDs
Figure 1 Finding interface IDs

Once you've got the supported interfaces, you need their human-readable names. One part of the registry entry is the human-readable name of the interface. By convention, the default value of the IID's registry key is the name of the interface. With this information it's easy to write a function that looks up an IID under \Interface and finds its value using RegQueryValue (see Figure 3). Given the GetInterfaceName function, you can implement QueryInterface, a function that will output something useful to the debugging device using OutputDebugString (see Figure 4).
Figure 5 contains the source code for IFDROP.EXE, a somewhat more interesting application of both GetInterfaceName and GetSupportedInterfaces. IFDROP.EXE displays the supported interfaces of any object dropped onto its main window (see Figure 6). If the dropped data object contains an embedded OLE compound document, IFDROP temporarily creates the embedding and enumerates its interfaces. If the dropped data object does not contain an embedded object, IFDROP simply inspects the dropped object directly.
Figure 6 IFDROP
Figure 6 IFDROP

While IFDROP is virtually useless to end users, it is interesting to programmers for seeing how various applications export extended functionality to aware clients. Figure 6 shows that Microsoft® Word embedded documents implement IDispatch interfaces. This interface can be used by applications such as PowerPoint® to change the formatting of an embedded object without invoking the UI of the server application.

Q I can instantiate a new document in my Automation-enabled application from Visual Basic® using CreateObject, but when I use GetObject to get a reference to the currently active document, the call fails. What am I doing wrong?

Lots of people
Via Internet

A To understand why GetObject is failing, it first helps to understand how GetObject decides which document is the current document. The Visual Basic GetObject function takes two parameters (a filename and a ProgID) and returns an Automation-capable object. Either argument can be NULL (in Visual Basic, this means the parameter is missing), and the behavior of the function is radically different depending on which parameter is provided. If GetObject is called with just the filename,


 Dim fooDoc as Object
 Set fooDoc = GetObject("C:\docs\Docked.foo")
Visual Basic will create a moniker based on the filename and attempt to bind to it. If the file is already opened for editing, GetObject will return an IDispatch interface to the running object. If the file is not currently open, the bind operation will attempt to instantiate a new COM object and initialize it from the contents of the file. The instantiable CLSID is resolved internally using the GetClassFile API, which uses a variety of techniques to resolve the filename to a CLSID:
  • If it's a structured storage file, an attempt is made to read the attached CLSID using ReadClassStg.
  • The file is tested against the various bit patterns stored under the \FileType registry key, looking for a match.
  • The subkey under HKEY_CLASSES_ROOT that matches the extension of the file is opened (in this case, \.foo), and should have the ProgID stored at the key's default value. Given a ProgID, GetClassFile can then derive the corresponding CLSID using CLSIDFromProgID.
If none of these three techniques yields a CLSID, the GetObject call will fail. If one of these techniques comes up with a valid CLSID, the bind operation will use it to call CoCreateInstance to instantiate the object. Once the object is instantiated, Visual Basic will call QueryInterface on it, looking for an IDispatch interface to return to the caller of GetObject.
The behavior described above is a byproduct of COM classes that support IPersistFile (or IPersistStorage) and IDispatch. As long as the registry keys and values point to the appropriate places, things just magically work. This is exactly what happens by default when you use the Visual C++ AppWizard to generate an Automation-enabled OLE compound document server.
It seems logical that since MFC-based applications support the use of GetObject discussed above, they would also support the second form of GetObject as well. This is not the case. With the second form of GetObject, the caller simply provides a ProgID and Visual Basic will find the active object for that ProgID:

 Dim fooDoc as Object
 Set fooDoc = GetObject(, "Foo.Document")
Visual Basic is utilizing COM's notion of an active object, which is maintained on a per-class basis. There are three OLE API functions that allow you to set and get the active object of a given class:

 HRESULT RegisterActiveObject (LPUNKNOWN punk,
                               REFCLSID rclsid,
                               LPVOID pvreserved,
                               DWORD* pdwRegister);
 HRESULT RevokeActiveObject (DWORD dwRegister,
                             LPVOID pvreserved);
 HRESULT GetActiveObject (REFCLSID rclsid,
                          LPVOID pvreserved,
                          LPUNKNOWN * punk,);
Given these API functions, it is relatively straightforward for Visual Basic to implement the second form of GetObject (see Figure 7).
To allow an application to take advantage of GetObject, all you have to do is make sure that the currently active object is registered using RegisterActiveObject. Unfortunately, this is not as trivial as it sounds.
Ideally, you want to register your document object (and any contained subobjects) when one of the document's windows receives input focus. For an MFC application, this could be done in the OnActivateView member of the View class:

 void CMyView::OnActivateView(BOOL bActivate, 
                              CView* pActivateView,
                              CView* pDeactiveView) 
 {
 // call base class implementation
   CView::OnActivateView(bActivate, 
                         pActivateView,  
                         pDeactiveView);

 // register document object if active
   if (bActivate) {
     CMyDoc *pDoc = GetDocument();
     LPUNKNOWN punk = pDoc->GetIDispatch(FALSE);
     RegisterActiveObject(CLSID_MyDoc, 
                          punk,
                          0, 
                          &(pDoc->dwReg));
 }
Note that even though the view becomes nonactive, the document remains the active object. The document should only cease to be the active object when either another document of the same class becomes UI active or the document is destroyed.
Under 16-bit Windows
®, achieving this effect is trivial. By taking advantage of the undocumented behavior of RegisterActiveObject, each view can just register itself as active when it receives focus, and the registration preempts the previously active object. This technique works under Win32® only if one instance of the application is running. Calling RegisterActiveObject does not preempt the current active object if it was registered by another application. This means that to get the desired effect, you need to manually detect which application instance has foreground status and notify all other instances to revoke their registered objects to make way for the foreground app. Additionally, when an application shuts down, it must notify the topmost instance in z-order that it needs to reregister its object as active, as it may not be the topmost application on the desktop. One way to implement this is to store the thread IDs of all instances of the application in a shared array maintained in sorted z-order.
Figure 8 shows a class called SharedSortedDWORDArray. This class uses a shared section object and mutex to manage an array of DWORDs that will correspond to the thread IDs of your application. SharedSortedDWORDArray simply appends new DWORDs to the end of the array after removing any duplicate entries. Given the array implementation shown, it is now possible to build a class that can arbitrate UI activation and active object registration across process boundaries.
Figure 9 contains a class called Arbitrator that does just that. Arbitrator objects keep track of the active object for the process's UI thread. This is controlled using the RegisterObject and RevokeObject member functions. Arbitrator objects also keep track of which Arbitrator object is active on the system. To do this, an Arbitrator object calls the SuspendApp and ResumeApp member functions when the application loses or gains UI activation and calls the ActiveChanging member function when a WM_ACTIVECHANGING message is received by the application's main frame window.
When the Arbitrator receives a ResumeApp call, it moves its thread ID to the top of the shared array. Both SuspendApp and ResumeApp call PostChangeMessage, which broadcasts the registered window message WM_ACTIVECHANGING to all application frame windows. In response to this window message, it is assumed that the frame WndProc will notify the application's Arbitrator by calling ActiveChanging. ActiveChanging simply tests to see if the current thread is the topmost thread, and if so, it registers the cached object using RegisterActiveObject. If the current thread is not the topmost thread, ActiveChanging revokes the cached object using RevokeActiveObject. The RegisterObject member will call RegisterActiveObject, as it assumes that the current thread is the topmost thread.
Adding Active Object support to an application is extremely simple with the Arbitrator. The first step is to declare a global Arbitrator object for your document's class. (I use MFC here, but it's easy to generalize this code.)

 // from mainfrm.cpp
 Arbitrator g_arbitrator(clsid, __TEXT("MyDoc"));
Step two handles the registered WM_ACTIVECHANGING message in your main frame window.

 // from mainfrm.cpp
 
 // add this entry to the frame's message map
 ON_REGISTERED_MESSAGE(WM_ACTIVECHANGING, \
                       OnActiveChanging)

// implement this function LPARAM CMainFrame::OnActiveChanging(WPARAM wParam, LPARAM lParam) { g_arbitrator.ActiveChanging(); return 0; }
Next, handle the WM_ACTIVATEAPP message in your main frame window.

 // from mainfrm.cpp
 
 // add this entry to the frame's message map
 ON_WM_ACTIVATEAPP()
 
 // implement this function
 void CMainFrame::OnActivateApp(BOOL bActive, 
                                HTASK hTask) 
 {
   CMDIFrameWnd::OnActivateApp(bActive, hTask);
 	
   if (bActive)
     g_arbitrator.ResumeApp();
   else
     g_arbitrator.SuspendApp();
 }
Then, in your View class's OnActivateView, register the document's IDispatch interface with the Arbitrator.

 // from myview.cpp
 extern Arbitrator g_arbitrator;
 
 void 
 CMyView::OnActivateView(BOOL bActivate, 
                         CView* pActivateView, 
                         CView* pDeactiveView) 
 {      
 // call base class implementation
   CView::OnActivateView(bActivate, 
                         pActivateView, 
                         pDeactiveView);
       
 // register document object if active
   if (bActivate) {
     CMyDoc *pDoc = GetDocument();
     LPUNKNOWN punk = pDoc->GetIDispatch(FALSE);
     g_arbitrator.RegisterObject(punk);
   }
 }
Finally, in your Document class's destructor, revoke the document's active status with the Arbitrator.

 // from mydoc.cpp
 extern Arbitrator g_arbitrator;
 
 void 
 CMyDoc::~CMyDoc(void) 
 {
   g_arbitrator.RevokeObject(GetIDispatch(FALSE));
   AfxOleUnlockApp();  	
 }
These five simple steps enable your app to take complete advantage of the Visual Basic GetObject function.

Q Functions in the OLE API that accept text-based parameters are prototyped using the LPOLESTR data type, while the Win32 API functions use the LPTSTR data type. What is the correct way to write code that works with both text data types?

A Like the Win32 API, the 32-bit OLE API supports Unicode text, which uses the 16-bit wchar_t data type to store each character. Following the Win32 model, the OLE header files are not hardcoded to Unicode, but are prototyped using generic text data types to allow backwards compatibility with ANSI/DBCS-based code through conditional compilation. The preprocessor symbols UNICODE and OLE2ANSI determine the "width" of the Win32 and OLE APIs, respectively. The various text data types for both Win32 and OLE are listed in Figure 10.
Unlike the Win32 API, OLE is uniformly implemented using Unicode under all 32-bit incarnations of Windows (Windows NT
, Windows® 95, and Win32s®). Additionally, there are few text-based entry points in OLE, and more than half of the text-based entry points are interface member functions, not global functions. Because of this, it is recommended that you access OLE using only Unicode (that is, compile without defining OLE2ANSI) and simply coerce any ANSI strings you need to pass to OLE by hand.
Converting to and from Unicode can be done using either the Win32 API functions MultiByteToWideChar


 int MultiByteToWideChar (
    UINT  CodePage,            // code page 
    DWORD  dwFlags,            // mapping flags 
    LPCSTR  lpMBStr,           // narrow string 
    int  cchMultiByte,         // narrow strlen
    LPWSTR  lpWideCharStr,     // wide string
    int  cchWideChar           // wide buf size
 );
or WideCharToMultiByte

 int WideCharToMultiByte(
    UINT  CodePage,            // code page 
    DWORD  dwFlags,            // mapping flags 
    LPCWSTR  lpWideCharStr,    // wide string 
    int  cchWideChar,          // wide strlen
    LPSTR  lpMultiByteStr,     // narrow string
    int  cchMultiByte,         // narrow buf size
    LPCSTR  lpDefaultChar,     // def for bad char(s)
    LPBOOL lpUsedDefaultChar   // bad char happened?
  );
or by using ANSI C library functions.

 size_t mbstowcs( 
    wchar_t *wcstr,           // wide string 
    const char *mbstr,        // narrow string
    size_t count              // narrow string len
 );
 size_t wcstombs( 
    char *mbstr,              // narrow string 
    const wchar_t *wcstr,     // wide string
    size_t count              // wide string len
 );
Both pairs of functions perform essentially the same operation. The Win32 versions allow the code page to be specified and permit the caller to provide a default character sequence if Unicode characters do not map into the narrow character set. If your primary concern is simply to wedge narrow character strings into Unicode, you can safely use the simpler C library functions to do the conversion.
Run-time conversion is straightforward for strings used as input parameters (that is, char-based strings for use as const OLECHAR * parameters), since the conversion only needs to take place in one direction. The easiest thing to do is provide some mechanism that allows the caller to simply wrap all string parameters in some sort of expression:

 // from objbase.h
 HRESULT StgCreateDocfile(LPCOLESTR szFileName, 
                         DWORD flags, DWORD res,
                         IStorage **ppstg);
 IStorage *MyCreateFile(LPCSTR szName)
 {
   IStorage *result;
   StgCreateDocfile(String16(szName), // force wide
                    dwFlags, 0, &result);
   return result;
 }
The OLESTR macro cannot be used on the szName variable because it only performs a token paste of the leading L that forces string literals to be represented as Unicode strings. What you need instead is some mechanism that accepts a narrow string as an argument and yields the corresponding wide version. If String16 were a function, it would look something like this.

 const wchar_t * String16(const char *sz)
 {
  // return the converted version of sz
 }
The problem is, it's extremely difficult to implement this function correctly. The obvious implementation would use a static buffer:

 const wchar_t * String16(const char *sz)
 {
   wchar_t result[SOME_ARBITRARY_MAX_LEN];
   mbstowcs(result, sz, sizeof(result) - 1);
   return result;
 }
But this solution is not reentrant, and will fail when used to convert more than one function parameter at a time or when used in a multithreaded environment. The reentrancy problem could be solved by dynamically allocating a string for each conversion

 const wchar_t * String16(const char *sz)
 {
   size_t len = mbstowcs(0, sz, strlen(sz)) + 1;
   wchar_t *result = new wchar_t[len];
   mbstowcs(result, sz, len);
   return result;
 }
but you can't reliably ensure that the caller will delete the string after it is used. Assuming you are using C++, one solution to the problem would be a "shim" object that can be created as a temporary when passed as an argument and will clean up properly at the end of its lifetime. As a minimum, such a class would need the following public members:

 class String16 {
 public: 
 // constructor to alloc and convert string
   String16(const char *sz8);
 // typecast operator to give out converted string
   operator const wchar_t * (void) const;
 // destructor to clean up alloced string
   ~String16(void);
 };
Given a class definition (and corresponding implementation), you can now temporarily widen any string by creating an anonymous String16 object in the parameter list.

 IStorage *MyCreateFile(LPCSTR szName)
 {
   IStorage *result;
   StgCreateDocfile(String16(szName), // force wide
                    dwFlags, 0, &result);
   return result;
 }
When the code executes, MyCreateFile will wind up calling String16::String16, then String16::operator const wchar_t *, then StgCreateDocfile, and finally String16::~String16—which is exactly what you want.
Figure 11 shows a complete implementation of the String16 and the corresponding String8 classes. Both implementations have overloaded constructors, allowing both narrow and wide character strings to be used. When the native constructor is used, the string is simply passed through the object, and no conversion or allocation needs to be performed. This passthrough usage permits conditional compilation, so you can create Win32 and OLE-compatible data types that, like the __TEXT and OLESTR macros, use the UNICODE and OLE2ANSI symbols.

 #ifdef OLE2ANSI
 typedef String8 OLESTRCVAR;
 #else
 typedef String16 OLESTRCVAR;
 #endif
 
 #ifdef UNICODE
 typedef String16 __TEXTCVAR;
 #else
 typedef String8 __TEXTCVAR;
 #endif
OLESTRCVAR and __TEXTCVAR emulate the behavior of OLESTR and __TEXT by converting strings at run time. For speed, therefore, it is still preferable to use OLESTR and __TEXT for string literals.
The OLESTRCVAR and __TEXTCVAR types correct an overwhelming majority of OLE cross-wideness problems, as most of the OLE entry points that use text take only strings as input parameters. It is important to note that a lot of input string parameters are incorrectly prototyped using the non-const LPOLESTR data type instead of the correct LPCOLESTR type. Where this occurs, you need to explicitly force the String16 class to invoke its const-correct typecast operator by first casting to const and then casting away const.

 // const incorrect prototype from objbase.h
 HRESULT CLSIDFromString(LPOLESTR sz, LPCLSID lpc);
 // correct usage of OLESTRCVAR
 CLSID GetCLSID(LPCSTR sz)
 {
   CLSID result;
   CLSIDFromString(
            LPOLESTR(    // cast away const
            LPCOLESTR(   // convert to wide const
            OLESTRCVAR(  // accept narrow string
            sz))),
            &result);
   return result;
 }
Expressions like this one make a convincing argument for investing the time to implement const-correct interfaces.

Have a question about programming with ActiveX or COM? Send your questions via email to Don Box at dbox@develop.com or http://www.develop.com/dbox/default.asp

From the August 1995 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe .

© 1997 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2014 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft