Code Project Introduction To COM Part I
Code Project Introduction To COM Part I
Code Project Introduction To COM Part I
- CodeProject
Pagina 1 di 11
Beginner
VC6Win2K, Visual-Studio, MFC, ATL, COM, Dev Posted: Updated: Views: 1 Jul 2000 27 Jul 2000 778,507
542 votes for this article. Popularity: 13.08 Rating: 4.78 out of 5
1 2 3 4 5
Introduction
COM (Component Object Model) is the popular TLA (three-letter acronym) that seems to be everywhere in the Windows world these days. There are tons of new technologies coming out all the time, all based on COM. The documentation throws around lots of terms like COM object, interface, server, and so on, but it all assumes you're familiar with how COM works and how to use it. This article introduces COM from the beginning, describes the underlying mechanisms involved, and shows how to use COM objects provided by others (specifically, the Windows shell). By the end of the article, you will be able to use the COM objects built-in to Windows and provided by third parties. This article assumes you are proficient in C++. I use a little bit of MFC and ATL in the sample code, but I will explain the code thoroughly, so you should be able to follow along if you are not familiar with MFC or ATL. The sections in this article are: COM - What Exactly Is It? - A quick introduction to the COM standard, and the problems it was created to solve. You don't need to know this to use COM, but I'd still recommend reading it to get an understanding of why things are done the way they are in COM. Definitions of the Basic Elements - COM terminology and descriptions of what those terms represent. Working with COM Objects - An overview of how to create, use, and destroy COM objects. The Base Interface - IUnknown - A description of the methods in the base interface, IUnknown. Pay Close Attention - String Handling - How to handle strings in COM code. Bringing it All Together - Sample Code - Two sets of sample code that illustrate all the concepts discussed in the article. Handling HRESULTs - A description of the HRESULT type and how to test for error and success codes. References - Books you should expense if your employer will let you. :)
http://www.codeproject.com/KB/COM/comintro.aspx?display=Print
18/01/2010
Pagina 2 di 11
http://www.codeproject.com/KB/COM/comintro.aspx?display=Print
18/01/2010
Pagina 3 di 11
Finally, the COM library is the part of the OS that you interact with when doing COM-related stuff. Often, the COM library is referred to as just "COM," but I will not do that here, to avoid confusion.
rclsid
The CLSID of the coclass. For example, you can pass CLSID_ShellLink to create a COM object used to create shortcuts.
pUnkOuter
This is only used when aggregating COM objects, which is a way of taking an existing coclass and adding new methods to it. For our purposes, we can just pass NULL to indicate we're not using aggregation.
dwClsContext
Indicates what kind of COM servers we want to use. For this article, we will always be using the simplest kind of server, an in-process DLL, so we'll pass CLSCTX_INPROC_SERVER. One caveat: you should not use CLSCTX_ALL (which is the default in ATL) because it will fail on Windows 95 systems that do not have DCOM installed.
riid
The IID of the interface you want returned. For example, you can pass IID_IShellLink to get a pointer to an IShellLink interface.
ppv
Address of an interface pointer. The COM library returns the requested interface through this parameter.
http://www.codeproject.com/KB/COM/comintro.aspx?display=Print
18/01/2010
Pagina 4 di 11
When you call CoCreateInstance(), it handles looking up the CLSID in the registry, reading the location of the server, loading the server into memory, and creating an instance of the coclass you requested. Here's a sample call, which instantiates a CLSID_ShellLink object and requests an IShellLink interface pointer to that COM object.
HRESULT hr; IShellLink* pISL; hr = CoCreateInstance ( CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**) &pISL ); if ( SUCCEEDED ( hr ) ) { // Call methods using pISL here. } else { // Couldn't create the COM object. } // // // // // CLSID of coclass not used - aggregation type of server IID of interface Pointer to our interface pointer
First we declare an HRESULT to hold the return from CoCreateInstance() and an IShellLink pointer. We call CoCreateInstance() to create a new COM object. The SUCCEEDED macro returns TRUE if hr holds a code indicating success, or FALSE if hr indicates failure. There is a corresponding macro FAILED that tests for a failure code.
if ( SUCCEEDED ( hr ) ) { // Call methods using pISL here. // Tell the COM object that we're done with it. pISL->Release(); }
http://www.codeproject.com/KB/COM/comintro.aspx?display=Print
18/01/2010
Pagina 5 di 11
1. AddRef() - Tells the COM object to increment its reference count. You would use this method if you made a copy of an interface pointer, and both the original and the copy would still be used. We won't need to use AddRef() for our purposes in this article. 2. Release() - Tells the COM object to decrement its reference count. See the previous example for a code snippet demonstrating Release(). 3. QueryInterface() - Requests an interface pointer from a COM object. You use this when a coclass implements more than one interface. We've already seen Release() in action, but what about QueryInterface()? When you create a COM object with CoCreateInstance(), you get an interface pointer back. If the COM object implements more than one interface (not counting IUnknown), you use QueryInterface() to get any additional interface pointers that you need. The prototype of QueryInterface() is:
HRESULT IUnknown::QueryInterface ( REFIID iid, void** ppv );
iid
The IID of the interface you're requesting.
ppv
Address of an interface pointer. QueryInterface() returns the interface through this parameter if it is successful. Let's continue our shell link example. The coclass for making shell links implements IShellLink and IPersistFile. If you already have an IShellLink pointer, pISL, you can request an IPersistFile interface from the COM object with code like this:
HRESULT hr; IPersistFile* pIPF; hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );
You then test hr with the SUCCEEDED macro to determine if QueryInterface() worked. If it succeeded, you can then use the new interface pointer, pIPF, just like any other interface. You must also call pIPF>Release() to tell the COM object that you're done using the interface.
TCHAR and the _t functions (for example, _tcscpy()) are designed to let you handle Unicode and ANSI
strings with the same source code. In most cases, you'll be writing code that uses ANSI strings and the ANSI Windows APIs, so for the rest of this article, I will refer to chars instead of TCHARs, just for simplicity. You should definitely read up on the TCHAR types, though, to be aware of them in case you ever come across them in code written by others. When you get a Unicode string back from a COM method, you can convert it to a char string in one of several ways: 1. 2. 3. 4. Call Call Use Use the WideCharToMultiByte() API. the CRT function wcstombs(). the CString constructor or assignment operator (MFC only). an ATL string conversion macro.
http://www.codeproject.com/KB/COM/comintro.aspx?display=Print
18/01/2010
Pagina 6 di 11
You can convert a Unicode string to an ANSI string with the WideCharToMultiByte() API. This API's prototype is:
int WideCharToMultiByte ( UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar );
CodePage
The code page to convert the Unicode characters into. You can pass CP_ACP to use the current ANSI code page. Code pages are sets of 256 characters. Characters 0-127 are always identical to the ASCII encoding. Characters 128-255 differ, and can contain graphics or letters with diacritics. Each language or region has its own code page, so it's important to use the right code page to get proper display of accented characters.
dwFlags dwFlags determine how Windows deals with "composite" Unicode characters, which are a letter followed by a diacritic. An example of a composite character is è. If this character is in the code page specified in CodePage, then nothing special happens. However, if it is not in the code page,
Windows has to convert it to something else. Passing WC_COMPOSITECHECK makes the API check for non-mapping composite characters. Passing WC_SEPCHARS makes Windows break the character into two, the letter followed by the diacritic, for example e`. Passing WC_DISCARDNS makes Windows discard the diacritics. Passing WC_DEFAULTCHAR makes Windows replace the composite characters with a "default" character, specified in the lpDefaultChar parameter. The default behavior is WC_SEPCHARS.
lpWideCharStr
The Unicode string to convert.
cchWideChar
The length of lpWideCharStr in Unicode characters. You will usually pass -1, which indicates that the string is zero-terminated.
lpMultiByteStr
A char buffer that will hold the converted string.
cbMultiByte
The size of lpMultiByteStr, in bytes.
lpDefaultChar
Optional - a one-character ANSI string that contains the "default" character to be inserted when dwFlags contains WC_COMPOSITECHECK | WC_DEFAULTCHAR and a Unicode character cannot be mapped to an equivalent ANSI character. You can pass NULL to have the API use a system default character (which as of this writing is a question mark).
lpUsedDefaultChar
Optional - a pointer to a BOOL that will be set to indicate if the default char was ever inserted into the ANSI string. You can pass NULL if you don't care about this information. Whew, a lot of boring details! Like always, the docs make it seem much more complicated than it really is. Here's an example showing how to use the API:
// Assuming we already have a Unicode string wszSomeString... char szANSIString [MAX_PATH]; WideCharToMultiByte ( CP_ACP, // ANSI code page
http://www.codeproject.com/KB/COM/comintro.aspx?display=Print
18/01/2010
Pagina 7 di 11
After this call, szANSIString will contain the ANSI version of the Unicode string.
wcstombs()
The CRT function wcstombs() is a bit simpler, but it just ends up calling WideCharToMultiByte(), so in the end the results are the same. The prototype for wcstombs() is:
size_t wcstombs ( char* mbstr, const wchar_t* wcstr, size_t count );
mbstr
A char buffer to hold the resulting ANSI string.
wcstr
The Unicode string to convert.
count
The size of the mbstr buffer, in bytes.
wcstombs() uses the WC_COMPOSITECHECK | WC_SEPCHARS flags in its call to WideCharToMultiByte (). To reuse the earlier example, you can convert a Unicode string with code like this:
wcstombs ( szANSIString, wszSomeString, sizeof(szANSIString) );
CString
The MFC CString class contains constructors and assignment operators that accept Unicode strings, so you can let CString do the conversion work for you. For example:
// Assuming we already have wszSomeString... CString str1 ( wszSomeString ); CString str2; str2 = wszSomeString; // Convert with a constructor.
ATL macros
ATL has a handy set of macros for converting strings. To convert a Unicode string to ANSI, use the W2A() macro (a mnemonic for "wide to ANSI"). Actually, to be more accurate, you should use OLE2A(), where the "OLE" indicates the string came from a COM or OLE source. Anyway, here's an example of how to use these macros.
#include <atlconv.h> // Again assuming we have wszSomeString... { char szANSIString [MAX_PATH]; USES_CONVERSION; // Declare local variable used by the macros. lstrcpy ( szANSIString, OLE2A(wszSomeString) ); }
http://www.codeproject.com/KB/COM/comintro.aspx?display=Print
18/01/2010
Pagina 8 di 11
The OLE2A() macro "returns" a pointer to the converted string, but the converted string is stored in a temporary stack variable, so we need to make our own copy of it with lstrcpy(). Other macros you should look into are W2T() (Unicode to TCHAR), and W2CT() (Unicode string to const TCHAR string). There is an OLE2CA() macro (Unicode string to a const char string) which we could've used in the code snippet above. OLE2CA() is actually the correct macro for that situation, since the second parameter to lstrcpy() is a const char*, but I didn't want to throw too much at you at once.
But keep in mind that wcout expects all strings to be in Unicode, so if you have any "normal" strings, you'll still need to output them with std::cout. If you have string literals, prefix them with L to make them Unicode, for example:
wcout << L"The Oracle says..." << endl << wszOracleResponse;
If you keep a string in Unicode, there are a couple of restrictions: You must use the wcsXXX() string functions, such as wcslen(), on Unicode strings. With very few exceptions, you cannot pass a Unicode string to a Windows API on Windows 9x. To write code that will run on 9x and NT unchanged, you'll need to use the TCHAR types, as described in MSDN.
http://www.codeproject.com/KB/COM/comintro.aspx?display=Print
18/01/2010
Pagina 9 di 11
In this sample, I used std::wcout to display the Unicode string wszWallpaper. Using a COM object with a multiple interfaces The second example shows how to use QueryInterface() with a COM object that exposes a single interface. The code uses the Shell Link coclass contained in the shell to create a shortcut to the wallpaper file that we retrieved in the last example. The steps involved are: 1. 2. 3. 4. 5. 6. 7. Initialize the COM library. Create a COM object used to create shortcuts, and get an IShellLink interface. Call the SetPath() method of the IShellLink interface. Call QueryInterface() on the COM object and get an IPersistFile interface. Call the Save() method of the IPersistFile interface. Release the interfaces. Uninitialize the COM library.
// Convert the wallpaper path to ANSI
// 1. Initialize the COM library (make Windows load the DLLs). Normally you would // call this in your InitInstance() or other startup code. In MFC apps, use // AfxOleInit() instead. CoInitialize ( NULL ); 2. Create a COM object, using the Shell Link coclass provided by the shell. // The 4th parameter tells COM what interface we want (IShellLink). hr = CoCreateInstance ( CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**) &pISL ); if ( SUCCEEDED(hr) ) { // 3. Set the path of the shortcut's target (the wallpaper file). hr = pISL->SetPath ( sWallpaper ); if ( SUCCEEDED(hr) ) {
http://www.codeproject.com/KB/COM/comintro.aspx?display=Print
18/01/2010
Pagina 10 di 11
The
Handling HRESULTs
I've already shown some simple error handling, using the SUCCEEDED and FAILED macros. Now I'll give some more details on what to do with the HRESULTs returned from COM methods. An HRESULT is a 32-bit signed integer, with nonnegative values indicating success, and negative values indicating failure. An HRESULT has three fields: the severity bit (to indicate success or failure), the facility code, and the status code. The "facility" indicates what component or program the HRESULT is coming from. Microsoft assigns facility codes to the various components, for example COM has one, the Task Scheduler has one, and so on. The "code" is a 16-bit field that has no intrinsic meaning; the codes are just an arbitrary association between a number and a meaning, just like the values returned by GetLastError (). If you look up error codes in the winerror.h file, you'll see a lot of HRESULTs listed, with the naming convention [facility]_[severity]_[description]. Generic HRESULTs that can be returned by any component (like E_OUTOFMEMORY) have no facility in their name. Examples: REGDB_E_READREGDB: Facility = REGDB, for "registry database"; E = error; READREGDB is a description of the error (couldn't read the database). S_OK: Facility = generic; S = success; OK is a description of the status (everything's OK). Fortunately, there are easier ways to determine the meaning of an HRESULT than looking through winerror.h. HRESULTs for built-in facilities can be looked up with the Error Lookup tool. For example, say you forgot to call CoInitialize() before CoCreateInstance(). CoCreateInstance() will return a value of 0x800401F0. You can enter that value into Error Lookup and you'll see the description: "CoInitialize has not been called."
You can also look up HRESULT descriptions in the debugger. If you have an HRESULT variable called hres, you can view the description in the Watch window by entering "hres,hr" as the value to watch. The ",hr" tells VC to display the value as an HRESULT description.
http://www.codeproject.com/KB/COM/comintro.aspx?display=Print
18/01/2010
Pagina 11 di 11
References
Essential COM by Don Box, ISBN 0-201-63446-5. Everything you'd ever want to know about the COM spec and IDL (interface definition language). The first two chapters go into great detail about the COM spec and the problems it was designed to solve. MFC Internals by George Shepherd and Scot Wingo, ISBN 0-201-40721-3. Contains an in-depth look at MFC's COM support. Beginning ATL 3 COM Programming by Richard Grimes, et al, ISBN 1-861001-20-7. This book goes into depth about about writing your own COM components using ATL.
License
This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below. A list of licenses authors might use can be found here
Member
http://www.codeproject.com/KB/COM/comintro.aspx?display=Print
18/01/2010