=========================================================== INSIDE OLE BY KRAIG BROCKSCHMIDT COMPANION DISC COPYRIGHT (C) 1995 BY KRAIG BROCKSCHMIDT =========================================================== Corrections to Printed Text of Inside OLE Acknowledgements: My thanks also to Paul Gunn for reviewing some of the text. Addendum: OLE And Unicode It may not be clear from the book text itself that the OLE API functions and interfaces defined for Win32 operating systems are 100% Unicode, even on an ANSI platform such as Windows 95. For this reason, ANSI applications have to convert strings between the two character sets when working with certain OLE APIs and interfaces. You'll see such places in the sample code, conditionally compiled using the WIN32ANSI symbol. (If the symbol is not set, the compilation is 100% Unicode.) In short, any string that comes back from OLE contains Unicode characters, and any string you send to OLE has to contain the same. The samples convert strings on a case-by-case basis, as needed. There are, however, some cases where you will not see an explicit conversion in the code, specifically where an OLE API function is concerned. The master header file INC\INOLE.H, which is included with all the samples, will redefine specific OLE API functions to redirect them to ANSI versions implemented in the helper library found in the INOLE directory. Functions found there inside ANSI.CPP will call the Unicode OLE function converting input and output strings as necessary. Addendum: A global comment about the printed code: All the code as it appears in the printed text is oriented around a native Unicode application for the sake of brevity. The actual sample code compiles to ANSI as well as 16-bits, so there will be a few minor differences where strings and string manipulation functions are concerned. In addition, you'll see char, TCHAR, and OLECHAR used for various character types. char will force ANSI characters regardless of all other conditions; TCHAR will conditionally compile to ANSI or Unicode depending on the UNICODE symbol (see BUILD.TXT); OLECHAR is always Unicode (equivalent to WCHAR) even on ANSI-only platforms such as Windows 95. You'll see string literals wrapped in either the TEXT or OLETEXT macro. The TEXT macros conditionally create an ANSI or Unicode string depending again on the compiler flags. The OLETEXT macro, defined in INC\BOOK1632.H, always creates a Unicode macro. Both macros are defined as no-ops for 16-bit compilations. Finally, the INC\BOOK1632.H file contains macros to hide the differences between Win16 and Win32 code. This results in cleaner samples. So if you see a function call in the code that you know is not available or part of the API for the system you're working in, it's probably defined in this file. Page xxiv: A clarification The "Windows Software Development Kit (SDK)" included with Visual C++ isn't the complete Win32 SDK that Microsoft provides separately for Windows 95 and Windows NT (3.5 and 3.51). A few of the book's samples require pieces of the Win32 SDK, specifically the MIDL compiler used to compile custom interfaces in Chapters 6 and 9. You will need the full Win32 SDK to compile CHAP06\IANIMAL, CHAP06\IKOALA, and CHAP09\IDESCRIP. It is also important to note again that Visual C++ 2.0 and 2.1 do not include the header and import library for the standard Windows 95 and Windows NT 3.51 OLE UI Library, OLEDLG.DLL. The files OLEDLG.H and OLEDLG.LIB, which are used in the samples by default, are found in the Win32 SDK products for either platform as well as later versions of Visual C++ 32-bit. Page 55: Table 1-1 BUILD directory The binaries included on the companion disc were compiled and tested on Windows NT 3.51. Perfect operation on Windows 95 is not guaranteed as Windows 95 was not yet released when this book was published. Page 137: Second paragraph, end of second line in that paragraph "RELEASEINTERFACE" should be "ReleaseInterface" Page 145: First paragraph Lo and behold my wife and I bought another car after I wrote this chapter, and I gave away the Datsun 610 as a trade in. So I now drive an 82 Honda Accord. Page 168: Legend on Figure 3-2 The label for "*" should read "only available under 16-bits with OLE 2.02 and later." Page 199: Note near top of page In the future Microsoft will include these files as part of the Win32 SDK outside of the CDK. At the time of writing, however, the CDK was the only source for these files. Page 247: Footnote 6 Windows NT 3.5 actually does allow a client to ask for any class factory interface on a local server. Windows NT 3.51 is no different. However, an interface like IClassFactory2 does not have marshaling support at the time of writing. Page 273: CKoalaClassFactory::CreateInstanceLic This function should not call the classes' own RequestLicKey because the latter will fail when the machine is not globally licensed. For this reason, replace this code: hr=RequestLickey(&bstrTemp); if (FAILED(hr)) return hr; with this code: OLECHAR szLic[256]; mbstowcs(szLic, g_szLic, sizeof(g_szLic)); bstrTemp=SysAllocString(szLic); if (NULL==bstrTemp) return ResultFromScode(E_OUTOFMEMORY); Page 316: Reference to OLEDLG.DLL at end of second paragraph The text says that the samples "attempt" to use OLEDLG.DLL. If you do not have the necessary header files for this library, see BUILD.TXT for information on using the version of the library included with Visual C++ (which would apply to another compiler for the most part as well). Page 325: Reference to [async] in the second paragraph after the code. [async] actually doesn't do anything at the time of writing. The marshaling code generated by MIDL will still generate synchronous calls. Page 380: A clarification to association by bit pattern GetClassFile processes all the FileType entries for a CLSID separately such that any single match for any individual pattern entry will associate the file with the CLSID. This is obviously what should happen, as it allows multiple file formats to map to the same CLSID. The pattern matching process can easily be confused to mean that all patterns must match instead of only one, which is incorrect. Pages 405-406: Clarification to footnote and discussion in general The reason that IPersistStreamInit is not derived from IPersistStream (although it is still polymorphic) is that some objects may need to treat the two interfaces exclusively. That is, an object may very much require the IPersistStreamInit::InitNew semantics such that it does not want to give clients the impression that IPersistStream is suitable. In other words, an object that immplements IPersistStreamInit may not support IPersistStream through its QueryInterface. Those objects that can handle the lack of InitNew, using it only for optimization purposes, can implement both interfaces and provide them both through its QueryInterface. The Polyline sample is such an object. Page 471: Concerning the samples and Windows 95 At the time of writing, these samples suffered from "Invalid Page Fault" crashes on Windows 95 Build 460. They do, however, work fine on Windows NT. You may encounter similar problems with beta versions of Windows 95 yourself, and if you find the problem to actually be in the samples, please let the author know. Page 502: Behavior of ReleaseStgMedium When the STGMEDIUM structure contains an IStorage or IStream, ReleaseStgMedium will call BOTH pUnkForRelease->Release() if non-NULL as well as IStorage::Release or IStream::Release. That means that a data source providing IStorage or IStream types will need to call IStorage::AddRef or IStream::AddRef in addition to supplying a pUnkForRelease if that source wants to maintain ownership of the medium. Page 507: A note about STATDATA The client that enumerates advise connections must be sure to free any non-NULL DVTARGETDEVICE structure inside the FORMATETC of STATDATA with CoTaskMemFree and must also call IAdviseSink::Release. The enumerator object that creates these structures must allocate the DVTARGETDEVICE with CoTaskMemAlloc and must create or AddRef the IAdviseSink pointer. Page 562: Code for Freeloader's CFreeloadDoc::Paste The code, as printed, uses the data obtained from the clipboard after CloseClipboard is called, which is incorrect. The code should appear as follows: HGLOBAL hMem; if (!OpenClipboard(hWndFrame)) return FALSE; /* * Try to get data in order of metafile, dib, bitmap. We set * stm.tymed up front so if we actually get something a call * to ReleaseStgMedium will clean it up for us. */ stm.pUnkForRelease=NULL; stm.tymed=TYMED_MFPICT; hMem=GetClipboardData(CF_METAFILEPICT); if (NULL!=hMem) cf=CF_METAFILEPICT; if (0==cf) { stm.tymed=TYMED_HGLOBAL; hMem=GetClipboardData(CF_DIB); if (NULL!=hMem) cf=CF_DIB; } if (0==cf) { stm.tymed=TYMED_GDI; hMem=GetClipboardData(CF_BITMAP); if (NULL!=hMem) cf=CF_BITMAP; } stm.hGlobal=OleDuplicateData(hMem, cf, NULL); CloseClipboard(); Page 573: Correction to IDataObject::GetData code Because DATATRAN is using pUnkForRelease to maintain ownership of its data, it has to call AddRef through any IStorage or IStream pointer returned from GetData. Accordingly, the code shown at the top of this page is insufficient and should appear as follow: for (i=0; i < cItems; i++) { cb=SendMessage(hList, LB_GETTEXT, i, (LPARAM)&pRen); if (LB_ERR!=cb) { /* * Check if the requested FORMATETC is the same as one * that we already have. If so, then copy that STGMEDIUM * to pSTM and AddRef ourselves for pUnkForRelease. */ if (pFE->cfFormat==pRen->fe.cfFormat && (pFE->tymed & pRen->fe.tymed) && pFE->dwAspect==pRen->fe.dwAspect) { /* * ReleaseStgMedium will Release both storage * and stream elements regardless of the value * of pUnkForRelease, so we have to AddRef the * element and bump our own ref count here. */ if (TYMED_ISTORAGE==pRen->fe.tymed) pRen->stm.pstg->AddRef(); if (TYMED_ISTREAM==pRen->fe.tymed) pRen->stm.pstm->AddRef(); *pSTM=pRen->stm; AddRef(); return NOERROR; } } } Page 670: Additional comments about dual and custom interfaces Versions of Visual Basic later than 3.0 and many versions of Visual Basic for Application support direct calling of dual and vtable custom interfaces. This eliminates most of the advantages that dispinterfaces once enjoyed, that is, the exclusive ability for VB to call that interface. Now that VB can call any interface, using dual interfaces and custom vtable interfaces should be the typical mode of operation, since much greater performance can be achieved with vtable binding. If you want to have dispID binding still, then a dual interface is encouraged. Page 677: Concerning Beeper3. Error objects (as described more below) also solve the problem of returning exceptions from dual interfaces. Beeper3 as it exists does not return exceptions, so an additional sample, Beeper3a, is provided to demonstrate the right way to use error objects from vtable interfaces (including the vtable portion of a dual interface) Pages 701-706: About error objects Besides the multi-threaded issue, error objects were invented to allow vtable interfaces, dual interfaces, and those implemented using the standard dispatch object (demonstrated in Beeper 5) to return exception information. As stated in the text, ITypeInfo::Invoke will call GetErrorInfo to retrieve an exception, but it will do this only for vtable functions that return an HRESULT and return DISP_E_EXCEPTION. Beeper3, however, doesn't do this, as its vtable functions do not return HRESULTs. Therefore you will not see exceptions from Beeper3. A modification of Beeper3 that does this correctly is the sample CHAP14\BEEPER3A, whose custom interface does return HRESULTs and will return DISP_E_EXCEPTION when it has filled an error object. Please refer to that example in preferance to Beeper 3. Page 708: Third paragraph, reason why HRESULT is returned. HRESULT is needed as a return value from dual interface members not only for marshaling but also for support of error objects, as described above. Page 710: Concerning Beeper5 This sample could return exceptions through error objects if it was modified to return HRESULTs from its vtable members instead of other types. In short, the same changes that took Beeper3 to Beeper3a apply to Beeper5. A code example for this is not, however, provided, and is left as an exercise to the reader. As it stands, Beeper5 doesn't return exception information. Page 736: Concerning the test program CHAP15\VBSQUARE This test script works only with versions of Visual Basic after 3.0. Page 808: Cosmo and the registry If you encounter any registry trouble with Cosmo18 after running Cosmo14, go to the registry and delete all entries for Cosmo, then reregister Cosmo18. Page 881: CHAP18\COSMO1.0 This sample has not been thoroughly tested, as OLE 1 on 32-bit platforms is officially not supported by Microsoft. If this sample fails to work under 32-bits, try a 16-bit compilation, which will still interoperate with the 32-bit Cosmo18. Page 997: Code listing for CPatronDoc::Rename The line at the top of the page should read: INOLE_RegisterAsRunning(this, pmk , ROTFLAGS_REGISTRATIONKEEPSALIVE, &m_dwRegROT); Page 999: Code listing for CPage::NotifyTenantsOfRename The second conditional statement at the top of the page should appear as follows: if (NULL!=pmkWild) { INOLE_RegisterAsRunning(this, pmkWild , ROTFLAGS_REGISTRATIONKEEPSALIVE, &m_dwRegROTWild); pmkWild->Release(); } Page 1114: IOleControlSite::FreezeEvents When a container freezes events, it really tells the control that the container will not process those events, but the control is still allowed to fire them. In other words, this function tells a control to not expect any behavior from the container when events are fired until FreezeEvents(FALSE) is called. A container has to robustly handle events that it receives when events have been frozen Page 1139: Second paragraph under "Notes on Polyline..." Polyline on 32 bits does not supply a ToolboxBitmap32, nor does the 16-bit version supply a ToolboxBitmap. Page 1164: Make yourself a big "Yippee! You Made It!" note on this page. Give yourself a reward when you finish reading this mighty tome. You deserve it! And thank you for reading it as well!