2009년 11월 03일
[Variable] COM & ATL by Silan Liu #1
Silan Liu
1. COM Basics
1.1. Service Control Manager (SCM)
A Windows program called Service Control Manager (SCM) does most of the job in COM server invoking. It finds the server through System Registry, runs it, has it create the COM object, sets up local/remote transparency, and returns an interface pointer to the client.
Then the client can directly invoke methods of the COM object through the pointer, and there is no middleware involved unless it is a remote server, in which case only RPC is involved.
1.2. Interface IUnknown
All COM interfaces should inherit interface IUnknown:
interface IUnknown
{
HRESULT QueryInterface(REFIID iid, void** ppvObject);
ULONG AddRef();
ULONG Release();
}
One COM object may be used by multiple clients. The COM server maintains a reference count for each interface of the object. When one client asks to create an instance of the interface withCoCreateInstance or CoCreateInstanceEx, the COM server will call that interface’s method AddRef to increment the reference count.
When one client is finished with an interface, it should call its Release method to decrement the reference count. When the count reaches zero, the COM server should destroy the COM object.
In Visual Basic, a local COM object is automatically destroyed when leaving scope. To manually delete an object, say
1.3. Global Unique ID (GUID)
Each coclass and interface has its guid, which is a 128-bit number. For easy use by the programmer, an easy-remember manifest constant is defined for each guid. In Visual C++ these constants are defined in a header file e.g. “Bank_i.c”, and some entries look like:
const IID IID_IGreet = {0x7A5E6E81,0x3DF8,0x11D3,{0x90,0x3D,0x00,0x10,0x5A,0xA4,0x5B,0xDC}};
const IID LIBID_BANKLib = {0x0FFBDAA1,0xFCA7,0x11D2,{0x8F,0xF4,0x00,0x10,0x5A,0xA4,0x5B,0xDC}};
const CLSID CLSID_Account = {0x0FFBDAAE,0xFCA7,0x11D2,{0x8F,0xF4,0x00,0x10,0x5A,0xA4,0x5B,0xDC}};
GUID constants such as IID_IGreet and CLSID_Account are not globally unique, but it does not matter because they live only in the scope of one application program. The compiler will read the interface definition file to convert the constant to the corresponding guid.
GUIDs for classes are of type CLSID, and all variables start with “CLSID_” followed by the class name, such as “CLSID_Account”. GUIDs for interfaces are of type IID, and all start with “IID_” followed by the interface name such as “IID_IAccount”.
To convert between CLSID and unicode array:
CLSID clsid;
...
WCHAR * ostr;
HRESULT hr = StringFromCLSID(clsid, &ostr);
Program “Microsoft Visual Studio\Common\Tools\Guidgen.exe” is used to generate a guid. Choose the format of the guid and press button “Copy”. The generated guid will be in the clipboard.
You can also add it to the “Tools” menu with menu “Tools | Customize | Tools”.
1.4. OLE/COM Object Viewer
OLE/COM Object Viewer goes through System Registry and collects all info about each coclass and their interfaces, and put them under one entry as their “user names”, which is the help string in the IDL file. You can find this entry under “All Objects” entry.
The “user name” entry stores the info about the coclass itself. Under it there are entries for all the interfaces of this class. Each entry stores the info about a specific interface.
OLD/COM Object Viewer can be started both from the “Start” menu and from Visual C++ menu “Tools”.
1.5. System Registry
System Registry contains information about the computer. Under its “HKEY_CLASSES_ROOT” entry, all information about a coclass is stored. Under this entry, all coclasses are listed by their ProgIDs. Among the countless ProgID entries, there are two other entries: one named CLSID, under which all coclasses are listed by their guids; the other named Interface, under which all interfaces are listed by their guids.
ProgIDs are not guaranteed to be unique. They are used by some languages that can not directly refer to guids such as VBScript. VB can directly refer to guids, and can also optionally use ProgIDs. COM library provides to functions which can go through the System Registry and convert a ProgID to a CLSID or vice versa:
CLSID clsid;
CLSIDFromProgID(L"Cars.Jeep.1", &clsid);
LPOLESTR progid;
ProgIDFromCLSID(clsid, &progid);
We also need an entry to store some attributes about the whole server. For each server we can assign an AppID, under which we store all those atttributes such as AccessPermission,AuthenticationLevel, DllSurogate, LunchPermissions, RemoteServerName, etc. Then under each coclass entry under HKCR\CLSID, we add an AppID entry containing the AppID of the server.
When a coclass in invoked, the SCM reads the CLSID entry under HKCR\CLSID. If it finds an AppID entry, it will go further to find the AppID entry under HKCR\AppID. There it reads more about the server and knows how to deal with the server. For example, if SCM finds RemoteServerName entry there containing the name of another computer, it knows that the server is located oin another computer and it should contact the SCM of that computer.
When you call COM library functions such as CoCreateInstance passing a guid constant such as "CLSID_CoCar" or "IID_IRegistration", the compiler converts the guid contstant to the real guid by looking up the guid definition file *_i.c. At run time the real guid is sent to the API function.
¨ DLL server's entries in System Registry
A DLL server should have the following basic entries in the System Registry:
HKEY_CLASSES_ROOT\<ProgID>\CLSID = <clsid>
HKEY_CLASSES_ROOT\CLSID\<clsid> = <ProgID>
HKEY_CLASSES_ROOT\CLSID\<clsid>\InprocServer32 = <server full path>
You do not have to register your type library, for VC can directly #import from any directory, and VB and J++ can browse to find a *.tlb file. However, if you want to do it, you should add the following type library entries:
HKEY_CLASSES_ROOT\CLSID\<clsid>\TypeLib = <libid>
HKEY_CLASSES_ROOT\TypeLib\<libid> = <type library help string>
HKEY_CLASSES_ROOT\TypeLib\<libid>\1.0\0\Win32 = <type library full path>
HKEY_CLASSES_ROOT\TypeLib\<libid>\1.0\FLAGS = 0
HKEY_CLASSES_ROOT\TypeLib\<libid>\1.0\HELPDIR
When client calls CoCreateInstance with a CLSID and a IID, the COM run time will:
1. go to the HKCR\CLSID\<guid>\InProcServer32 entry to find the location of the DLL server.
2. load the DLL into process, call its exported DllGetClassObject function passing the coclass guid to get an IClassFactory pointer of the corresponding class factory;
3. call the IClassFactory's CreateInstance method passing the IID of the interface to get a pointer of the interface.
You can see in the above process that the guids of the interfaces are only used inside the class factory. There is no need to store an entry for an interface in the System Registry.
¨ EXE server's entries in System Registry
A EXE server should have similar basic entries as a DLL server except it is LocalServer32:
HKEY_CLASSES_ROOT\<ProgID>\CLSID = <clsid>
HKEY_CLASSES_ROOT\CLSID\<clsid> = <ProgID>
HKEY_CLASSES_ROOT\CLSID\<clsid>\LocalServer32 = <server full path>
Besides these, it should have the following entries for each interface:
HKEY_CLASSES_ROOT\Interface\<iid>
HKEY_CLASSES_ROOT\Interface\ProxyStubClsid = <proxy/stub dll clsid>
HKEY_CLASSES_ROOT\Interface\ProxyStubClsid32 = <proxy/stub dll clsid>
If you use universal marshaller oleaut32.dll, which reads the registered type library and creates proxy/stub classes on the fly, you should put the CLSID of oleaut32.dll under ProxyStubClsid32, add the following interface entry, plus all the type library entries:
HKEY_CLASSES_ROOT\Interface\TypeLib = <type library libid>
For each interface you invoke, one proxy/stub object is instantiated. Therefore, you must set up a correspondence between an interface and its proxy/stub DLL server, so that when client invoke an interface, the SCM can find the corresponding proxy/stub DLL and create the corresponding proxy/stub object from it.
For this purpose, the System Registry stores the guids of the all interfaces of an EXE server under entry HKCR\Interface. Under each interface guid entry there is a ProxyStubClsid32 entry, which stores the CLSID of the custom proxy/stub DLL server which contains the proxy/stub coclass for that interface, or the universal marshaller oleaut32.dll.
Therefore, if you use the type library marshalling, you must register the type library under HKCR\TypeLib entry and each interface entry.
System Registry editor can be started at command line by typing “regedit”, or from OLE/COM Object Viewer in Visual C++.
2. Unicode Handling
2.1. Definitions of Different Type of Strings
¨ wchar_t
In C++ wchar_t is 16-bit wide character represented by unsigned short:
typedef wchar_t WCHAR; // wc, 16-bit UNICODE character
typedef unsigned short wchar_t;
¨ LPOLESTR
In C++ LPOLESTR is a 16-bit unicode string represented by const unsigned short *:
typedef OLECHAR __RPC_FAR * LPOLESTR;
typedef WCHAR OLECHAR;
¨ LPCOLESTR
In C++ LPOLESTR is a constant 16-bit unicode string represented by const unsigned short *:
typedef const OLECHAR __RPC_FAR *LPCOLESTR;
¨ LPSTR
In C++ LPSTR is 8-bit multi-byte/ANSI string represented by char *
typedef CHAR *LPSTR, *PSTR;
typedef char CHAR;
¨ LPCSTR
In C++ LPCSTR is 8-bit multi-byte/ANSI string represented by const char *:
typedef CONST CHAR *LPCSTR, *PCSTR;
¨ TCHAR
In C++ TCHAR is 8-bit character represented by char:
typedef char TCHAR, *PTCHAR;
2.2. CString <=> Multibyte String
#define m(x) ::MessageBox(NULL, x, "Test", MB_OK | MB_TOPMOST)
CString cstr("Hello the world");
// CString::GetBuffer returns a pointer to its underlying buffer.
LPSTR lpsz = cstr.GetBuffer(cstr.GetLength());
// Both lpsz and cstr can access the underlying buffer.
m(lpsz);
m(cstr);
// You can amend the buffer through cstr.
cstr = "Hi Frank.";
m(lpsz);
// You can also amend the buffer through lpsz, but you can't free it.
lpsz[8] = '!';
m(cstr);
// free(lpsz); // Not allowed! Will cause run-time error!
// After releasing the buffer, lpsz's content becomes undefined
cstr.ReleaseBuffer();
m(cstr);
// If you want to avoid amending the original content of the CString,
// make a copy.
LPSTR lpszCopy = (LPSTR)malloc(strlen(lpsz));
memset(lpszCopy, 0, strlen(lpsz) + 1);
strcpy(lpszCopy, lpsz);
m(lpszCopy);
2.3. CString <=> Wide Character String
void CICPLoginDlg::CStringToWideChar(CString cstr, wchar_t * pwchar, int size)
{
int cstrLen = cstr.GetLength();
ASSERT(cstrLen < MAX_LENGTH);
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, cstr.GetBuffer(cstrLen),
cstrLen, pwchar, size);
cstr.ReleaseBuffer();
}
2.4. Allocating and Freeing BSTRs with API Functions
BSTR stands for basic string. It is a 16-bit Unicode with a prefix telling the count of the bytes in this string, therefore do not need NULL at the end.
When a server creates and passes out a BSTR, the server allocates memory space for the BSTR, while the client should deallocate the memory with a call to API function SysFreeString.
¨ Creating a new BSTR
BSTR bstr1 = SysAllocString(L"Hello world!");
OLECHAR * pOleStr = OLESTR("Good morning!");
BSTR bstr2 = SysAllocString(pOleStr);
¨ Modifying a BSTR
SysReAllocString(&bstr1, L"Good morning!");
¨ Freeing a BSTR
SysFreeString(bstr1);
2.5. BSTR <=> CString
CString cstr(“Hello!”);
BSTR bstr = cstr.AllocSysString();
BSTR bstr = SysAllocString(L"Hello Frank!");
CString cstr(bstr);
2.6. BSTR <=> Multibyte String
Second, use function wcstombs (wide character string to multibyte string) and mbstowcs (multibyte string to wide character string):
BSTR bstr1 = SysAllocString(L"Hellow world!");
char * buf[80];
wcstombs(buf, bstr1, 80);
SysFreeString(bstr1);
sprintf(buf, "Good morning!");
BSTR bstr2;
mbstowcs(bstr2, buf, 80);
2.7. Multibyte String <=> Wide Character String
char * mbstr = "Hello world!";
int len1 = strlen(mbstr);
wchar_t wcbuf[80];
int len2 = sizeof(wcbuf) / sizeof(wchar_t);
::memset(wcbuf, 0, sizeof(wcbuf));
CP_ACP,
MB_PRECOMPOSED,
mbstr,
len1,
wcbuf,
len2);
char buf[300];
sprintf(buf, "wcbuf = %S", wcbuf);
::MessageBox(NULL, buf, NULL, MB_OK);
wchar_t * wcstr = L"Hello world!";
int len3 = wcslen(wcstr);
char mbbuf[300];
int len4 = sizeof(mbbuf);
::memset(mbbuf, 0, len4);
CP_ACP,
WC_COMPOSITECHECK,
wcstr,
len3,
mbbuf,
len4,
NULL,
NULL);
::MessageBox(NULL, mbbuf, NULL, MB_OK);
2.8. Converting with ATL Macros
ATL provides a group of macros to convert between different types. Because one convertion involves a series of temporary variables to hold and swap the string buffers, the space is prepared by macroUSES_CONVERSION, which should be called before any conversion. They are defined in <atlconv.h>.
ANSI to ... | OLE to ... | TCHAR to ... | wchar_t to ... |
A2BSTR | OLE2A | T2A | W2A |
A2COLE | OLE2BSTR | T2BSTR | W2BSTR |
A2CT | OLE2CA | T2CA | W2CA |
A2CW | OLE2CT | T2COLE | W2COLE |
A2OLE | OLE2CW | T2CW | W2CT |
A2T | OLE2T | T2OLE | W2OLE |
A2W | OLE2W | T2W | W2T |
MessageBox(NULL, W2A(bstr), NULL, MB_OK);
2.9. MFC's BSTR Helper Class _bstr_t
MFC provides class _bstr_t to wrap BSTR. _bstr_t's constructor can take many types as input:
_bstr_t( ) throw( );
_bstr_t( const _bstr_t & s1 ) throw( );
_bstr_t( const char * s2 ) throw( _com_error );
_bstr_t( const wchar_t * s3 ) throw( _com_error );
_bstr_t( const _variant_t & var ) throw ( _com_error );
_bstr_t( BSTR bstr, bool fCopy ) throw ( _com_error );
Its assignment operator = is also overloaded for many types:
_bstr_t& operator=( const _bstr_t & s1 ) throw ( );
_bstr_t& operator=( const char * s2 ) throw( _com_error );
_bstr_t& operator=( const wchar_t * s3 ) throw( _com_error );
_bstr_t& operator=( const _variant_t & var ) throw( _com_error );
It has also overloaded +=, +, ! and all comparison operators.
To get a LPTSTR from _bstr_t:
_bstr_t bstrt("Hello!");
LPTSTR lp1 = (LPTSTR)bstrt;
LPTSTR lp2 = bstrt.operator char *();
You can use _bstr_t anywhere expecting BSTR. If you like, you can also explicitly cast _bstr_t to BSTR:
_bstr_t bstrt("Hello!");
BSTR bstr = (BSTR)bstrt;
SysFreeString(bstr1);
_bstr_t object does not need to be deallocated. It is deallocated automatically when it leaves scope.
Class _bstr_t is contained in header file <comdef.h>.
2.10. ATL's BSTR Helper Class CComBSTR
ATL also provides a wrapper class CComBSTR, which is more light than _bstr_t. CComBSTR wraps a BSTR data member m_str. Space for BSTR is allocated in its constructor and deallocated in the destructor.
Its constructor can take LPCOLESTR or LPCSTR as input. It can also designate the size of the buffer.
CComBSTR( int nSize );
CComBSTR( int nSize, LPCOLESTR sz );
CComBSTR( int nSize, LPCSTR sz );
CComBSTR( LPCOLESTR pSrc );
CComBSTR( LPCSTR pSrc );
CComBSTR( const CComBSTR& src );
Therefore you can say:
CComBSTR comBstr(“Hello!”);
To detach m_str from CComBSTR:
BSTR bstr = comBstr.Detach();
To attach a BSTR to a CComBSTR:
BSTR bstr = SysAllocString(L"Hello!");
CComBSTR comBstr;
comBstr.Attach(bstr);
To get a copy of m_str:
BSTR bstr = comBstr.Copy();
To get the address of m_str:
BSTR * pBstr = &comBstr.
To free m_str:
comBstr.Empty();
The overloaded assignment operator takes LPCOLESTR or LPCSTR as parameter. Therefore, to change the value of CComBSTR:
CComBSTR comBstr();
comBstr = L"Good morning!";
CComBSTR can only be casted to CString, which can be than casted to LPCSTR or LPCTSTR:
cstr = (CString)comBstr;
CComBSTR is contained in header file <atlbase.h>, which also contains other wrapper classes.
# by | 2009/11/03 10:05 | ┗▷▶▷ Pragma | 트랙백 | 덧글(0)














