分類: com 2011-12-15 22:53 66人閱讀 收藏 舉報(bào)
第7章
相信有了前六章的知識(shí)積累,學(xué)些以后的章節(jié)將會(huì)很順利,。本章實(shí)現(xiàn)了一個(gè)真正的COM組件,并通過client客戶端來使用這個(gè)組件,。
本章將介紹類廠,,類廠是能夠創(chuàng)建其他組件的組件,CoCreateInstance函數(shù)也是按照一定方法通過類廠來創(chuàng)建組件的,。
CoCreateInstance
CoCreateInstance函數(shù)是COM庫(kù)的函數(shù),,函數(shù)原型如下
- HRESULT __stdcall CoCreateInstance(const CLSID &clsid,
- IUnknown *pIUnkownOuter,
- DWORD dwClsContext,
- const IID &iid,
- void **ppv);
其中前四個(gè)是輸入?yún)?shù),最后一個(gè)是輸出參數(shù),。第一個(gè)參數(shù)clsid是所創(chuàng)建組件的CLSID,,第二個(gè)參數(shù)pIUnkownOuter是聚合組件需要用的,將在第8章介紹,,第三個(gè)參數(shù)dwClsContext是限定所創(chuàng)建組件的執(zhí)行上下文,,第四個(gè)參數(shù)iid是所創(chuàng)建組件的接口的IID,第五個(gè)參數(shù)ppv將返回該接口的指針,。
dwClsContext
dwClsContext可以控制所創(chuàng)建是與客戶在相同的進(jìn)程中運(yùn)行,,還是在不同的進(jìn)程中運(yùn)行,或者是在另外一臺(tái)機(jī)器上運(yùn)行,。此參數(shù)可以是如下值的組合
CLSCTX_INPROC_SERVER 客戶希望創(chuàng)建在同一進(jìn)程中運(yùn)行的組件,,因此組件必須是在DLL中實(shí)現(xiàn)。
CLSCTX_INPROC_HANDLER 客戶希望創(chuàng)建進(jìn)程中處理器,。
CLSCTX_LOCAL_SERVER 客戶希望創(chuàng)建一個(gè)在同一機(jī)器上的另外一個(gè)進(jìn)程中運(yùn)行的組件,。組件是由EXE實(shí)現(xiàn)的。
CLSCTX_REMOTE_SERVER 客戶希望創(chuàng)建一個(gè)在遠(yuǎn)程機(jī)器上運(yùn)行的組件,。分布式COM組件。
在OBJBASE.H中定義了一些上述值的組合,。
CoCreateInstance的具有一定的不靈活性,,解決問題的辦法是使用專門用于創(chuàng)建所需組件的組件,這個(gè)組件就是類廠,。
類廠
某個(gè)特定的類廠可以創(chuàng)建某個(gè)特定CLSID相對(duì)應(yīng)的組件,,客戶可以通過類廠提供的接口來對(duì)組件的創(chuàng)建過程進(jìn)行控制,。客戶使用CoCreateInstance所創(chuàng)建的組件實(shí)際上是通過類廠的IClassFactory創(chuàng)建的,,使用類廠創(chuàng)建組件的步驟是首先創(chuàng)建類廠,,然后使用IClassFactory創(chuàng)建所需的組件。
創(chuàng)建類廠
COM庫(kù)函數(shù)CoGetClassObject創(chuàng)建同某個(gè)CLSID相應(yīng)的類廠,。函數(shù)原型如下
- HRESULT __stdcall CoGetClassObject(const CLSID &clsid,
- DWORD dwClsContext,
- COSERVERINFO *pServerInfo,
- const IID &iid,
- void **ppv);
同CoCreateInstance非常相似,。第一個(gè)參數(shù)const CLSID&待創(chuàng)建組件的CLSID,第二個(gè)參數(shù)DWORD dwClsContext是待創(chuàng)建組件的執(zhí)行上下文,,第三個(gè)參數(shù)COSERVERINFO*用于遠(yuǎn)程組件的訪問,,將在第10章討論,第四個(gè)參數(shù)const IID&是IClassFactory接口的IID,,第五個(gè)參數(shù)返回IClassFactory接口的指針,。
創(chuàng)建組件
IClassFactory
大多數(shù)組件是使用IClassFactory來創(chuàng)建的,原型如下
- interface IClassFactory:IUnknown
- {
- HRESULT __stdcall CreateInstance(IUnkown *pUnknownOuter, const IID &id, void **ppv);
- HRESULT __stdcall LockServer(BOOL bLock);
- };
IClassFactory::CreateInstance函數(shù)的第一個(gè)參數(shù)IUnknown*是組件聚合使用的,,將在第8章介紹,,后兩個(gè)參數(shù)跟CoCreateInstance后兩個(gè)參數(shù)作用相同,將在創(chuàng)建組件的
同時(shí)返回此組件的某個(gè)接口指針,??梢钥吹絀ClassFactory::CreateInstance并沒有接收一個(gè)CLSID參數(shù),這意味著此函數(shù)將只能創(chuàng)建同某個(gè)CLSID——即傳給CoGetClassObject的參數(shù)CLSID相應(yīng)的組件,。
在兩種情況下使用創(chuàng)建類廠再創(chuàng)建組件的方法,,而不是直接使用CoCreateInstance的方法直接創(chuàng)建組件
(1)想使用IClassFactory2來創(chuàng)建組件。IClassFactory2是Microsoft定義的另外一個(gè)接口,,此接口在IClassFactory的基礎(chǔ)上增加了獲取組件接口的許可權(quán)限功能,。
(2)需要?jiǎng)?chuàng)建一個(gè)組件的多個(gè)實(shí)例。這樣只需創(chuàng)建相應(yīng)的類廠一次,,而CoCreateInstance需要為每一個(gè)組件實(shí)例分別創(chuàng)建并釋放相應(yīng)的類廠,。
類廠的特性
(1)類廠將只能給你創(chuàng)建同某個(gè)CLSID相應(yīng)的組件。
(2)與某個(gè)特定CLSID相應(yīng)的類廠是由組件開發(fā)人員來實(shí)現(xiàn)的,。大多數(shù)情況下,,類廠組件包含在它所創(chuàng)建的組件的相同的DLL中。
類廠的創(chuàng)建
客戶通過CoGetClassObject來創(chuàng)建類廠,,這就需要在DLL中實(shí)現(xiàn)一個(gè)特定的函數(shù),,此函數(shù)名為DllGetClassObject,函數(shù)原型如下
- STDAPI DllGetClassObject(const CLSID &clsid, const IID &iid, void **ppv);
函數(shù)的三個(gè)參數(shù)同CoGetClassObject中參數(shù)的意義相同,。
通過類廠來創(chuàng)建組件的示意圖如下,,COM庫(kù)函數(shù)CoGetClassObject將根據(jù)傳入?yún)?shù)CLSID查詢注冊(cè)表,裝載組件所在的DLL庫(kù)。
CFactory只不過是另外一個(gè)組件而已,,它也跟其他組件一樣實(shí)現(xiàn)了IUnknown接口,。
IClassFactory::CreateInstance和DllGetClassObject的實(shí)現(xiàn)是相同的,這兩個(gè)函數(shù)都將創(chuàng)建一個(gè)組件然后向它查詢某個(gè)接口,。
組件的注冊(cè)
實(shí)現(xiàn)組件的DLL中輸出四個(gè)函數(shù),,除了DllGetClassObject外,DllRegisterServer和DllUnregisterServer將用于組件在注冊(cè)表中注冊(cè)和取消注冊(cè)(鏈接的時(shí)候需要鏈接Advapi32.lib),,調(diào)用regsvr32來完成注冊(cè)和取消,。
類廠的復(fù)用
在設(shè)計(jì)類廠和組件的時(shí)候,可以做到只用一個(gè)類廠的實(shí)現(xiàn)來完成所有組件的創(chuàng)建,。將在第9章中實(shí)現(xiàn),。但即使是這樣,類廠CFactory的一個(gè)實(shí)例也僅能創(chuàng)建一個(gè)同某個(gè)CLSID相應(yīng)的組件,。
DLL的卸載
COM庫(kù)中實(shí)現(xiàn)了一個(gè)CoFreeUnusedLibraries的函數(shù),,以釋放那些不再需要使用的DLL庫(kù)所占用的內(nèi)存,由組件的客戶進(jìn)行調(diào)用,。
DllCanUnloadNow函數(shù)
也是在實(shí)現(xiàn)組件的DLL中的輸出函數(shù),,CoFreeUnusedLibraries函數(shù)將調(diào)用DllCanUnloadNow函數(shù),以詢問Dll是否可以被卸載,。代碼中g(shù)_lComponents的作用就是這個(gè),,可以看到IClassFactory::CreateInstance也就是說在創(chuàng)建組件的時(shí)候,組件的構(gòu)造函數(shù)都可以將g_lComponents增大,,組件的析構(gòu)函數(shù)可以將g_lComponents的值減小,。若g_lComponets值為0,CoFreeUnusedLibraries可以將組件的DLL卸載掉,。
LockServer函數(shù)
使用g_lComponents只是對(duì)DLL中的組件進(jìn)行了記數(shù),,另外一個(gè)組件CFactory并沒有記數(shù)。對(duì)類廠的記數(shù)使用了IFactory::LockServer函數(shù),,組件Server內(nèi)部設(shè)置了另外一個(gè)與g_IComponents不同的計(jì)數(shù)值進(jìn)行計(jì)數(shù),。(將在第10章進(jìn)行討論,主要原因是因?yàn)榈?0章將會(huì)講到的進(jìn)程外組件(exe實(shí)現(xiàn)的)的類廠無法像進(jìn)程中的組件(dll實(shí)現(xiàn)的)一樣方便的進(jìn)行記數(shù),。)
本章代碼
組件端:
cmpnt.cpp
-
-
-
-
-
- #include <objbase.h>
- #include "iface.h" //interface declarations
- #include "Registry.h" //Registry helper function
- #include <iostream>
- #include <string>
- using namespace std;
-
-
- void trace(string msg)
- {
- cout<<msg<<endl;
- }
-
-
-
- static HMODULE g_hModule = NULL;
- static long g_lComponent = 0;
- static long g_lServerLocks = 0;
-
-
- const char g_szFriendlyName[] = "InsideCOM Chapter 7 Example";
-
-
- const char g_szVerIndProgID[] = "InsideCOM.Chap07";
-
-
- const char g_szProgID[] = "InsideCOM.Chap07.1";
-
-
- class CA:public IX, public IY
- {
- public:
-
- virtual HRESULT __stdcall QueryInterface(const IID &iid, void **ppv);
- virtual ULONG __stdcall AddRef();
- virtual ULONG __stdcall Release();
- virtual void __stdcall Fx() {cout<<"Fx"<<endl;}
- virtual void __stdcall Fy() {cout<<"Fy"<<endl;}
- CA();
- ~CA();
- protected:
- long m_cRef;
- };
-
- CA::CA()
- {
- m_cRef = 1;
-
- InterlockedIncrement(&g_lComponent);
- }
-
- CA::~CA()
- {
- InterlockedDecrement(&g_lComponent);
- trace("Component:destroy self");
- }
-
-
- HRESULT __stdcall CA::QueryInterface(const IID &iid, void **ppv)
- {
- if(iid == IID_IUnknown)
- {
- *ppv = static_cast<IX*>(this);
- }
- else if(iid == IID_IX)
- {
- *ppv = static_cast<IX*>(this);
- trace("component: return pointer to ix");
- }
- else if(iid == IID_IY)
- {
- *ppv = static_cast<IY*>(this);
- trace("component: return pointer to iy");
- }
- else
- {
- *ppv = NULL;
- return E_NOINTERFACE;
- }
- reinterpret_cast<IUnknown*>(*ppv)->AddRef();
- return S_OK;
- }
-
- ULONG __stdcall CA::AddRef()
- {
- return InterlockedIncrement(&m_cRef);
- }
-
- ULONG __stdcall CA::Release()
- {
- if(InterlockedDecrement(&m_cRef)== 0)
- {
- delete this;
- return 0;
- }
- return m_cRef;
- }
-
-
-
-
- class CFactory:public IClassFactory
- {
- public:
-
- virtual HRESULT __stdcall QueryInterface(const IID &iid, void **ppv);
- virtual ULONG __stdcall AddRef();
- virtual ULONG __stdcall Release();
-
- virtual HRESULT __stdcall CreateInstance(IUnknown *pUnknownOuter, const IID &iid, void **ppv);
- virtual HRESULT __stdcall LockServer(BOOL bLock);
-
- CFactory():m_cRef(1){}
-
- ~CFactory()
- {
- trace("class factory :destory self");
- }
- private:
- long m_cRef;
- };
-
-
-
-
- HRESULT __stdcall CFactory::QueryInterface(const IID &iid, void **ppv)
- {
- if((iid == IID_IUnknown) || (iid == IID_IClassFactory))
- {
- *ppv = static_cast<IClassFactory*>(this);
- }
- else
- {
- *ppv = NULL;
- return E_NOINTERFACE;
- }
- reinterpret_cast<IUnknown*>(*ppv)->AddRef();
- return S_OK;
- }
-
- ULONG __stdcall CFactory::AddRef()
- {
- return InterlockedIncrement(&m_cRef);
- }
-
- ULONG __stdcall CFactory::Release()
- {
- if(InterlockedDecrement(&m_cRef) == 0)
- {
- delete this;
- return 0;
- }
- return m_cRef;
- }
-
-
-
-
- HRESULT __stdcall CFactory::CreateInstance(IUnknown *pUnknownOuter, const IID &iid, void **ppv)
- {
- trace("class factory : create component");
-
- if(pUnknownOuter != NULL)
- {
- return CLASS_E_NOAGGREGATION;
- }
-
- CA *pA = new CA();
- if(pA == NULL)
- {
- return E_OUTOFMEMORY;
- }
-
- HRESULT hr = pA->QueryInterface(iid, ppv);
-
-
- pA->Release();
- return hr;
- }
-
-
- HRESULT __stdcall CFactory::LockServer(BOOL bLock)
- {
- if(bLock)
- {
- InterlockedIncrement(&g_lServerLocks);
- }
- else
- {
- InterlockedDecrement(&g_lServerLocks);
- }
- return S_OK;
- }
-
-
-
-
-
- STDAPI DllGetClassObject(const CLSID &clsid, const IID &iid, void **ppv)
- {
- trace("DllGetClassObeject: create class factory");
- if(clsid != CLSID_Component1)
- {
- return CLASS_E_CLASSNOTAVAILABLE;
- }
-
- CFactory *pFactory = new CFactory;
- if(pFactory == NULL)
- {
- return E_OUTOFMEMORY;
- }
- HRESULT hr = pFactory->QueryInterface(iid, ppv);
- pFactory->Release();
- return hr;
- }
-
- STDAPI DllCanUnloadNow()
- {
- if((g_lComponent == 0) && (g_lServerLocks == 0))
- {
- return S_OK;
- }
- else
- {
- return S_FALSE;
- }
- }
-
-
- STDAPI DllRegisterServer()
- {
- return RegisterServer(g_hModule,
- CLSID_Component1,
- g_szFriendlyName,
- g_szVerIndProgID,
- g_szProgID);
- }
-
-
- STDAPI DllUnregisterServer()
- {
- return UnregisterServer(CLSID_Component1,
- g_szVerIndProgID,
- g_szProgID);
- }
-
- BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD dwReason, void *lpReserved)
- {
- if(dwReason == DLL_PROCESS_ATTACH)
- {
- g_hModule = hModule;
- }
- return TRUE;
- }
-
-
cmpnt.def
- LIBRARY Cmpnt.dll
- DESCRIPTION 'Chapter 7 Example COM Component'
-
- EXPORTS DllGetClassObject @2 PRIVATE
- DllRegisterServer @3 PRIVATE
- DllUnregisterServer @4 PRIVATE
- DllCanUnloadNow @5 PRIVATE
registry.h
- #ifndef _REGISTRY_H_
- #define _REGISTRY_H_
-
- HRESULT RegisterServer(HMODULE hModule,
- const CLSID &clsid,
- const char *szFriendlyName,
- const char *szVerIndProgID,
- const char *szProgID);
- HRESULT UnregisterServer(const CLSID &clsid,
- const char *szVerIndProgID,
- const char *szProgID);
- #endif
registry.cpp
-
-
-
- #include <objbase.h>
- #include <cassert>
- #include "registry.h"
-
-
-
-
-
- BOOL SetKeyAndValue(const char *szKey, const char *szSubKey, const char *szValue);
-
-
- void CLSIDtochar(const CLSID &clsid, char *szClsID, int nLength);
-
-
- LONG RecursiveDeleteKey(HKEY hKeyParent, const char *szKeyChild);
-
- const int CLSID_STRING_SIZE = 39 ;
-
-
-
-
- HRESULT RegisterServer(HMODULE hModule,
- const CLSID& clsid,
- const char* szFriendlyName,
- const char* szVerIndProgID,
- const char* szProgID)
- {
- char szModule[512] ;
- DWORD dwResult =::GetModuleFileName(hModule, szModule, sizeof(szModule)/sizeof(char));
-
- assert(dwResult != 0) ;
-
-
- char szCLSID[CLSID_STRING_SIZE] ;
- CLSIDtochar(clsid, szCLSID, sizeof(szCLSID)) ;
-
-
- char szKey[64] ;
- strcpy(szKey, "CLSID\\") ;
- strcat(szKey, szCLSID) ;
-
-
- SetKeyAndValue(szKey, NULL, szFriendlyName) ;
-
-
- SetKeyAndValue(szKey, "InprocServer32", szModule) ;
-
-
- SetKeyAndValue(szKey, "ProgID", szProgID) ;
-
-
- SetKeyAndValue(szKey, "VersionIndependentProgID",
- szVerIndProgID) ;
-
-
- SetKeyAndValue(szVerIndProgID, NULL, szFriendlyName) ;
- SetKeyAndValue(szVerIndProgID, "CLSID", szCLSID) ;
- SetKeyAndValue(szVerIndProgID, "CurVer", szProgID) ;
-
-
- SetKeyAndValue(szProgID, NULL, szFriendlyName) ;
- SetKeyAndValue(szProgID, "CLSID", szCLSID) ;
-
- return S_OK ;
- }
-
-
-
-
- LONG UnregisterServer(const CLSID& clsid,
- const char* szVerIndProgID,
- const char* szProgID)
- {
-
- char szCLSID[CLSID_STRING_SIZE] ;
- CLSIDtochar(clsid, szCLSID, sizeof(szCLSID)) ;
-
-
- char szKey[64] ;
- strcpy(szKey, "CLSID\\") ;
- strcat(szKey, szCLSID) ;
-
-
- LONG lResult = RecursiveDeleteKey(HKEY_CLASSES_ROOT, szKey) ;
- assert( (lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND) ) ;
-
-
- lResult = RecursiveDeleteKey(HKEY_CLASSES_ROOT, szVerIndProgID) ;
- assert((lResult == ERROR_SUCCESS) ||
- (lResult == ERROR_FILE_NOT_FOUND)) ;
-
-
- lResult = RecursiveDeleteKey(HKEY_CLASSES_ROOT, szProgID) ;
- assert((lResult == ERROR_SUCCESS) ||
- (lResult == ERROR_FILE_NOT_FOUND)) ;
-
- return S_OK ;
- }
-
-
- BOOL SetKeyAndValue(const char *szKey, const char *szSubKey, const char *szValue)
- {
- HKEY hKey;
- char szKeyBuf[1024] ;
-
-
- strcpy(szKeyBuf, szKey) ;
-
-
- if (szSubKey != NULL)
- {
- strcat(szKeyBuf, "\\") ;
- strcat(szKeyBuf, szSubKey ) ;
- }
-
-
- long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT ,
- szKeyBuf,
- 0, NULL, REG_OPTION_NON_VOLATILE,
- KEY_ALL_ACCESS, NULL,
- &hKey, NULL) ;
- if (lResult != ERROR_SUCCESS)
- {
- return FALSE ;
- }
-
-
- if (szValue != NULL)
- {
- RegSetValueEx(hKey, NULL, 0, REG_SZ,
- (BYTE *)szValue,
- strlen(szValue)+1) ;
- }
-
- RegCloseKey(hKey) ;
- return TRUE ;
-
- }
-
-
- LONG RecursiveDeleteKey(HKEY hKeyParent, const char *szKeyChild)
- {
-
- HKEY hKeyChild ;
- LONG lRes = RegOpenKeyEx(hKeyParent, szKeyChild, 0,
- KEY_ALL_ACCESS, &hKeyChild) ;
- if (lRes != ERROR_SUCCESS)
- {
- return lRes ;
- }
-
-
- FILETIME time ;
- char szBuffer[256] ;
- DWORD dwSize = 256 ;
- while (RegEnumKeyEx(hKeyChild, 0, szBuffer, &dwSize, NULL,
- NULL, NULL, &time) == S_OK)
- {
-
- lRes = RecursiveDeleteKey(hKeyChild, szBuffer) ;
- if (lRes != ERROR_SUCCESS)
- {
-
- RegCloseKey(hKeyChild) ;
- return lRes;
- }
- dwSize = 256 ;
- }
-
-
- RegCloseKey(hKeyChild) ;
-
-
- return RegDeleteKey(hKeyParent, szKeyChild) ;
- }
-
- void CLSIDtochar(const CLSID &clsid, char *szClSID, int nLength)
- {
- assert(nLength >= CLSID_STRING_SIZE) ;
-
- LPOLESTR wszCLSID = NULL ;
- HRESULT hr = StringFromCLSID(clsid, &wszCLSID) ;
- assert(SUCCEEDED(hr)) ;
-
-
- wcstombs(szClSID, wszCLSID, nLength);
-
-
- CoTaskMemFree(wszCLSID) ;
- }
iface.h
-
-
- #include <objbase.h>
- interface IX:IUnknown
- {
- virtual void __stdcall Fx() = 0;
- };
-
- interface IY:IUnknown
- {
- virtual void __stdcall Fy() = 0;
- };
-
- interface IZ:IUnknown
- {
- virtual void __stdcall Fz() = 0;
- };
-
- extern const IID IID_IX;
- extern const IID IID_IY;
- extern const IID IID_IZ;
- extern const CLSID CLSID_Component1;
-
guids.cpp
-
-
-
- #include <objbase.h>
-
-
- extern const IID IID_IX =
- {0x32bb8320, 0xb41b, 0x11cf,
- {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ;
-
-
- extern const IID IID_IY =
- {0x32bb8321, 0xb41b, 0x11cf,
- {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ;
-
-
- extern const IID IID_IZ =
- {0x32bb8322, 0xb41b, 0x11cf,
- {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ;
-
-
- extern const CLSID CLSID_Component1 =
- {0x0c092c21, 0x882c, 0x11cf,
- {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ;
-
-
客戶端
clients.cpp
-
-
-
-
- #include <objbase.h>
- #include "iface.h"
- #include <iostream>
- #include <string>
- using namespace std;
-
- void trace(string msg)
- {
- cout<<msg<<endl;
- }
-
- int main(void)
- {
- CoInitialize(NULL);
- trace("client:call CoCreateInstance to create component and get interface ix");
- IX *pIx = NULL;
- HRESULT hr = ::CoCreateInstance(CLSID_Component1, NULL, CLSCTX_INPROC_SERVER, IID_IX, (void**)&pIx);
- if(SUCCEEDED(hr))
- {
- trace("client:Succeeded getting IX");
- pIx->Fx();
- trace("client:Ask for interface IY");
- IY *pIy = NULL;
- hr = pIx->QueryInterface(IID_IY, (void**)&pIy);
- if(SUCCEEDED(hr))
- {
- trace("client:Succeeded getting IY");
- pIy->Fy();
- pIy->Release();
- trace("client:Release IY interface");
- }
- else
- {
- trace("client:Could not get interface IY");
- }
- trace("client:Ask for interface IZ");
- IZ *pIz = NULL;
- hr = pIx->QueryInterface(IID_IZ, (void**)&pIz);
- if(SUCCEEDED(hr))
- {
- trace("client:Succeeded getting IZ");
- pIz->Fz();
- pIz->Release();
- trace("client:Release IZ interface");
- }
- else
- {
- trace("client:Could not get interface IZ");
- }
- trace("client:Release IX interface");
- pIx->Release();
- }
- else
- {
- cout<<"Client: Could not create component.hr ="<<hex<<hr<<endl;
- }
- CoUninitialize();
- return 0;
- }
運(yùn)行結(jié)果
|