| Obsah | Předchozí - Kutáme trošku hlouběji | Další - A jak dále? |
COM
Znáte to všichni, neobejde se bez něj skoro žádá učebnice jazyka C:
#include <stdio.h>
void main()
{
printf("Hello, world!\n");
}
Udělejme nyní podobný program, ale v COM. Vytvořme nový projekt typu Win32 DLL.
Hello.idl
import "unknwn.idl";
[ local, uuid(B58DF060-EAD9-11d7-BB81-000475BB5B75) ]
interface IHello: IUnknown
{
HRESULT Print();
};
[ uuid(B58DF061-EAD9-11d7-BB81-000475BB5B75), helpstring("Hello COM Library") ]
library LHello
{
importlib("stdole2.tlb");
[ uuid(B58DF062-EAD9-11d7-BB81-000475BB5B75) ]
coclass CHello
{
interface IHello;
}
};
Visual Studio 5.0: Zvolte Project - Settings - Custom Build. Do Build command zadejte midl /header "trash.h" Hello.idl a do Output file(s) zadejte Hello.tlb
Visual Studio 6.0: Zvolte Project - Settings - Hello.idl - MIDL. Odškrtněte MkTypLib compatible, do Output file name zadejte Hello.tlb a do Output header file name zadejte trash.h.
(Vysvětlení: hlavičkový soubor s rozhraním si totiž napíšeme sami)
V IDL souboru jsme definovali rozhraní s atributem local. Ten stanovuje, že komponentu budeme moci používat jen na lokálním počítači. Proto také nebudeme do projektu vkládat proxy soubory Hello_p.c a dlldata.c.
Guid.cpp
#include <objbase.h>
extern const GUID IID_IHello = {0xB58DF060,0xEAD9,0x11d7,{0xBB,0x81,0x00,0x04,0x75,0xBB,0x5B,0x75}};
extern const GUID LIBID_LHello = {0xB58DF061,0xEAD9,0x11d7,{0xBB,0x81,0x00,0x04,0x75,0xBB,0x5B,0x75}};
extern const GUID CLSID_CHello = {0xB58DF062,0xEAD9,0x11d7,{0xBB,0x81,0x00,0x04,0x75,0xBB,0x5B,0x75}};
extern const char* PROGID_CHello = "MyCorporation.Hello";
extern const char* COMMENT_CHello = "Hello World component";
extern const char* SCLSID_CHello = "{B58DF062-EAD9-11D7-BB81-000475BB5B75}";
Řetězcové konstanty využijeme později při psaní rutin pro zápis do registrů.
Hello.h
#include <objbase.h>
extern long g_locks;
extern const GUID IID_IHello;
extern const GUID LIBID_LHello;
extern const GUID CLSID_CHello;
extern const char* PROGID_CHello;
extern const char* COMMENT_CHello;
extern const char* SCLSID_CHello;
class IHello: public IUnknown
{
public:
virtual HRESULT __stdcall Print() = 0;
};
class CHello: public IHello
{
public:
CHello();
virtual ~CHello();
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
virtual HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);
virtual HRESULT __stdcall Print();
protected:
long refcount;
};
class CHelloFactory: public IClassFactory
{
public:
virtual ULONG __stdcall AddRef() { return 0; }
virtual ULONG __stdcall Release() { return 0; }
virtual HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);
virtual HRESULT __stdcall CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv);
virtual HRESULT __stdcall LockServer(BOOL bLock);
};
Globální proměnná g_locks udává počet zámků na danou knihovnu. Používá ji funkce DllCanUnloadNow
Konstanty s GUIDy jsou definovány v souboru Guid.cpp. Abych je mohl používat i jinde, musím je deklarovat jako externí.
CHelloFactory bude existovat pouze v jedné globální instanci, proto se v ní nemusím zabývat správou referencí.
Pro přehlednost budu uvádět kód na více částí, které proložím komentáři.
Hello.cpp
#include <windows.h>
#include "Hello.h"
CHello::CHello()
{
refcount = 0;
g_locks++;
}
CHello::~CHello()
{
g_locks--;
}
Každé instanci CHello odpovídá jeden zámek. Tím máme zaručeno, že knihovna se neodpojí dříve, než všechny její objekty budou smazány.
Hello.cpp (pokračování)
STDMETHODIMP_(ULONG) CHello::AddRef()
{
return InterlockedIncrement(&refcount);
}
STDMETHODIMP_(ULONG) CHello::Release()
{
if (InterlockedDecrement(&refcount) == 0)
{
delete this;
return 0;
}
return refcount;
}
STDMETHODIMP CHello::QueryInterface(REFIID riid, void** ppv)
{
if (riid == IID_IUnknown)
*ppv = (IUnknown*)this;
else if (riid == IID_IHello)
*ppv = (IHello*)this;
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
((IUnknown*)*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP CHello::Print()
{
MessageBox(NULL, "Hello, world!", PROGID_CHello, MB_ICONINFORMATION);
return S_OK;
}
V metodách AddRef a Release používám speciální funkce na přičtení a odečtení jedničky, které zajistí, že přičtení/odečtení a přiřazení do výstupu proběhne bezprostředně za sebou i ve vícevláknových aplikacích.
Při vracení návratové hodnoty v metodě Release je nutné odlišit případ, kdy počet referencí klesne na nulu - v tomto případě totiž smažu objekt a tím i proměnnou refcount.
V QueryInterface je nutné zavolat AddRef, jelikož při úspěšném dotazu se pointer na objekt přiřadí nové proměnné ppv.
Hello.cpp (pokračování)
STDMETHODIMP CHelloFactory::QueryInterface(REFIID riid, void** ppv)
{
if (riid == IID_IUnknown)
*ppv = (IUnknown*)this;
else if (riid == IID_IClassFactory)
*ppv = (IClassFactory*)this;
else
{
ppv = NULL;
return E_NOINTERFACE;
}
((IUnknown*)*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP CHelloFactory::CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv)
{
CHello* pHello;
HRESULT hr;
if (pUnkOuter)
return CLASS_E_NOAGGREGATION;
pHello = new CHello;
if (!pHello)
return E_OUTOFMEMORY;
hr = pHello->QueryInterface(riid, ppv);
if (FAILED(hr))
delete pHello;
return hr;
}
STDMETHODIMP CHelloFactory::LockServer(BOOL bLock)
{
if (bLock)
InterlockedIncrement(&g_locks);
else
InterlockedDecrement(&g_locks);
return S_OK;
}
V CreateInstance nevoláme AddRef - to už udělala metoda QueryInterface. Po zavolání QueryInterface sice existují dvě reference na komponentu (ppv a pHello), avšak pHello je pouze lokální a na konci metody bude zrušená.
Zavoláme-li metodu LockServer, máme zřejmě zaručeno, že knihovna nebude odpojena ani v případě, že neexistují žádné instance jejích objektů.
Exports.cpp
#include <stdio.h>
#include "Hello.h"
HMODULE g_hModule;
long g_locks;
CHelloFactory gHelloFactory;
extern "C" BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
g_hModule = hModule;
}
g_locks = 0;
return TRUE;
}
STDAPI DllCanUnloadNow()
{
return g_locks == 0 ? S_OK : S_FALSE;
}
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv)
{
*ppv = NULL;
if (rclsid == CLSID_CHello)
return gHelloFactory.QueryInterface(riid, ppv);
return CLASS_E_CLASSNOTAVAILABLE;
}
BOOL HelperWriteKey(const char *lpSubKey, const char* val_name, const char* szData)
{
HKEY hk;
RegCreateKey(HKEY_CLASSES_ROOT, lpSubKey, &hk);
RegSetValueEx(hk, val_name, 0, REG_SZ, (const BYTE *)szData, strlen(szData));
RegCloseKey(hk);
return TRUE;
}
STDAPI DllRegisterServer()
{
char szSubKey[MAX_PATH], szBuff[MAX_PATH];
sprintf(szSubKey, "CLSID\\%s", SCLSID_CHello);
HelperWriteKey(szSubKey, NULL, COMMENT_CHello);
sprintf(szSubKey, "CLSID\\%s\\InprocServer32", SCLSID_CHello);
GetModuleFileName(g_hModule, szBuff, sizeof(szBuff));
HelperWriteKey(szSubKey, NULL, szBuff);
sprintf(szSubKey, "CLSID\\%s\\ProgId", SCLSID_CHello);
HelperWriteKey(szSubKey, NULL, PROGID_CHello);
HelperWriteKey(PROGID_CHello, NULL, COMMENT_CHello);
sprintf(szSubKey, "%s\\CLSID", PROGID_CHello);
HelperWriteKey(szSubKey, NULL, SCLSID_CHello);
return S_OK;
}
STDAPI DllUnregisterServer()
{
char szSubKey[MAX_PATH];
sprintf(szSubKey, "CLSID\\%s\\InprocServer32", SCLSID_CHello);
RegDeleteKey(HKEY_CLASSES_ROOT, szSubKey);
sprintf(szSubKey, "CLSID\\%s\\ProgId", SCLSID_CHello);
RegDeleteKey(HKEY_CLASSES_ROOT, szSubKey);
sprintf(szSubKey, "CLSID\\%s", SCLSID_CHello);
RegDeleteKey(HKEY_CLASSES_ROOT, szSubKey);
sprintf(szSubKey, "%s\\bCLSID", PROGID_CHello);
RegDeleteKey(HKEY_CLASSES_ROOT, szSubKey);
RegDeleteKey(HKEY_CLASSES_ROOT, PROGID_CHello);
return S_OK;
}
HelperWriteKey je pouze pomocná (neexportovaná) funkce pro zápis do registrů.
STDAPI je zkratka za extern "C" HRESULT __export __stdcall.
Hello.def DESCRIPTION "Hello World Component" EXPORTS DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE
Informace pro linker, které funkce je třeba exportovat.
V projektu byste měli mít všechny soubory zde uvedené (a žádné jiné). Zkompilujte nyní celý projekt a zaregistrujte jej pomocí regsvr32 <path_to_dll>\Hello.dll.
Nyní ještě napišme jednoduchého klienta. Oproti klientovi z druhé kapitoly v něm využijeme TLB hlavičku místo zdrojové C++ hlavičky. Oproti klientovi z třetí kapitoly musíme pozměnit direktivu #import, jelikož v tomto případě není TLB hlavička součástí dynamické knihovny.
Vytvořte nový projekt typu Win32 Application.
V direktivě #import zadejte skutečnou cestu k souboru Hello.tlb.
HelloClient.cpp
#include <objbase.h>
#import "hello.tlb"
const IID IID_IHello = {0xB58DF060,0xEAD9,0x11d7,{0xBB,0x81,0x00,0x04,0x75,0xBB,0x5B,0x75}};
const CLSID CLSID_CHello = {0xB58DF062,0xEAD9,0x11d7,{0xBB,0x81,0x00,0x04,0x75,0xBB,0x5B,0x75}};
LHello::IHello* hello;
HRESULT hr;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
CoInitialize(NULL);
hr = CoCreateInstance(CLSID_CHello, NULL, CLSCTX_INPROC_SERVER, IID_IHello, (void**)&hello);
if (SUCCEEDED(hr))
{
hello->Print();
hello->Release();
}
CoUninitialize();
return 0;
}

| Obsah | Předchozí - Kutáme trošku hlouběji | Další - A jak dále? | © 2003 Robert A. T. Káldy |