6 folii na stronÄ
6 folii na stronÄ
6 folii na stronÄ
You also want an ePaper? Increase the reach of your titles
YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM – OPRACOWANIE: JAROSŁAW FRANCIK<br />
// LISTING #1–PIERWSZE PODEJŚCIE<br />
// LISTING #2 – DRUGIE PODEJŚCIE<br />
#include <br />
#include <br />
// interfejs A<br />
interface IA<br />
{<br />
virtual void __stdcall fa() = 0;<br />
};<br />
// interfejs B<br />
interface IB<br />
{<br />
virtual void __stdcall fb() = 0;<br />
};<br />
// Składnik<br />
class CMyComp : public IA, public IB<br />
{<br />
// Implementacja interfejsu A<br />
virtual void __stdcall fa();<br />
// Implementacja interfejsu B<br />
virtual void __stdcall fb();<br />
};<br />
void CMyComp::fa()<br />
{<br />
cout
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM – OPRACOWANIE: JAROSŁAW FRANCIK<br />
///////////////////////////////<br />
// Aplikacja kliencka<br />
#include "interface.h"<br />
void main()<br />
{ // inicjalizacja składnika<br />
IUnknown *pUnknown = CreateInstance();<br />
IUnknown *CreateInstance()<br />
{<br />
IUnknown *p = (IA*)new CMyComp;<br />
p->AddRef();<br />
return p;<br />
}<br />
}<br />
// chcemy skorzystać z interfejsu IA<br />
IA *pIA = NULL;<br />
HRESULT hr = pUnknown->QueryInterface(IID_IA, (void**)&pIA);<br />
if (SUCCEEDED(hr))<br />
{ pIA->fa();<br />
pIA->Release();<br />
}<br />
// chcemy skorzystać z interfejsu IB<br />
IB *pIB = NULL;<br />
hr = pUnknown->QueryInterface(IID_IB, (void**)&pIB);<br />
if (SUCCEEDED(hr))<br />
{ pIB->fb();<br />
pIB->Release();<br />
}<br />
pUnknown->Release(); // delete pUnknown;<br />
///////////////////////////////<br />
// Moduł składnika<br />
#include "interface.h"<br />
#include <br />
// Klasa składnika<br />
class CMyComp : public IA, public IB<br />
{ // Implementacja interfejsu IUnknown<br />
virtual HRESULT _stdcall QueryInterface(REFIID iid, void **ppv);<br />
virtual ULONG _stdcall AddRef();<br />
virtual ULONG _stdcall Release();<br />
// Implementacja interfejsu A<br />
virtual void __stdcall fa() {cout
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM – OPRACOWANIE: JAROSŁAW FRANCIK<br />
///////////////////////////////<br />
// Aplikacja kliencka - korzystająca z serwera COM<br />
// LISTING #3 – TRZECIE PODEJŚCIE<br />
///////////////////////////////<br />
// Plik <strong>na</strong>główkowy interface.h<br />
#include <br />
// interfejs A<br />
interface IA : public IUnknown<br />
{<br />
virtual void __stdcall fa() = 0;<br />
};<br />
// interfejs B<br />
interface IB : public IUnknown<br />
{<br />
virtual void __stdcall fb() = 0;<br />
};<br />
// deklaracje identyfikatorów IID<br />
extern const IID IID_IA;<br />
extern const IID IID_IB;<br />
#include "interface.h"<br />
void main()<br />
{<br />
// inicjalizacja podsystemu COM<br />
CoInitialize(NULL);<br />
// inicjalizacja składnika<br />
IUnknown *pUnknown = NULL;<br />
HRESULT hr = CoCreateInstance(CLSID_Component, NULL,<br />
CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pUnknown);<br />
if (SUCCEEDED(hr))<br />
{ // chcemy skorzystać z interfejsu IA<br />
IA *pIA = NULL;<br />
hr = pUnknown->QueryInterface(IID_IA, (void**)&pIA);<br />
if (SUCCEEDED(hr))<br />
{<br />
pIA->fa();<br />
pIA->Release();<br />
}<br />
///////////////////////////////<br />
// Definicja identyfikatorów IID<br />
// {59F1DE0D-8843-4a57-82CE-D6DBA3D28D28}<br />
static const GUID CLSID_Component =<br />
{ 0x59f1de0d, 0x8843, 0x4a57, { 0x82, 0xce, 0xd6, 0xdb,<br />
0xa3, 0xd2, 0x8d, 0x28 } };<br />
// {033E7EC7-50FA-404c-9C59-4CBA8892F054}<br />
static const GUID IID_IA =<br />
{ 0x33e7ec7, 0x50fa, 0x404c, { 0x9c, 0x59, 0x4c, 0xba, 0x88,<br />
0x92, 0xf0, 0x54 } };<br />
// {FD854FCC-6AE2-4876-BB60-DC276E5DFC68}<br />
static const GUID IID_IB =<br />
{ 0xfd854fcc, 0x6ae2, 0x4876, { 0xbb, 0x60, 0xdc, 0x27,<br />
0x6e, 0x5d, 0xfc, 0x68 } };<br />
}<br />
}<br />
// dalej jak w listingu #2<br />
pUnknown->Release();<br />
///////////////////////////////<br />
// Moduł składnika (serwer)<br />
#include "interface.h"<br />
#include <br />
#include "registry.h"<br />
// <strong>na</strong>rzędzia do rejestru systemowego...<br />
///////////////////////////////////////////////////////////<br />
// Zmienne globalne<br />
static HMODULE g_hModule = NULL; // uchwyt modułu DLL<br />
static long g_cComponents = 0 ; // Licznik aktywnych składników<br />
static long g_cServerLocks = 0 ; // Licznik dla LockServer<br />
// Dane do rejestru systemowego<br />
const char g_szProgID[] = "COMLecture.Approach3.1";<br />
const char g_szFriendlyName[] = "Wykład z COM, podejście 3";<br />
const char g_szVerIndProgID[] = "COMLecture.Approach3";<br />
3
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM – OPRACOWANIE: JAROSŁAW FRANCIK<br />
///////////////////////////////////////////////////////////<br />
///////////////////////////////////////////////////////////<br />
// Klasa składnika<br />
// Class Factory<br />
class CMyComp : public IA, public IB<br />
{<br />
public:<br />
// Implementacja interfejsu IUnknown<br />
virtual HRESULT _stdcall QueryInterface(REFIID iid, void **ppv);<br />
virtual ULONG _stdcall AddRef();<br />
virtual ULONG _stdcall Release();<br />
// Implementacja interfejsu A<br />
virtual void __stdcall fa() {cout
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM – OPRACOWANIE: JAROSŁAW FRANCIK<br />
///////////////////////////////////////////////////////////<br />
///////////////////////////////////////////////////////////<br />
// Class Factory – interfejs IClassFactory<br />
// Funkcje eksportowane!<br />
HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter,<br />
const IID& iid, void** ppv)<br />
{<br />
if (pUnknownOuter != NULL)<br />
return CLASS_E_NOAGGREGATION;<br />
}<br />
// Utworzenie składnika<br />
CMyComp* pMyComp = new CMyComp;<br />
if (!pMyComp)<br />
return E_OUTOFMEMORY;<br />
// Utworzenie żądanego interfejsu<br />
HRESULT hr = pMyComp->QueryInterface(iid, ppv);<br />
pMyComp->Release();<br />
return hr;<br />
HRESULT __stdcall CFactory::LockServer(BOOL bLock)<br />
{<br />
if (bLock)<br />
InterlockedIncrement(&g_cServerLocks) ;<br />
else<br />
InterlockedDecrement(&g_cServerLocks) ;<br />
return S_OK ;<br />
}<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Inicjalizacja DLL<br />
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,<br />
LPVOID lpReserved)<br />
{<br />
if (ul_reason_for_call == DLL_PROCESS_ATTACH)<br />
g_hModule = hModule;<br />
return TRUE ;<br />
}<br />
STDAPI DllCanUnloadNow()<br />
{ if ((g_cComponents == 0) && (g_cServerLocks == 0))<br />
return S_OK;<br />
else<br />
return S_FALSE;<br />
}<br />
STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv)<br />
{<br />
// Czy możemy utworzyć taki składnik?<br />
if (clsid != CLSID_Component)<br />
return CLASS_E_CLASSNOTAVAILABLE ;<br />
}<br />
// Utwórz fabrykę klas<br />
CFactory* pFactory = new CFactory;<br />
if (pFactory == NULL)<br />
return E_OUTOFMEMORY;<br />
// Zwróć żądany interfejs<br />
HRESULT hr = pFactory->QueryInterface(iid, ppv);<br />
pFactory->Release();<br />
return hr;<br />
STDAPI DllRegisterServer()<br />
{ return RegisterServer(g_hModule, CLSID_Component,<br />
g_szFriendlyName, g_szVerIndProgID, g_szProgID);<br />
}<br />
STDAPI DllUnregisterServer()<br />
{ return UnregisterServer(CLSID_Component, g_szVerIndProgID,<br />
g_szProgID);<br />
}<br />
///////////////////////////////<br />
// Plik COMPONENT.DEF<br />
LIBRARY<br />
Component.dll<br />
DESCRIPTION 'Przykład do wykładu COM, (C) Jarosław Francik 2001'<br />
EXPORTS<br />
DllGetClassObject @2 PRIVATE<br />
DllCanUnloadNow @3 PRIVATE<br />
DllRegisterServer @4 PRIVATE<br />
DllUnregisterServer @5 PRIVATE<br />
5
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM – OPRACOWANIE: JAROSŁAW FRANCIK<br />
///////////////////////////////<br />
// Aplikacja kliencka - korzystająca z serwera COM<br />
// LISTING #4 – CZWARTE PODEJŚCIE (SERWER EXE)<br />
///////////////////////////////<br />
// Specyfikacja mytypes.IDL<br />
import "unknwn.idl";<br />
// Interface IA<br />
[ object,<br />
uuid(B5DB3493-9925-4633-B7DB-30DA5E75D347),<br />
helpstring("IA Interface"),<br />
pointer_default(unique)<br />
]<br />
interface IA : IUnknown<br />
{ HRESULT fa();<br />
};<br />
// Interface IB<br />
[ object,<br />
uuid(10AE43A2-2D3F-48a9-8ED3-D3F1F21CC898),<br />
helpstring("IB Interface"),<br />
pointer_default(unique)<br />
]<br />
interface IB : IUnknown<br />
{ HRESULT fb();<br />
};<br />
// Biblioteka typów + clsid składników<br />
[ uuid(3221CCFF-68B2-442b-AFDC-FA9C767F1FC4),<br />
version(1.0),<br />
helpstring("Biblioteka typów Approach3a")<br />
]<br />
library MyTypeLib<br />
{ importlib("stdole32.tlb") ;<br />
[<br />
uuid(E843265D-B374-4d56-A980-0EC5E9188B47),<br />
helpstring("Klasa komponentu Approach3a")<br />
]<br />
coclass Component<br />
{<br />
[default] interface IA;<br />
interface IB;<br />
};<br />
} ;<br />
#include "..\\mytypes.h"<br />
#include "..\\mytypes_i.c"<br />
void main()<br />
{<br />
// inicjalizacja podsystemu COM<br />
CoInitialize(NULL);<br />
// inicjalizacja składnika<br />
IUnknown *pUnknown = NULL;<br />
HRESULT hr = CoCreateInstance(CLSID_Component, NULL,<br />
CLSCTX_LOCAL_SERVER, IID_IUnknown, (void**)&pUnknown);<br />
///////////////////////////////<br />
// Moduł proxy<br />
// zawiera wyłącznie pliki utworzone przez midl:<br />
// dlldata.c, abc_i.c, abc_p.c,<br />
// linkowane z rpcndr.lib, rpcns4.lib i rpcrt4.lib<br />
// Oto plik PROXY.DEF:<br />
LIBRARY<br />
proxy.dll<br />
DESCRIPTION 'Przykład do wykładu COM, (C) Jarosław Francik 2001'<br />
EXPORTS<br />
DllGetClassObject @1 PRIVATE<br />
DllCanUnloadNow @2 PRIVATE<br />
GetProxyDllInfo @2 PRIVATE<br />
DllRegisterServer @4 PRIVATE<br />
DllUnregisterServer @5 PRIVATE<br />
///////////////////////////////<br />
// Moduł składnika (serwer)<br />
#include "..\\mytypes.h"<br />
#include "..\\mytypes_i.c"<br />
#include <br />
#include <br />
#include "registry.h"<br />
// <strong>na</strong>rzędzia do rejestru systemowego...<br />
///////////////////////////////////////////////////////////<br />
// Zmienne globalne<br />
// usunieto zmienną g_hModule<br />
// po<strong>na</strong>dto jak w podejściu Trzecim<br />
6
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM – OPRACOWANIE: JAROSŁAW FRANCIK<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Klasa składnika<br />
class CMyComp : public IA, public IB<br />
{<br />
public:<br />
// Implementacja interfejsu IUnknown jak w Podejściu Trzecim<br />
// Implementacja interfejsu A<br />
virtual HRESULT __stdcall fa()<br />
{ MessageBox(0, "Jestem fa", "Jestem fa", 0); return S_OK; }<br />
// Implementacja interfejsu B<br />
virtual HRESULT __stdcall fb()<br />
{ MessageBox(0, "Jestem fb", "Jestem fb", 0); return S_OK; }<br />
public:<br />
CMyComp() : m_nRef(1) { InterlockedIncrement(&g_cComponents); }<br />
~CMyComp() { InterlockedDecrement(&g_cComponents);<br />
if (g_cComponents == 0 && g_cServerLocks == 0)<br />
PostQuitMessage(0); }<br />
private:<br />
long m_nRef;<br />
};<br />
// Implementacja klasy CMyComp – jak w Podejściu Trzecim<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Class Factory<br />
// Deklaracja i implementacja fabryki klas jak w Podejściu Trzecim;<br />
// jedy<strong>na</strong> różnica dotyczy poniższej funkcji:<br />
HRESULT __stdcall CFactory::LockServer(BOOL bLock)<br />
{<br />
if (bLock)<br />
InterlockedIncrement(&g_cServerLocks);<br />
else<br />
InterlockedDecrement(&g_cServerLocks);<br />
}<br />
if (g_cComponents == 0 && g_cServerLocks == 0)<br />
PostQuitMessage(0);<br />
return S_OK ;<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Część aplikacyj<strong>na</strong> + obsługa COM<br />
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);<br />
int APIENTRY WinMain(HINSTANCE hInstance,<br />
HINSTANCE hPrevInstance,<br />
LPSTR lpCmdLine,<br />
int nCmdShow)<br />
{ /////////////////////////////////////////////////////////////<br />
// Inicjalizacja COM<br />
// flaga określająca tryb pracy<br />
bool bEmbedding = false;<br />
// Inicjalizacja COM<br />
HRESULT hr = CoInitialize(NULL) ;<br />
if (FAILED(hr))<br />
return 0 ;<br />
// Odczyt linii wywołania<br />
char szTokens[] = "-/";<br />
char* szToken = strtok(lpCmdLine, szTokens) ;<br />
while (szToken != NULL)<br />
{<br />
if (_stricmp(szToken, "UnregServer") == 0)<br />
{<br />
UnregisterServer(FALSE, CLSID_Component,<br />
g_szVerIndProgID, g_szProgID);<br />
CoUninitialize();<br />
return 0;<br />
}<br />
else if (_stricmp(szToken, "RegServer") == 0)<br />
{<br />
RegisterServer(FALSE, hInstance, CLSID_Component,<br />
g_szFriendlyName, g_szVerIndProgID, g_szProgID);<br />
CoUninitialize();<br />
return 0;<br />
}<br />
else if (_stricmp(szToken, "Embedding") == 0)<br />
{<br />
bEmbedding = true;<br />
break ;<br />
}<br />
szToken = strtok(NULL, szTokens);<br />
}<br />
7
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM – OPRACOWANIE: JAROSŁAW FRANCIK<br />
// Rejestracja fabryki klas<br />
DWORD dRegister;<br />
CFactory *pFactory = new CFactory;<br />
hr = ::CoRegisterClassObject(CLSID_Component, pFactory,<br />
CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dRegister);<br />
if (FAILED(hr))<br />
{<br />
pFactory->Release() ;<br />
return FALSE ;<br />
}<br />
//////////////////////////////////////////////////////////////<br />
// Standardowa część aplikacji<br />
if (!bEmbedding)<br />
{<br />
// Rejestracja klasy ok<strong>na</strong><br />
WNDCLASS wc;<br />
memset(&wc, 0, sizeof(wc));<br />
wc.lpfnWndProc = (WNDPROC)WndProc;<br />
wc.hInstance = hInstance;<br />
wc.hCursor = LoadCursor(NULL, IDC_ARROW);<br />
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);<br />
wc.lpszClassName = "my_class1";<br />
RegisterClass(&wc);<br />
// Utworzenie ok<strong>na</strong><br />
HWND hWnd;<br />
hWnd = CreateWindow("my_class1", "Tytuł",<br />
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,<br />
CW_USEDEFAULT, CW_USEDEFAULT,<br />
NULL, NULL, hInstance, NULL);<br />
if (!hWnd)<br />
return FALSE;<br />
ShowWindow(hWnd, nCmdShow);<br />
UpdateWindow(hWnd);<br />
}<br />
//////////////////////////////////////////////////////////////<br />
// Końcowa deinicjalizacja systemu COM!<br />
CoRevokeClassObject(dRegister);<br />
CoUninitialize();<br />
return msg.wParam;<br />
//////////////////////////////////////////<br />
// Funkcja okienkowa<br />
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,<br />
WPARAM wParam, LPARAM lParam)<br />
{<br />
switch (message)<br />
{<br />
case WM_DESTROY:<br />
// nie pozwala zamknąć aplikacji,<br />
// dopóki klienci korzystają z serwera<br />
if (g_cComponents == 0 && g_cServerLocks == 0)<br />
PostQuitMessage(0);<br />
break ;<br />
}<br />
case WM_CLOSE:<br />
// znosi blokadę <strong>na</strong> czas używania interfejsu użytkownika<br />
::InterlockedDecrement(&g_cServerLocks);<br />
return (DefWindowProc(hWnd, message, wParam, lParam)) ;<br />
default:<br />
return DefWindowProc(hWnd, message, wParam, lParam);<br />
}<br />
return 0;<br />
// Blokuje serwer <strong>na</strong> czas używania interfejsu użytkownika<br />
InterlockedIncrement(&g_cServerLocks);<br />
}<br />
// Pętla komunikatów<br />
MSG msg;<br />
while (GetMessage(&msg, NULL, 0, 0))<br />
{<br />
TranslateMessage(&msg);<br />
DispatchMessage(&msg);<br />
}<br />
8
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM – OPRACOWANIE: JAROSŁAW FRANCIK<br />
///////////////////////////////<br />
// Moduł składnika (serwer)<br />
// LISTING #5 – PIĄTE PODEJŚCIE (DISPINTERFEJSY)<br />
///////////////////////////////<br />
// Specyfikacja mytypes.IDL<br />
import "unknwn.idl";<br />
// Interface IA<br />
[ object,<br />
uuid(B5DB3493-9925-4633-B7DB-30DA5E75D347),<br />
helpstring("IA Interface"),<br />
pointer_default(unique),<br />
dual,<br />
oleautomation<br />
]<br />
interface IA : IDispatch<br />
{ HRESULT fa();<br />
};<br />
// reszta bez zmian<br />
///////////////////////////////<br />
// Aplikacja kliencka - korzystająca z interfejsu vtable<br />
// BEZ ŻADNYCH ZMIAN<br />
///////////////////////////////<br />
// Moduł proxy – bez zmian<br />
// rozszerzenie klasy CMyComp<br />
// obejmuje implementację interfejsu IDispatch<br />
class CMyComp : public IA, public IB<br />
{<br />
public:<br />
// Implementacja interfejsu IDispatch<br />
virtual HRESULT __stdcall GetTypeInfoCount(<br />
UINT __RPC_FAR *pctinfo);<br />
virtual HRESULT __stdcall GetTypeInfo(<br />
UINT iTInfo, LCID lcid,<br />
ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);<br />
virtual HRESULT __stdcall GetIDsOfNames(<br />
REFIID riid,<br />
LPOLESTR __RPC_FAR *rgszNames,<br />
UINT cNames,<br />
LCID lcid,<br />
DISPID __RPC_FAR *rgDispId);<br />
virtual HRESULT __stdcall Invoke(<br />
DISPID dispIdMember,<br />
REFIID riid,<br />
LCID lcid,<br />
WORD wFlags,<br />
DISPPARAMS __RPC_FAR *pDispParams,<br />
VARIANT __RPC_FAR *pVarResult,<br />
EXCEPINFO __RPC_FAR *pExcepInfo,<br />
UINT __RPC_FAR *puArgErr);<br />
protected:<br />
ITypeInfo *m_pITypeInfo;<br />
HRESULT InitTypeInfo();<br />
public:<br />
// rzeczy pomocne<br />
// uzupełnienie konstruktora:<br />
CMyComp() : m_nRef(1)<br />
{ InterlockedIncrement(&g_cComponents);<br />
InitTypeInfo(); }<br />
};<br />
// RESZTA BEZ ZMIAN<br />
9
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM – OPRACOWANIE: JAROSŁAW FRANCIK<br />
// Funkcja tworząca obiekt informacji o typach<br />
// Implementacja interfejsu IDispatch opiera się <strong>na</strong><br />
// wykorzystaniu funkcjo<strong>na</strong>lności obiektu informacji o typach<br />
HRESULT __stdcall CMyComp::GetTypeInfoCount(UINT __RPC_FAR *pctinfo)<br />
{ *pctinfo = 1;<br />
return S_OK;<br />
}<br />
HRESULT __stdcall CMyComp::GetTypeInfo(UINT iTInfo, LCID lcid,<br />
ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo)<br />
{<br />
*ppTInfo = NULL;<br />
if (iTInfo != 0)<br />
return DISP_E_BADINDEX;<br />
m_pITypeInfo->AddRef();<br />
*ppTInfo = m_pITypeInfo;<br />
return S_OK;<br />
}<br />
HRESULT __stdcall CMyComp::GetIDsOfNames(REFIID riid,<br />
LPOLESTR __RPC_FAR *rgszNames,<br />
UINT cNames,<br />
LCID lcid,<br />
DISPID __RPC_FAR *rgDispId)<br />
{<br />
if (riid != IID_NULL)<br />
return DISP_E_UNKNOWNINTERFACE;<br />
HRESULT hr;<br />
hr = m_pITypeInfo->GetIDsOfNames(rgszNames, cNames, rgDispId);<br />
return hr;<br />
}<br />
HRESULT __stdcall CMyComp::Invoke(<br />
DISPID dispIdMember,<br />
REFIID riid,<br />
LCID lcid,<br />
WORD wFlags,<br />
DISPPARAMS __RPC_FAR *pDispParams,<br />
VARIANT __RPC_FAR *pVarResult,<br />
EXCEPINFO __RPC_FAR *pExcepInfo,<br />
UINT __RPC_FAR *puArgErr)<br />
{<br />
if (riid != IID_NULL)<br />
return DISP_E_UNKNOWNINTERFACE;<br />
HRESULT hr = m_pITypeInfo->Invoke((IA*)this, dispIdMember,<br />
wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);<br />
return hr;<br />
}<br />
HRESULT CMyComp::InitTypeInfo()<br />
{<br />
HRESULT hr;<br />
}<br />
// spróbuj załadować bibliotekę typów z rejestru systemowego<br />
ITypeLib *pTypeLib = NULL;<br />
hr = LoadRegTypeLib(LIBID_MyTypeLib, 1, 0, 0x00, &pTypeLib);<br />
if (FAILED(hr))<br />
{<br />
// załaduj z pliku<br />
// ustal <strong>na</strong>zwę pliku exe i podziel ją <strong>na</strong> czynniki pierwsze<br />
char szModule[512] ;<br />
DWORD dwResult =<br />
::GetModuleFileName(g_hInstance, szModule, 512);<br />
char szDrive[_MAX_DRIVE];<br />
char szDir[_MAX_DIR];<br />
_splitpath(szModule, szDrive, szDir, NULL, NULL);<br />
// Określ <strong>na</strong>zwę pliku TLB<br />
char szTypeLibFullName[_MAX_PATH];<br />
sprintf(szTypeLibFullName, "%s%s%s", szDrive, szDir,<br />
"mytypes.tlb");<br />
// convert to wide char<br />
wchar_t wszTypeLibFullName[_MAX_PATH];<br />
mbstowcs(wszTypeLibFullName, szTypeLibFullName, _MAX_PATH);<br />
hr = LoadTypeLib(wszTypeLibFullName, &pTypeLib);<br />
if (FAILED(hr))<br />
return hr;<br />
// <strong>na</strong> wszelki wypadek zarejestruj<br />
hr = RegisterTypeLib(pTypeLib, wszTypeLibFullName, NULL);<br />
if (FAILED(hr))<br />
return hr;<br />
}<br />
// pobierz TypeInfo dla IA<br />
m_pITypeInfo = NULL;<br />
hr = pTypeLib->GetTypeInfoOfGuid(IID_IA, &m_pITypeInfo);<br />
pTypeLib->Release();<br />
if (FAILED(hr))<br />
return hr;<br />
return S_OK;<br />
// Pod<strong>na</strong>dto uzupełniono CMyComp::QueryInterface tak, by zwracała<br />
// w razie potrzeby wskaźnik <strong>na</strong> IDispatch<br />
10
// Ater<strong>na</strong>tyw<strong>na</strong> Aplikacja kliencka<br />
// Aplikacja korzysta z dispinterfejsu<br />
#include <br />
#include <br />
OD KLASY C++ DO SERWERA COM: PROGRAMY PRZYKŁADOWE DO WYKŁADU PROGRAMOWANIE SKŁADNIKOWE W MODELU COM – OPRACOWANIE: JAROSŁAW FRANCIK<br />
////////////////////////////////////////<br />
// Klient w Visual Basicu:<br />
Dim x As Object<br />
Set x = CreateObject(COMLecture.Approach5”)<br />
x.fa<br />
void main()<br />
{<br />
// inicjalizacja podsystemu COM<br />
CoInitialize(NULL);<br />
wchar_t progid[] = L"COMLecture.Approach5";<br />
CLSID clsid;<br />
CLSIDFromProgID(progid, &clsid);<br />
/////////////////////////////////////////////<br />
// Klient w MFC (po utworzeniu klasy “wrapper”<br />
IA ia;<br />
ia.CreateDispatch("COMLecture.Approach5");<br />
ia.fa();<br />
IDispatch *pIDispatch = NULL;<br />
HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER,<br />
IID_IDispatch, (void**)&pIDispatch);<br />
assert(SUCCEEDED(hr));<br />
DISPID dispid;<br />
OLECHAR *<strong>na</strong>me = L"fa";<br />
hr = pIDispatch->GetIDsOfNames(IID_NULL, &<strong>na</strong>me, 1,<br />
GetUserDefaultLCID(), &dispid);<br />
assert(SUCCEEDED(hr));<br />
VARIANTARG varg;<br />
VariantInit(&varg);<br />
varg.vt = VT_EMPTY;<br />
}<br />
DISPPARAMS param;<br />
param.cArgs = 0;<br />
param.rgvarg = &varg;<br />
param.cNamedArgs = 0;<br />
param.cArgs = NULL;<br />
hr = pIDispatch->Invoke(dispid, IID_NULL, GetUserDefaultLCID(),<br />
DISPATCH_METHOD, ¶m, NULL, NULL, NULL);<br />
assert(SUCCEEDED(hr));<br />
11
Wstęp do programowania<br />
składnikowego<br />
Programowanie składnikowe<br />
Złota zasada:<br />
Interfejsem jest wszystko<br />
CZĘŚĆ 1:<br />
składniki i interfejsy<br />
Interfejsy lepiej oddają funkcjo<strong>na</strong>lność<br />
obiektów niż dziedziczenie<br />
Jarosław Francik<br />
Założenia:<br />
1. Programowanie składnikowe to<br />
sposób programowania (filozofia)<br />
2. COM to tylko jed<strong>na</strong> ze specyfikacji<br />
wspierających programowanie składnikowe<br />
3. W ramach wykładu korzystamy ze zwykłego<br />
C++ by tworzyć oprogramowanie składnikowe<br />
4. W miarę potrzeb będziemy wprowadzać<br />
elementy COM<br />
Pierwsze podejście...<br />
Interfejs = klasa abstrakcyj<strong>na</strong><br />
Składnik = klasa implementująca<br />
abstrakcyjny interfejs<br />
(dziedzicząca po klasie<br />
abstrakcyjnej)<br />
Wiele interfejsów = dziedz. wielobazowe<br />
#include <br />
// interfejs A<br />
class IA<br />
{<br />
public:<br />
virtual void fa() = 0;<br />
};<br />
// interfejs B<br />
class IB<br />
{<br />
public:<br />
virtual void fb() = 0;<br />
};<br />
// Składnik<br />
class CMyComp : public IA, public IB<br />
{<br />
// Implementacja interfejsu A<br />
virtual void fa();<br />
void CMyComp::fa()<br />
{<br />
cout
Pierwsze podejście...<br />
Specyfikacja COM nie wymusza<br />
stosowania klas C++,<br />
jed<strong>na</strong>k:<br />
bi<strong>na</strong>rny format tablicy funkcji wirtualnych<br />
C++ jest zgodny ze specyfikacją COM<br />
Pierwsze podejście...<br />
Wprowadzamy ułatwienia<br />
i wymogi COM:<br />
include <br />
interface (słowo kluczowe C++)<br />
__stdcall – konwencja wywołania<br />
Pierwsze podejście...<br />
[ listing #1 ]<br />
Słaby punkt:<br />
wykorzystujemy bezpośrednio wskaźnik<br />
<strong>na</strong> klasę komponentu<br />
w ten sposób zbyt mocno wiążemy<br />
klienta z implementacją składnika<br />
Rozwiązanie:<br />
stworzyć odrębną funkcję CreateInstance,<br />
której implementacja jest wewnętrznym<br />
szczegółem modułu składnika<br />
Lepsze podejście...<br />
Publicznie widzimy:<br />
interfejsy<br />
deklarację funkcji CreateInstance<br />
<strong>na</strong>rzędzie do uzyskiwania wskazanych<br />
interfejsów<br />
?<br />
oczywiście też interfejs:<br />
IUnknown<br />
2
Wstęp do programowania<br />
składnikowego<br />
CZĘŚĆ 2:<br />
interfejs Iunknown<br />
Jarosław Francik<br />
Interfejs IUnknown<br />
interface IUnknown<br />
{<br />
virtual HRESULT __stdcall QueryInterface<br />
(const IID &iid, void **ppc) = 0;<br />
virtual ULONG __stdcall AddRef() = 0;<br />
virtual ULONG __stdcall Release() = 0;<br />
};<br />
Każdy interfejs COM dziedziczy po IUnknown<br />
IUnknown::QueryInterface<br />
Klient<br />
1. Klient wywołuje<br />
QueryInterface(IID)<br />
2. Obiekt zwraca<br />
wskaźnik <strong>na</strong> interfejs<br />
3. Klient może<br />
wywoływać metody<br />
Składnik<br />
COM<br />
HRESULT QueryInterface(const IID &iid, void **p);<br />
IUnknown::QueryInterface<br />
(użycie przez klienta)<br />
// inicjalizacja składnika<br />
IUnknown *pComp = CreateInstance();<br />
// chcemy skorzystać z interfejsu IA<br />
IA *pIA = NULL;<br />
HRESULT hr = pComp->QueryInterface<br />
(IID_IA, (void**)&pIA);<br />
if (SUCCEEDED(hr))<br />
{<br />
pIA->fa();<br />
}<br />
identyfikator interfejsu – IID (GUID)<br />
IUnknown::QueryInterface<br />
(implementacja)<br />
HRESULT _stdcall CMyComp::QueryInterface(REFIID iid,<br />
void **ppv)<br />
{<br />
if (iid == IID_IUnknown)<br />
*ppv = (IA*)this;<br />
else if (iid == IID_IA)<br />
*ppv = (IA*)this;<br />
else if (iid == IID_IB)<br />
*ppv = (IB*)this;<br />
else { ppv = NULL; return E_NOINTERFACE; }<br />
((IUnknown*)(*ppv))->AddRef();<br />
return S_OK;<br />
}<br />
Funkcja CreateInstance<br />
IUnknown *CreateInstance()<br />
{<br />
IUnknown *p = (IA*)new CMyComp;<br />
p->AddRef();<br />
return p;<br />
}<br />
W bibliotece COM API dostęp<strong>na</strong> jest funkcja<br />
CoCreateInstance o podobnym działaniu<br />
1
Drugie podejście<br />
T<br />
T<br />
Co widzi klient?<br />
T interfejsy<br />
T identyfikatory interfejsów (IID)<br />
T <strong>na</strong>główek funkcji CreateInstance<br />
(w COM korzystamy raczej<br />
z funkcji bibliotecznej CoCreateInstance)<br />
Co implementuje składnik?<br />
T klasa implementująca interfejs IUnknown<br />
oraz własne interfejsy<br />
T funkcja CreateInstance<br />
[ listing #2 ]<br />
Nie używamy dziedziczenia<br />
wirtualnego! Jest ono<br />
niezgodne z formatem<br />
bi<strong>na</strong>rnym COM<br />
QueryInterface<br />
QueryInterface możemy stosować do<br />
dowolnego interfejsu, nie tylko do<br />
IUnknown – gdyż wszystkie interfejsy<br />
dziedziczą po IUnknown<br />
QueryInterface<br />
IUnknown *p = CreateInstance();<br />
IA *pIA = NULL;<br />
hr = p->QueryInterface(IID_IA, (void**)&pIA);<br />
if (!SUCCEEDED(hr)) return;<br />
// Interfejs IB otrzymujemy z IA<br />
IB *pIB = NULL;<br />
hr = pIA->QueryInterface(IID_IB, (void**)&pIB);<br />
if (!SUCCEEDED(hr)) return;<br />
// Interfejs IUnknown otrzymujemy z IB<br />
IUnknown * pUnknown = NULL;<br />
hr = pIB->QueryInterface(IID_IUnknown,<br />
(void**)&pUnknown);<br />
if (!SUCCEEDED(hr)) return;<br />
QueryInterface<br />
T<br />
T<br />
T<br />
T<br />
Za każdym razem otrzymuję ten sam interfejs<br />
IUnknown<br />
Zawsze mogę otrzymać interfejs,<br />
który już kiedyś uzyskałem<br />
Zawsze mogę uzyskać interfejs,<br />
który już mam<br />
Jeśli mogę otrzymać interfejs gdziekolwiek,<br />
to mogę go otrzymać wszędzie<br />
2
QueryInterface<br />
T definiuje obiekt<br />
T pozwala udostępniać różne wersje<br />
interfejsów dla tego samego obiektu<br />
T pomaga utrzymać kompatybilność<br />
wstecz przy wypuszczaniu nowej wersji<br />
produktu<br />
T ALE: wymaga to dotrzymania założeń<br />
przez projektanta nowej wersji!!!<br />
Zliczanie referencji:<br />
AddRef/Release<br />
T Klasa składnika zawiera licznik<br />
referencji<br />
T AddRef zwiększa licznik<br />
T Release zmniejsza licznik i w razie<br />
potrzeby usuwa składnik z pamięci<br />
T Utworzenie obiektu = AddRef<br />
T Porzucenie obiektu = Release<br />
Zliczanie referencji:<br />
AddRef/Release<br />
ULONG _stdcall CMyComp::AddRef()<br />
{<br />
return InterlockedIncrement(&m_nRef);<br />
}<br />
ULONG _stdcall CMyComp::Release()<br />
{<br />
if (InterlockedDecrement(&m_nRef) == 0)<br />
{<br />
delete this;<br />
return 0;<br />
}<br />
return m_nRef;<br />
}<br />
AddRef/Release:<br />
trzy proste zasady<br />
T Wywołaj AddRef zanim zwrócisz wynik<br />
T<br />
jeśli zwracasz interfejs jako wartość funkcji. Dotyczy też<br />
QueryInterface i CreateInstance!<br />
T Wywołaj Release kiedy skończysz<br />
T<br />
gdy nie będziesz już dłużej wykorzystywał interfejsu<br />
T Wywołaj AddRef gdy robisz przypisanie<br />
T<br />
AddRef/Release:<br />
Optymalizacja<br />
Nie musisz stosować AddRef/Release w przypadku<br />
użycia wskaźnika o zagnieżdżonym zasięgu<br />
(w obrębie zasięgu innego wskaźnika)<br />
T<br />
T<br />
T<br />
T<br />
T<br />
T<br />
parametr wejściowy – nic (jest zagnieżdżony)<br />
parametr wyjściowy – AddRef<br />
parametr wejściowo-wyjściowy – być może AddRef + Release<br />
zmien<strong>na</strong> lokal<strong>na</strong> – nic (jest zagnieżdżo<strong>na</strong>)<br />
zmien<strong>na</strong> global<strong>na</strong> – AddRef + Release<br />
w przypadku wątpliwości – AddRef + Release<br />
Drugie podejście...<br />
T Słaby punkt:<br />
T funkcja CreateInstance zbyt mocno wiąże<br />
klienta z serwerem<br />
T Rozwiązanie:<br />
T skorzystamy z technologii COM...<br />
3
Wstęp do programowania<br />
składnikowego<br />
CZĘŚĆ 3:<br />
Funkcja CoCreateInstance, fabryki klas<br />
i serwery COM<br />
Jarosław Francik<br />
Podstawy technologii COM<br />
<br />
HRESULT<br />
standardowy typ wartości zwracanych przez<br />
funkcje<br />
<br />
<br />
<br />
S_OK S_FALSE<br />
E_UNEXPECTED E_NOTIMPL<br />
E_NOINTERFACE E_OUTOFMEMORY<br />
Bity:<br />
0-15 return code co się stało<br />
16-30 facility code id części systemu<br />
31 sukces czy porażka<br />
Makro SUCCEEDED<br />
Funkcja FormatMessage<br />
Podstawy technologii COM<br />
Podstawy technologii COM<br />
<br />
Rejestr Windows<br />
HKEY_CLASSES_ROOT<br />
CLSID<br />
{59F1DE0D-8843-4A57-82CE-D6DBA3D28D28}: Wykład COM, podejście 3<br />
T InProcServer32: c:\windows\system32\Component.dll<br />
T ProgID: COMLecture.Approach3.1<br />
T VersionIndependentProgID: COMLecture.Approach3<br />
COMLecture.Approach3.1: Wykład COM, podejście 3<br />
CLSID: {59F1DE0D-8843-4A57-82CE-D6DBA3D28D28}<br />
COMLecture.Approach3: Wykład COM, podejście 3<br />
CLSID: {59F1DE0D-8843-4A57-82CE-D6DBA3D28D28}<br />
CurVer: COMLecture.Approach3.1<br />
<br />
Rejestr Windows (c.d.)<br />
Przydatne funkcje:<br />
<br />
CLSIDFromProgID<br />
Rejestrowanie serwerów COM<br />
Serwer musi sam dostarczyć eksportowanych funkcji:<br />
DllRegisterServer<br />
DllUnregisterServer<br />
Przydatne <strong>na</strong>rzędzia:<br />
RegOpenKeyEx, RegCreateKeyEx, RegSetValueEx,<br />
RegEnumKeyEx, RegDeleteKey, RegCloseKey<br />
Pliki Registry.h i Registry.cpp moż<strong>na</strong> z<strong>na</strong>leźć wraz z<br />
programem przykładowym approach3<br />
<br />
Podstawy technologii COM<br />
Przydatne funkcje:<br />
CoInitialize(NULL); // dla każdego procesu osobno!<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
CoUninitialize<br />
StringFromCLSID<br />
StringFromIID<br />
CLSIDFromString<br />
IIDFromString<br />
CoTaskMemAlloc<br />
CoTaskMemFree<br />
Podstawy technologii COM<br />
CoCreateInstance (<br />
const CLSID &clsid,<br />
IUnknown *pIUnknownOuter,<br />
DWORD dwClsContext,<br />
const IID &iid,<br />
void **ppv);<br />
1
Trzecie podejście - klient<br />
Zastępujemy dotychczasowy<br />
CreateInstance wywołaniem<br />
CoCreateInstance<br />
Powiązanie między klientem a serwerem<br />
już tylko poprzez CLSID!<br />
Trzecie podejście - klient<br />
CoInitialize(NULL);<br />
IA *pIA = NULL;<br />
HRESULT hr = CoCreateInstance(<br />
CLSID_MyComponent,<br />
NULL,<br />
CLSCTX_INPROC_SERVER,<br />
IID_IA,<br />
(void**)&pIA);<br />
if (SUCCEEDED(hr))<br />
{ pIA->fa();<br />
pIA->Release();<br />
}<br />
Fabryka klas<br />
IClassFactory – interfejs służący do tworzenia<br />
egzemplarzy klas (obiektów)<br />
interface IClassFactory<br />
{ HRESULT __stdcall CreateInstance<br />
(IUnknown *pUnknownOuter,<br />
const IID& iid, void **ppv);<br />
HRESULT __stdcall LockServer(BOOL b);<br />
}<br />
w wywołaniu CreateInstance brakuje CLSID<br />
Fabryka klas służy do tworzenia obiektów określonej klasy<br />
<br />
Fabryka klas<br />
Funkcja CoGetClassObject zwraca wskaźnik <strong>na</strong><br />
fabrykę klas dla określonego CLSID<br />
HRESULT __stdcall CoGetClassObject(<br />
const CLSID &clsid,<br />
DWORD dwClsContext,<br />
COSERVERINFO *pServerInfo,<br />
const IID &iid,<br />
void **ppv);<br />
CoGetClassObject wywołuje DllGetClassObject:<br />
funkcję obowiązkowo eksportowaną przez serwery COM<br />
Fabryka klas<br />
Fabryka klas<br />
<br />
CoCreateInstance vc CoGetClassObject<br />
HRESULT __stdcall CoCreateInstance (...)<br />
{ *ppv = NULL;<br />
IClassFactory *pFactory = NULL;<br />
HRESULT hr = CoGetClassObject(clsid, ...,<br />
IID_IClassFactory, &pFactory);<br />
if (SUCCEEDED(hr))<br />
{ hr = pFactory->CreateInstance(..., iid, ppv);<br />
pFactory->Release;<br />
}<br />
return hr;<br />
}<br />
Klient<br />
CoCreateInstance<br />
COM API<br />
CoGet-<br />
ClassObject<br />
IA*<br />
DLL<br />
DllGetClassObject<br />
Class<br />
Factory<br />
new CFactory<br />
IClassFactory*<br />
IClassFactory::CreateInstance<br />
IClassFactory::Release<br />
pIA->fa<br />
Składnik<br />
new CMyComp<br />
QueryInterface<br />
2
Trzecie podejście - serwer<br />
<br />
<br />
<br />
Implementacja klasy składnika:<br />
prawie bez zmian<br />
Implementacja fabryki klas:<br />
<br />
<br />
<br />
implementacja IUnknown – standardowa<br />
implementacja IClassFactory::CreateInstance<br />
implementacja IClassFactory::LockServer<br />
Implementacja eksportowanych funkcji:<br />
<br />
<br />
<br />
DllCanUnloadNow – wymaga zliczania instancji<br />
DllGetClassObject<br />
DllRegisterServer / DllUnRegisterServer<br />
[ listing #3 ]<br />
Trzecie podejście – co dalej?<br />
Serwer COM rejestrujemy za pomocą<br />
programu regsrv32<br />
Interfejsy mają stałe IID<br />
Aplikacje wcale nie muszą korzystać<br />
stale z tych samych CLSID<br />
3
TWORZENIE SKŁADNIKÓW<br />
WEWNĄTRZPROCESOWYCH<br />
Poniższy zwięzły opis przedstawia <strong>na</strong>jważniejsze kroki niezbędne dla uzyskania klienta i serwera<br />
wewnątrzprocesowego COM. Ilustracją poniższej instrukcji są kody źródłowe approach3 i approach3a, a<br />
także inne materiały dostępne <strong>na</strong> stronie sun.iinf.polsl.gliwice.pl/~jfrancik/lectures/com.html.<br />
1. STWORZENIE SPECYFIKACJI IDL<br />
Plik IDL zawiera specyfikację tworzonych interfejsów i klas składników (a także biblioteki typów).<br />
Wynikiem kompilacji pliku abc.idl są między innymi pliki abc.h (deklaracje interfejsów w języku C/C++)<br />
oraz abc_i.c (definicje identyfikatorów iid i clsid).<br />
[Krok ten eliminuje potrzebę tworzenia pliku <strong>na</strong>główkowego interface.h<br />
oraz definiowania explicite identyfikatorów iid / clsid – por. listing approach 3]<br />
2. STWORZENIE APLIKACJI KLIENCKIEJ<br />
Aplikacja kliencka powin<strong>na</strong> obejmować obydwa pliki wynikowe kompilacji specyfikacji IDL wspomniane w<br />
poprzednim punkcie. Aplikacja inicjalizuje system COM (CoInitialize), zakłada nowy egzemplarz składnika<br />
za pomocą funkcji CoCreateInstance i otrzymuje wskaźnik <strong>na</strong> podany interfejs. Wskaźnik <strong>na</strong> dowolny, inny<br />
interfejs aplikacja może otrzymać wywołując funkcję QueryInterface już posiadanego interfejsu. Aplikacja<br />
jest zobowiąza<strong>na</strong> wywoływać funkcje AddRef/Release – zgodnie z obowiązującymi zasadami (por.<br />
prezentacja approach2.pdf).<br />
3. STWORZENIE MODUŁU DLL SERWERA<br />
Na stworzenie modułu serwera składają się <strong>na</strong>stępujące czynności:<br />
1. Wprowadzenie do projektu plików wynikowych kompilacji specyfikacji IDL wyszczególnionych w p. 1.<br />
2. Założenie globalnych liczników: aktywnych składników oraz blokad serwera.<br />
3. Stworzenie klasy składnika. Wszystkie implementowane interfejsy powinny być wyszczególnione<br />
jako klasy bazowe. Należy zaimplementować wszystkie funkcje składowe tych interfejsów, a także<br />
funkcje składowe interfejsu IUnknown: QueryInterface, AddRef oraz Release.<br />
[Użycie specyfikacji IDL wymusza, by wszystkie funkcje składowe zwracały HRESULT.<br />
Listing Approach3 nie spełnia tego wymogu; stosowną poprawkę moż<strong>na</strong> z<strong>na</strong>leźć w listingu Approach3a]<br />
4. Stworzenie klasy implementującej interfejs IClassFactory (fabrykę klas). Należy zaimplementować<br />
zarówno funkcje składowe interfejsu IClassFactory (CreateInstance i LockServer), jak i<br />
wyszczególnione wcześniej funkcje składowe interfejsu IUnknown.<br />
5. Implementacja punktu wejściowego DLL (DllMain). W obrębie tej funkcji <strong>na</strong>leży zachować uchwyt<br />
modułu DLL HMODULE.<br />
6. Implementacja obowiązkowych funkcji eksportowanych:<br />
- DllCanUnloadNow – <strong>na</strong> podstawie wspomnianych w p. 3-2 globalnych liczników ustala, czy<br />
serwer może być usunięty z pamięci.<br />
- DllGetClassObject – sprawdza podany clsid i tworzy odpowiadającą mu fabrykę klas. Zwraca<br />
wskaźnik do wskazanego w parametrze interfejsu fabryki klas.<br />
- DllRegisterServer – <strong>na</strong> podstawie zachowanego w funkcji DllMain uchwytu modułu DLL oraz<br />
dodatkowych, pomocniczych zmiennych globalnych, przeprowadza rejestrację serwera.<br />
Pomocne są tu pliki registry.h i registry.cpp (© Dale Rogerson) zawierające pomocne funkcje.<br />
- DllUnregisterServer – <strong>na</strong> podstawie dodatkowych, pomocniczych zmiennych globalnych,<br />
przeprowadza wyrejestrowanie serwera. Pomocne są tu pliki registry.h i registry.cpp (© Dale<br />
Rogerson) zawierające pomocne funkcje.<br />
7. Rejestracja modułu w systemie (za pomocą opcji Tools|Register Control w Visual Studio lub<br />
wywołując program regsvr32).<br />
opracowanie: Jarosław Francik
IDL EKSPRESOWE WPROWADZENIE<br />
Jarosław Francik<br />
PRZYKŁAD DEFINICJI INTERFEJSU:<br />
OBJAŚNIENIA:<br />
import "unknwn.idl";<br />
[ object,<br />
uuid(B5DB3493-9925-4633-B7DB-30DA5E75D347),<br />
helpstring("przykładowy interfejs IA"),<br />
pointer_default(unique)<br />
]<br />
interface IA : IUnknown<br />
{ HRESULT fun([in] int x, [out] int &r);<br />
};<br />
Powyższa definicja odpowiada <strong>na</strong>stępującej klasie C++:<br />
interface IA : public IUnknown<br />
{ virtual void __stdcall fa() = 0;<br />
};<br />
Specyfikacja klasy jest poprzedzo<strong>na</strong> fragmentem z<strong>na</strong>jdującym się w <strong>na</strong>wiasach kwadratowych. Są to rozszerzenia<br />
IDL w stosunku do C++:<br />
import<br />
wstawia plik w IDL (w tym przypadku: definicję klasy IUnknown)<br />
object<br />
wyróżnik obiektu COM (IDL ma zastosowania szersze niż tylko COM)<br />
uuid<br />
identyfikator GUID<br />
helpstring zwięzły opis w języku <strong>na</strong>turalnym, udostępniany <strong>na</strong> poziomie biblioteki typów<br />
pointer_default domyślny tryb marshalingu wskaźników; dostepne wartości to:<br />
ref wskaźniki są traktowane jak referencje: nie mogą zawierać NULL, nie mogą<br />
być zmieniane, w obrębie funkcji nie mogą być tworzone aliasy<br />
unique wskaźniki mogą zawierać NULL, mogą być zmieniane, ale w obrębie funkcji<br />
nie mogą być tworzone aliasy<br />
ptr wszystko powyższe jest dozwolone<br />
Specyfikacja funkcji składowych. Wszystkie funkcji w IDL muszą zwracać wartość HRESULT. Poszczególne<br />
parametry mogą zawierać dodatkowe określniki, stanowiące rozszerzenia IDL w stosunku do C++. Są<br />
one niezbędne dla zapewnienia prawidłowego marshalingu parametrów.<br />
Do zdefiniowania parametrów marshalingu są potrzebne:<br />
– adres przesyłanej zmiennej,<br />
– kierunek przesyłu argumentu,<br />
– rozmiar przesyłanego bloku.<br />
Pierwszy z powyższych parametrów jest z<strong>na</strong>ny podczas wyko<strong>na</strong>nia programu.<br />
Kierunek przesyłu argumentu określa się za pomocą <strong>na</strong>stępujących określników:<br />
in<br />
out<br />
in, out<br />
wyróżnik argumentu wejściowego funkcji<br />
wyróżnik argumentu wyjściowego (wyniku) funkcji (<strong>na</strong> ogół wskaźnika lub referencji)<br />
wyróżnik argumentu będącego jednocześnie wejściowym i wyjściowym<br />
Rozmiar przesyłanego bloku jest <strong>na</strong>jczęściej jednoz<strong>na</strong>cznie określany <strong>na</strong> podstawie typu przesyłanych danych.<br />
W kolejnych podpunktach zostaną przedstawione elementy języka IDL pozwalające <strong>na</strong> określenie rozmiaru przesyłanych<br />
danych w przypadkach, gdy nie może on być ustalony tylko <strong>na</strong> podstawie typu danych.<br />
Przesyłanie łańcuchów wymaga użycia określnika string. Należy pamiętać, że COM obsługuje wyłącznie<br />
łańcuchy Unicode (wchar_t zamiast char):<br />
HRESULT strfun([in, string] wchar_t *pIn, [out, string] wchar_t **pOut);
Przesyłanie tablic wymaga określenia rozmiaru tablicy za pomocą określnika size_is. Pozwala on <strong>na</strong> podanie<br />
rozmiaru posługując się innym parametrem, który powinien zawierać rozmiar tablicy:<br />
HRESULT arrfunIn([in] long nSize, [in, size_is(nSize)] long array[]);<br />
HRESULT arrfunOut([out, in] long *nSize, [out, size_is(nSize)] long array[]);<br />
<strong>na</strong>leży zauważyć, że parametr użyty w size_in może być tylko wejściowy [in] lub wejściowo-wyjściowy [in,<br />
out]. Wynika z tego, że COM będzie zawsze wiedzieć, jak dużą tablicę udostępnił klient. Serwer może zapełnić<br />
tylko część tej tablicy, stosownie ustawiając parametr nSize. Z tego względu warto rozważyć <strong>na</strong>stępującą<br />
modyfikację funkcji arrfunOut, w której rozdzielono parametr wejściowy i wyjściowy:<br />
HRESULT arrfunOut2([in] nSizeIn, [out, size_is(nSizeIn)] long array[],<br />
[out] long *nSizeOut);<br />
Przekazywanie struktur jest łatwe, gdyż IDL pozwala <strong>na</strong> bezpośrednie definiowanie struktur:<br />
typedef struct Date<br />
{ int D; int M; int Y;<br />
} ;<br />
// ....<br />
HRESULT structfun([in] Date birthdate);<br />
Sprawa się komplikuje, gdy elementami struktury są wskaźniki <strong>na</strong> inne struktury, które również powinny<br />
być odwzorowane w ramach marshalingu. Kompilator IDL musi dokładnie wiedzieć, <strong>na</strong> co wskazuje wskaźnik<br />
– nie <strong>na</strong>leży więc raczej stosować typu void*. Jeśli przekazywany jest wskaźnik <strong>na</strong> interfejs COM, dobrym<br />
pomysłem jest przekazanie – w osobnym parametrze – jego IID. Służy temu specjalny określnik iid_is:<br />
HRESULT intfun([in] const IID &iid, [out, iid_is(iid)] IUnknown **ppv);<br />
Oczywiście mniej więcej to samo moż<strong>na</strong> uzyskać przekazując typ interfejsu wprost, np.:<br />
HRESULT intfun_<strong>na</strong>ive([out] IMyInterface **ppv);<br />
rozwiązanie takie zemści się jed<strong>na</strong>k, gdy zamiast wskaźnika <strong>na</strong> IMyInterface zechcemy przesłać coś, co<br />
wskazuje <strong>na</strong> jakąś klasę wywiedzoną z IMyInterface.<br />
PRZYKŁAD DEFINICJI<br />
BIBLIOTEKI TYPÓW:<br />
OBJAŚNIENIA:<br />
library<br />
coclass<br />
[ uuid(3221CCFF-68B2-442b-AFDC-FA9C767F1FC4),<br />
version(1.0),<br />
helpstring("Przykładowa biblioteka typów")<br />
]<br />
library MyTypeLib<br />
{ importlib("stdole32.tlb") ;<br />
[<br />
uuid(E843265D-B374-4d56-A980-0EC5E9188B47),<br />
helpstring("Klasa komponentu")<br />
]<br />
coclass Component<br />
{<br />
[default] interface IA;<br />
interface IB;<br />
};<br />
} ;<br />
definiuje bibliotekę typów o podanym GUID (LIBID). Biblioteka może zawierać jeden<br />
lub więcej komponentów – klas składników<br />
definiuje klasę składników o podanym GUID (CLSID). Klasa może udostępniać jeden lub<br />
więcej interfejsów. Jeden z nich może być domyślny, (istotne dla języków makr i VB).<br />
Opis powyższy ma charakter wstępny; w szczególności pominięto zagadnienia związane z definiowaniem<br />
dispinterfejsów (dipinterface, propget, propput, dual, oleautomation). Wyczerpujący opis języka IDL moż<strong>na</strong><br />
z<strong>na</strong>leźć w MSDN; dobrym sposobem <strong>na</strong>uki IDL jest też a<strong>na</strong>lizowanie różnych dostępnych definicji klas.
TWORZENIE SKŁADNIKÓW<br />
ZEWNĄTRZPROCESOWYCH<br />
Poniższy zwięzły opis przedstawia <strong>na</strong>jważniejsze kroki niezbędne dla uzyskania klienta i serwera zewnątrzprocesowego<br />
COM (serwer w pliku EXE). Tekst ten powinien być używany łącznie z instrukcją dot.<br />
tworzenia składników wewnątrzprocesowych (inproc). Ilustracją jest też kod źródłowy approach4, a także<br />
inne materiały dostępne <strong>na</strong> stronie sun.iinf.polsl.gliwice.pl/~jfrancik/lectures/com.html.<br />
1. STWORZENIE SPECYFIKACJI IDL<br />
Ten krok przebiega tak samo, jak w przypadku składników inproc.<br />
2. STWORZENIE APLIKACJI KLIENCKIEJ<br />
Jedyną różnicą pomiędzy aplikacją korzystającą ze składnika zewnątrzprocesowego (w stosunku do aplikacji<br />
używającej składnika inproc) jest wartość trzeciego parametru funkcji CoCreateinstance, która musi<br />
zawierać ustawiony bit CLSCTX_LOCAL_SERVER.<br />
3. STWORZENIE MODUŁU DLL PROXY<br />
Moduł proxy jest niezbędny przy komunikacji klienta z serwerem zewnątrzprocesowym, nie wymaga jed<strong>na</strong>k<br />
specjalnego kodowania. Wystarczy w tym celu:<br />
- Stworzyć projekt DLL obejmujący <strong>na</strong>stępujące pliki źródłowe, automatycznie generowane podczas<br />
kompilacji specyfikacji IDL: dlldata.c, abc_i.c, abc_p.c (przy założeniu, że kompilowano plik abc.idl).<br />
- Włączyć linkowanie <strong>na</strong>stepujących bibliotek (poza standardowymi): rpcndr.lib, rpcns4.lib i rpcrt4.lib.<br />
- Dodać definicję symbolu REGISTER_PROXY_DLL<br />
- Dołączyć plik def (por. przykład approach4)<br />
- Skompilować i zlinkować projekt.<br />
- Zarejestrować otrzymany plik DLL (za pomocą opcji Tools|Register Control w Visual Studio lub<br />
wywołując program regsvr32).<br />
4. STWORZENIE MODUŁU EXE SERWERA<br />
Większa część kodu źródłowego serwerów zewnątrz- i wewnątrzprocesowych jest taka sama. W szczególności<br />
punkty 1 – 4 w części instrukcji dotyczącej tworzenia serwerów inproc moż<strong>na</strong> zastosować do serwerów<br />
zewnątrzprocesowych. Punkty 5 – 7 tej instrukcji nie odnoszą się do serwerów w plikach EXE, w szczególności<br />
nie ma tu funkcji eksportowanych. Oto specyficzne dla tego typu serwerów czynności dodatkowe:<br />
1. Stworzenie funkcji WinMain jak w przykładzie Approach4. Poniżej przedstawiono <strong>na</strong>jważniejsze elementy:<br />
- Inicjalizacja systemu COM (CoInitialize).<br />
- A<strong>na</strong>liza linii wywołania: parametrów RegServer (rejestracja serwera), UnregServer (wyrejestrowanie<br />
serwera) i Embedding (praca tylko w trybie serwera, a nie aplikacji – zwykle powoduje wyłączenie<br />
wyświetlenia ok<strong>na</strong> aplikacji).<br />
[Uwaga – wśród plików projektu Approach4 z<strong>na</strong>jduja się pliki Registry.h i Registry.cpp<br />
z modyfikacjami niezbędnymi przy obsłudze składników zewnątrzprocesowych]<br />
- Utworzenie i rejestracja fabryk(i) klas (funkcja CoRegisterClassObject).<br />
- Wyświetlenie ok<strong>na</strong> (zwykle z wyjątkiem trybu /Embedding) i petla komunikatów<br />
- Wyrejestrowanie fabryk(i) klas (CoRevokeClassObject) i deinicjalizacja COM (CoUninitialize)<br />
2. Implementacja zarządzania usuwaniem aplikacji z pamięci. Wymaga to:<br />
- przy dekrementacji globalnych liczników (g_cComponents i g_cServerLocks) – sprawdzenia, czy<br />
aplikacja nie powin<strong>na</strong> być usunięta z pamięci:<br />
- if (g_cComponents == 0 && g_cServerLocks == 0) PostQuitMessage(0);<br />
- Blokady usuwania aplikacji, gdy używany jest interfejs użytkownika (okno aplikacji):<br />
- InterlockedIncrement(&g_cServerLocks); – wywołane tuż po otwarciu ok<strong>na</strong><br />
- Zniesienia powyższej blokady w chwili zamknięcia ok<strong>na</strong> aplikacji:<br />
- case WM_CLOSE: ::InterlockedDecrement(&g_cServerLocks); – w funkcji okienkowej<br />
- Dodatkowej kontroli podczas obsługi komunikatu WM_DESTROY:<br />
case WM_DESTROY: if (g_cComponents == 0 && g_cServerLocks == 0) PostQuitMessage(0);<br />
3. Zarejestrowanie serwera – poprzez wywołanie go z parametrem /RegServer.<br />
opracowanie: Jarosław Francik
Interfejs IDispatch,<br />
dispinterfejsy<br />
i automatyzacja<br />
Jarosław Francik<br />
Interface Idispatch : IUnknown<br />
{<br />
HRESULT GetTypeInfoCount([out] UINT __RPC_FAR *pctinfo);<br />
HRESULT GetTypeInfo(<br />
[in] UINT iTInfo, [in] LCID lcid,<br />
[out] ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);<br />
HRESULT GetIDsOfNames(<br />
[in] REFIID riid,<br />
[in] LPOLESTR __RPC_FAR *rgszNames,<br />
[in] UINT cNames,<br />
[in] LCID lcid,<br />
[out, size_is(cNames)] DISPID __RPC_FAR *rgDispId);<br />
HRESULT Invoke(<br />
[in] DISPID dispIdMember,<br />
[in] REFIID riid, [in] LCID lcid, [in] WORD wFlags,<br />
[in] DISPPARAMS __RPC_FAR *pDispParams,<br />
[out] VARIANT __RPC_FAR *pVarResult,<br />
[out] EXCEPINFO __RPC_FAR *pExcepInfo,<br />
[out] UINT __RPC_FAR *puArgErr);<br />
};<br />
interfejs IDispatch (IDL)<br />
Dispinterface<br />
Dispinterface<br />
Klient<br />
IDispatch<br />
QueryInterface<br />
Składnik COM<br />
Klient<br />
IDispatch<br />
QueryInterface<br />
Składnik COM<br />
AddRef<br />
Release<br />
GetTypeInfoCount<br />
dispinterface<br />
"fun1" 1<br />
"fun2" 2<br />
AddRef<br />
Release<br />
GetTypeInfoCount<br />
dispinterface<br />
"fun1" 1<br />
"fun2" 2<br />
GetTypeInfo<br />
GetIDsOfNames<br />
Invoke<br />
"fun3" 3<br />
1 fun1<br />
2 fun2<br />
GetTypeInfo<br />
GetIDsOfNames<br />
Invoke<br />
"fun3" 3<br />
pVTbl fun1<br />
fun2<br />
3 fun3<br />
fun3<br />
dual interface<br />
IDispatch<br />
Klient<br />
QueryInterface<br />
AddRef<br />
Release<br />
GetTypeInfoCount<br />
GetTypeInfo<br />
GetIDsOfNames<br />
Invoke<br />
fun1<br />
fun2<br />
fun3<br />
Składnik COM<br />
dispinterface<br />
"fun1" 1<br />
"fun2" 2<br />
"fun3" 3<br />
Parametry wywołania Invoke<br />
dispIdMember – dispid wywoływanej funkcji<br />
riid – zarezerwowany, ma być IID_NULL<br />
lcid – użyj GetUserDefaultLCID()<br />
wFlags – rodzaj funkcji:<br />
DISPATCH_METHOD, DISPATCH_PROPERTYGET,<br />
DISPATCH_PROPERTYPUT, DISPATCH_PROPERTYPUTREF<br />
pDispParams – tablica parametrów wywołania<br />
tagDISPPARAMS<br />
pVarResult – wynik funkcji (wskaźnik <strong>na</strong><br />
wariant)<br />
pExcepInfo, puArgErr – sytuacje wyjątkowe<br />
1
DISPPARAMS i Warianty<br />
struct tagDISPPARAMS { VARIANTARG *rgvarg; /* .... */ };<br />
struct tagVARIANT { /* .... */<br />
VARTYPE vt; /* .... */ // stałe VT_*<br />
union {<br />
LONG lVal;<br />
// VT_I4<br />
BYTE bVal;<br />
// VT_UI1<br />
SHORT iVal;<br />
// VT_I2<br />
/* ... */<br />
BSTR bStrVal; // VT_BYREF | VT_BSTR<br />
Ciekawe funkcje:<br />
SysAllocString<br />
VariantChangeType<br />
wchar_t progid[] = L"COMLecture.Approach5.1";<br />
CLSID clsid;<br />
CLSIDFromProgID(progid, &clsid);<br />
IDispatch *pIDispatch = NULL;<br />
HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER,<br />
IID_IDispatch, (void**)&pIDispatch);<br />
DISPID dispid;<br />
OLECHAR *<strong>na</strong>me = L"fa";<br />
hr = pIDispatch->GetIDsOfNames(IID_NULL, &<strong>na</strong>me, 1,<br />
GetUserDefaultLCID(), &dispid);<br />
VARIANTARG varg;<br />
VariantInit(&varg); varg.vt = VT_EMPTY;<br />
DISPPARAMS param;<br />
param.cArgs = 0; param.rgvarg = &varg;<br />
param.cNamedArgs = 0; param.cArgs = NULL;<br />
hr = pIDispatch->Invoke(dispid, IID_NULL, GetUserDefaultLCID(),<br />
DISPATCH_METHOD, ¶m, NULL, NULL, NULL);<br />
Klient dla dispinterface<br />
Visual C++ i MFC:<br />
IA ia;<br />
ia.CreateDispatch("COMLecture.Approach5.1„)<br />
ia.fa();<br />
Visual Basic:<br />
Dim x As Object<br />
CreateObject("COMLecture.Approach5.1„)<br />
x.fa<br />
Klienci dla dispinterface<br />
2
// Kalkulator (prosty) - wersja agregowal<strong>na</strong><br />
import "unknwn.idl";<br />
// Interface ICalc<br />
[<br />
object,<br />
uuid(892753ED-D14E-4d2f-B812-041E0C01F5F3),<br />
helpstring("Kalkulator (prosty), wersja agregowal<strong>na</strong>"),<br />
pointer_default(unique)<br />
]<br />
interface ICalc : IUnknown<br />
{<br />
HRESULT digit([in] int n);<br />
HRESULT comma();<br />
HRESULT sign();<br />
HRESULT op([in] int n);<br />
HRESULT eq();<br />
HRESULT c();<br />
HRESULT ce();<br />
[propget, id(1), helpstring("wyświetlacz")]<br />
HRESULT display([out, retval] double *pVal);<br />
[propput, id(1), helpstring("wyświetlacz")]<br />
HRESULT display([in] double newVal);<br />
};<br />
// Biblioteka typów + clsid składników<br />
[<br />
uuid(7FB7B169-2288-4713-98BD-4360E0147DD8),<br />
version(1.0),<br />
helpstring("Kalkulator (prosty), wersja agregowal<strong>na</strong>, bibl.typów")<br />
]<br />
library CalcAggrTypeLib<br />
{<br />
importlib("stdole32.tlb") ;<br />
[<br />
uuid(4D3B9D9B-8DCC-4443-BAC8-1DCAE9955270),<br />
helpstring("Kalkulator (prosty), wersja agregowal<strong>na</strong>")<br />
]<br />
coclass CalcSimple<br />
{<br />
[default] interface ICalc;<br />
};<br />
} ;<br />
// Serwer prostego kalkulatora - wersja agregowal<strong>na</strong><br />
// Copyright (c) 2001 by Jarosław Francik<br />
#include "stdafx.h"<br />
#include "calc2.h" // plik automatycznie utworzony przez MIDL<br />
#include "calc2_i.c" // plik automatycznie utworzony przez MIDL<br />
#include <br />
#include "registry.h" // pomocnicze <strong>na</strong>rzędzia do rejestru systemowego...<br />
///////////////////////////////////////////////////////////<br />
// Zmienne globalne<br />
static HMODULE g_hModule = NULL ;<br />
static long g_cComponents = 0 ;<br />
static long g_cServerLocks = 0 ;<br />
// uchwyt modułu DLL<br />
// Licznik aktywnych składników<br />
// Licznik dla LockServer<br />
// Dane do rejestru systemowego<br />
const char g_szProgID[] = "Calc.SimpleAggr.2";<br />
const char g_szFriendlyName[] = "Prosty kalkulator COM - wersja agregowal<strong>na</strong>";<br />
const char g_szVerIndProgID[] = "Calc.SimpleAggr";<br />
///////////////////////////////////////////////////////////<br />
// Interfejs INondelegatingUnknown<br />
interface INondelegatingUnknown<br />
{ virtual HRESULT __stdcall<br />
NondelegatingQueryInterface(const IID&, void**) = 0 ;<br />
virtual ULONG __stdcall NondelegatingAddRef() = 0 ;<br />
virtual ULONG __stdcall NondelegatingRelease() = 0 ;<br />
} ;<br />
///////////////////////////////////////////////////////////<br />
// Klasa składnika<br />
class CCalc : public ICalc, INondelegatingUnknown<br />
{public:<br />
// Implementacja interfejsu IUnknown<br />
virtual HRESULT _stdcall QueryInterface(REFIID iid, void **ppv);<br />
virtual ULONG _stdcall AddRef();<br />
virtual ULONG _stdcall Release();<br />
// Implementacja interfejsu INondelegatingUnknown<br />
virtual HRESULT _stdcall NondelegatingQueryInterface(REFIID iid, void **ppv);<br />
virtual ULONG _stdcall NondelegatingAddRef();<br />
virtual ULONG _stdcall NondelegatingRelease();<br />
// Implementacja interfejsu ICalc<br />
virtual HRESULT __stdcall digit(int n);<br />
virtual HRESULT __stdcall comma();<br />
virtual HRESULT __stdcall sign();<br />
virtual HRESULT __stdcall op(int n);<br />
virtual HRESULT __stdcall eq();<br />
virtual HRESULT __stdcall c();<br />
virtual HRESULT __stdcall ce();<br />
virtual HRESULT __stdcall get_display(double __RPC_FAR *pVal);<br />
virtual HRESULT __stdcall put_display(double newVal);<br />
// Implementacja wewnętrz<strong>na</strong><br />
double m_display, m_reg; // wyświetlacz i drugi rejestr<br />
int m_op; // kod operacji; 0=nic, 1-4 = + - * /<br />
bool m_bAppendDisp; // tryb dodawania do wyświetlacza;<br />
public:<br />
CCalc(IUnknown *pUnknownOuter) : m_nRef(1), m_display(0.0),<br />
m_reg(0.0), m_op(0), m_bAppendDisp(false)<br />
{ InterlockedIncrement(&g_cComponents);<br />
if (pUnknownOuter)<br />
m_pUnknownOuter = pUnknownOuter; // zostaliśmy zagregowani!<br />
else<br />
m_pUnknownOuter = (IUnknown*)(INondelegatingUnknown*)this;<br />
}<br />
~CCalc() { InterlockedDecrement(&g_cComponents); }<br />
private:<br />
long m_nRef;<br />
IUnknown *m_pUnknownOuter; // obiekt do którego delegujemy odwołania<br />
};
//<br />
// Implementacja interfejsu ICalc<br />
HRESULT __stdcall CCalc::digit(int n)<br />
{ if (!m_bAppendDisp)<br />
m_display = n;<br />
else<br />
m_display = m_display * 10 + n;<br />
m_bAppendDisp = true;<br />
return S_OK;<br />
}<br />
HRESULT __stdcall CCalc::comma()<br />
{ return E_NOTIMPL;<br />
}<br />
HRESULT __stdcall CCalc::sign()<br />
{ m_display = -m_display;<br />
return S_OK;<br />
}<br />
HRESULT __stdcall CCalc::op(int n)<br />
{ eq();<br />
m_op = n;<br />
return S_OK;<br />
}<br />
HRESULT __stdcall CCalc::eq()<br />
{ switch (m_op)<br />
{<br />
case 1: m_display = m_reg + m_display; break;<br />
case 2: m_display = m_reg - m_display; break;<br />
case 3: m_display = m_reg * m_display; break;<br />
case 4: m_display = m_reg / m_display; break;<br />
}<br />
m_reg = m_display;<br />
m_bAppendDisp = false;<br />
return S_OK;<br />
}<br />
HRESULT __stdcall CCalc::c()<br />
{ m_reg = m_display = m_op = 0;<br />
m_bAppendDisp = false;<br />
return S_OK;<br />
}<br />
HRESULT __stdcall CCalc::ce()<br />
{ m_display = 0;<br />
m_bAppendDisp = false;<br />
return S_OK;<br />
}<br />
HRESULT __stdcall CCalc::get_display(double __RPC_FAR *pVal)<br />
{<br />
*pVal = m_display;<br />
return S_OK;<br />
}<br />
HRESULT __stdcall CCalc::put_display(double newVal)<br />
{<br />
m_display = newVal;<br />
return S_OK;<br />
}<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Implementacja interfejsu IUnknown<br />
HRESULT _stdcall CCalc::QueryInterface(REFIID iid, void **ppv)<br />
{<br />
return m_pUnknownOuter->QueryInterface(iid, ppv);<br />
}<br />
ULONG _stdcall CCalc::AddRef()<br />
{<br />
return m_pUnknownOuter->AddRef();<br />
}<br />
ULONG _stdcall CCalc::Release()<br />
{<br />
return m_pUnknownOuter->Release();<br />
}<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Implementacja interfejsu INondelegatingUnknown<br />
HRESULT _stdcall CCalc::NondelegatingQueryInterface(REFIID iid, void **ppv)<br />
{<br />
if (iid == IID_IUnknown)<br />
*ppv = (INondelegatingUnknown*)this;<br />
else if (iid == IID_ICalc)<br />
*ppv = (ICalc*)this;<br />
else<br />
{ ppv = NULL;<br />
return E_NOINTERFACE;<br />
}<br />
((IUnknown*)(*ppv))->AddRef();<br />
return S_OK;<br />
}<br />
ULONG _stdcall CCalc::NondelegatingAddRef()<br />
{<br />
return InterlockedIncrement(&m_nRef);<br />
}<br />
ULONG _stdcall CCalc::NondelegatingRelease()<br />
{<br />
if (InterlockedDecrement(&m_nRef) == 0)<br />
{<br />
delete this;<br />
return 0;<br />
}<br />
return m_nRef;<br />
}<br />
///////////////////////////////////////////////////////////
//<br />
// Class Factory<br />
class CFactory : public IClassFactory<br />
{<br />
// standardowa deklaracja interfejsu IClassFactory<br />
} ;<br />
HRESULT __stdcall CFactory::QueryInterface(const IID& iid, void** ppv)<br />
// standardowa implementacja<br />
ULONG __stdcall CFactory::AddRef()<br />
// standardowa implementacja<br />
ULONG __stdcall CFactory::Release()<br />
// standardowa implementacja<br />
HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter,<br />
const IID& iid, void** ppv)<br />
{<br />
if (pUnknownOuter != NULL && iid != IID_IUnknown)<br />
return CLASS_E_NOAGGREGATION;<br />
}<br />
// Utworzenie składnika<br />
CCalc* pCalc = new CCalc(pUnknownOuter);<br />
if (!pCalc)<br />
return E_OUTOFMEMORY;<br />
// Utworzenie żądanego interfejsu<br />
HRESULT hr = pCalc->NondelegatingQueryInterface(iid, ppv);<br />
pCalc->NondelegatingRelease();<br />
return hr;<br />
HRESULT __stdcall CFactory::LockServer(BOOL bLock)<br />
// standardowa implementacja<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Funkcje eksportowane!<br />
// standardowa implementacja<br />
////////////////////////////////////////////////////////////<br />
// Kalkulator “<strong>na</strong>ukowy” (z wykorzystaniem agregacji)<br />
import "unknwn.idl";<br />
// Interface ICalcScientific<br />
[<br />
object,<br />
uuid(22E6B303-CEFE-4f65-9BED-8623AF83903E),<br />
helpstring("Kalkulator <strong>na</strong>ukowy"),<br />
pointer_default(unique)<br />
]<br />
interface ICalcScientific : IUnknown<br />
{<br />
HRESULT sqroot();<br />
};<br />
// Biblioteka typów + clsid składników<br />
[<br />
uuid(F3B7AD02-3D70-46e5-9313-D479B95E4089),<br />
version(1.0),<br />
helpstring("Kalkulator <strong>na</strong>ukowy, biblioteka typów")<br />
]<br />
library CalcSciTypeLib<br />
{<br />
importlib("stdole32.tlb") ;<br />
[<br />
uuid(BA847794-18E2-42ce-B053-754828A6388E),<br />
helpstring("Kalkulator <strong>na</strong>ukowy - komponent zagregowany")<br />
]<br />
coclass CalcScientific<br />
{<br />
[default] interface ICalcScientific;<br />
// interface ICalc;<br />
};<br />
} ;<br />
// Serwer kalkulatora <strong>na</strong>ukowego (z pierwiastkowaniem)<br />
// Moduł agreguje składnik Calc2 - prosty kalkulator<br />
// Copyright (c) 2001 by Jarosław Francik<br />
#include "stdafx.h"<br />
#include "calcsci.h" // plik automatycznie utworzony przez MIDL<br />
#include "calcsci_i.c" // plik automatycznie utworzony przez MIDL<br />
#include <br />
#include "registry.h" // pomocnicze <strong>na</strong>rzędzia do rejestru systemowego...<br />
#include <br />
#include "..\\calc2\\calc2.h"<br />
#include "..\\calc2\\calc2_i.c"<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Zmienne globalne<br />
// jak w poprzednim przykładzie...
//<br />
// Klasa składnika<br />
class CCalcScientific : public ICalcScientific<br />
{<br />
public:<br />
// Implementacja interfejsu IUnknown<br />
virtual HRESULT _stdcall QueryInterface(REFIID iid, void **ppv);<br />
virtual ULONG _stdcall AddRef();<br />
virtual ULONG _stdcall Release();<br />
// Implementacja interfejsu ICalcScientific<br />
virtual HRESULT __stdcall sqroot();<br />
// Funkcja inicjalizująca obiekt wewnętrzny<br />
// (wywoływa<strong>na</strong> przez fabrykę klas)<br />
HRESULT Init()<br />
{<br />
return CoCreateInstance(CLSID_CalcSimple, (IUnknown*)this,<br />
CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&m_pUnknownInner);<br />
}<br />
public:<br />
IUnknown *m_pUnknownInner;<br />
CCalcScientific() : m_nRef(1) { InterlockedIncrement(&g_cComponents); }<br />
~CCalcScientific() { InterlockedDecrement(&g_cComponents); }<br />
private:<br />
long m_nRef;<br />
};<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Implementacja interfejsu ICalcScientific<br />
HRESULT __stdcall CCalcScientific::sqroot()<br />
{<br />
ICalc *pCalc = NULL;<br />
HRESULT hr = m_pUnknownInner->QueryInterface(IID_ICalc, (void**)&pCalc);<br />
if (SUCCEEDED(hr))<br />
{<br />
double dDisp;<br />
pCalc->get_display(&dDisp);<br />
dDisp = sqrt(dDisp);<br />
pCalc->put_display(dDisp);<br />
return S_OK;<br />
}<br />
return hr;<br />
}<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Implementacja interfejsu IUnknown<br />
HRESULT _stdcall CCalcScientific::QueryInterface(REFIID iid, void **ppv)<br />
{<br />
if (iid == IID_IUnknown)<br />
*ppv = (ICalcScientific*)this;<br />
else if (iid == IID_ICalcScientific)<br />
*ppv = (ICalcScientific*)this;<br />
else if (iid == IID_ICalc)<br />
return m_pUnknownInner->QueryInterface(iid, ppv);<br />
else<br />
{ ppv = NULL;<br />
return E_NOINTERFACE;<br />
}<br />
((IUnknown*)(*ppv))->AddRef();<br />
return S_OK;<br />
}<br />
ULONG _stdcall CCalcScientific::AddRef()<br />
ULONG _stdcall CCalcScientific::Release()<br />
// standardowe implementacje<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Class Factory<br />
// standardowa implementacja fabryki klas (jak w pierwszym przykładzie)<br />
// zmiany dotyczą tylko poniższego:<br />
HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter,<br />
const IID& iid, void** ppv)<br />
{<br />
if (pUnknownOuter != NULL)<br />
return CLASS_E_NOAGGREGATION;<br />
// Utworzenie składnika<br />
CCalcScientific* pCalcScientific = new CCalcScientific;<br />
if (!pCalcScientific)<br />
return E_OUTOFMEMORY;<br />
// Utworzenie wewnętrznego składnika<br />
HRESULT hr = pCalcScientific->Init();<br />
if (!SUCCEEDED(hr))<br />
return CLASS_E_NOAGGREGATION;<br />
// Utworzenie żądanego interfejsu<br />
hr = pCalcScientific->QueryInterface(iid, ppv);<br />
pCalcScientific->Release();<br />
return hr;<br />
}<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Funkcje eksportowane!<br />
// standardowa implementacja
// Kalkulator (prosty) – wersja z punktami połączeń<br />
import "unknwn.idl";<br />
// Interface ICalc<br />
[<br />
object,<br />
uuid(14169CFC-8CC3-45ec-8C46-D080C8DE6D39),<br />
helpstring("Kalkulator (prosty), wersja z punktami połączenia"),<br />
pointer_default(unique)<br />
]<br />
interface ICalc : IUnknown<br />
{<br />
HRESULT digit([in] int n);<br />
HRESULT comma();<br />
HRESULT sign();<br />
HRESULT op([in] int n);<br />
HRESULT eq();<br />
HRESULT c();<br />
HRESULT ce();<br />
[propget, id(1), helpstring("wyświetlacz")]<br />
HRESULT display([out, retval] double *pVal);<br />
[propput, id(1), helpstring("wyświetlacz")]<br />
HRESULT display([in] double newVal);<br />
};<br />
// Interfejs wychodzący<br />
[<br />
object,<br />
uuid(A1284ED8-990E-4885-901A-26DA646245C8),<br />
helpstring("Kalkulator - Interfejs wychodzący"),<br />
pointer_default(unique)<br />
]<br />
interface IOutgoingCalc : IUnknown<br />
{<br />
HRESULT changed([in] double val);<br />
HRESULT errorB([in] BSTR msg);<br />
};<br />
// Biblioteka typów + clsid składników<br />
[<br />
uuid(AF933814-60B5-4017-A695-413D21340D47),<br />
version(1.0),<br />
helpstring("Kalkulator (prosty), wersja z punktami połączenia, biblioteka<br />
typów")<br />
]<br />
library CalcAggrTypeLib<br />
{<br />
importlib("stdole32.tlb") ;<br />
[<br />
uuid(0D49456B-4A4B-423c-9804-762059B7299A),<br />
helpstring("Kalkulator (prosty), wersja z punktami połączenia")<br />
]<br />
coclass CalcSimple<br />
{<br />
[default] interface ICalc;<br />
[source] interface IOutgoingCalc;<br />
};<br />
} ;<br />
// Serwer prostego kalkulatora - wersja z punktami połączenia<br />
// Copyright (c) 2001 by Jarosław Francik<br />
#include "stdafx.h"<br />
#include "calcCP.h"<br />
#include "calcCP_i.c"<br />
#include <br />
#include "registry.h"<br />
#include <br />
// plik automatycznie utworzony przez MIDL<br />
// plik automatycznie utworzony przez MIDL<br />
// pomocnicze <strong>na</strong>rzędzia do rejestru systemowego...<br />
// IConnectionPoint, IConnectionPointContainer<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Zmienne globalne<br />
static HMODULE g_hModule = NULL ;<br />
static long g_cComponents = 0 ;<br />
static long g_cServerLocks = 0 ;<br />
// uchwyt modułu DLL<br />
// Licznik aktywnych składników<br />
// Licznik dla LockServer<br />
// Dane do rejestru systemowego<br />
const char g_szProgID[] = "Calc.SimpleCP.2";<br />
const char g_szFriendlyName[] = "Prosty kalkulator - wersja z CP”;<br />
const char g_szVerIndProgID[] = "Calc.SimpleCP";<br />
// Wskaźnik do interfejsu ujścia<br />
IOutgoingCalc *g_pOutgoingCalc;<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Interfejs INondelegatingUnknown<br />
// jak w pierwszym przykładzie<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Klasa składnika<br />
class CCalc : public ICalc, INondelegatingUnknown,<br />
IConnectionPoint, IConnectionPointContainer<br />
{<br />
public:<br />
// Implementacja interfejsu IUnknown<br />
virtual HRESULT _stdcall QueryInterface(REFIID iid, void **ppv);<br />
virtual ULONG _stdcall AddRef();<br />
virtual ULONG _stdcall Release();<br />
// Implementacja interfejsu INondelegatingUnknown<br />
virtual HRESULT _stdcall NondelegatingQueryInterface(REFIID iid, void **ppv);<br />
virtual ULONG _stdcall NondelegatingAddRef();<br />
virtual ULONG _stdcall NondelegatingRelease();
};<br />
// Implementacja interfejsu IConnectionPoint<br />
virtual HRESULT _stdcall GetConnectionInterface(IID __RPC_FAR *pIID);<br />
virtual HRESULT _stdcall GetConnectionPointContainer(IConnectionPointContainer<br />
__RPC_FAR *__RPC_FAR *ppCPC);<br />
virtual HRESULT _stdcall Advise(IUnknown __RPC_FAR *pUnkSink,<br />
DWORD __RPC_FAR *pdwCookie);<br />
virtual HRESULT _stdcall U<strong>na</strong>dvise(DWORD dwCookie);<br />
virtual HRESULT _stdcall EnumConnections(IEnumConnections<br />
__RPC_FAR *__RPC_FAR *ppEnum);<br />
// Implementacja interfejsu IConnectionPointContainer<br />
virtual HRESULT _stdcall EnumConnectionPoints(IEnumConnectionPoints<br />
__RPC_FAR *__RPC_FAR *ppEnum);<br />
virtual HRESULT _stdcall FindConnectionPoint(REFIID riid,<br />
IConnectionPoint __RPC_FAR *__RPC_FAR *ppCP);<br />
// dalej jak w pierwszym przykładzie<br />
....<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Implementacja interfejsu Icalc<br />
// jak w pierwszym przykładzie<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Implementacja interfejsu IUnknown<br />
// jak w pierwszym przykładzie<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Implementacja interfejsu INondelegatingUnknown<br />
HRESULT _stdcall CCalc::NondelegatingQueryInterface(REFIID iid, void **ppv)<br />
{ if (iid == IID_IUnknown)<br />
*ppv = (INondelegatingUnknown*)this;<br />
else if (iid == IID_ICalc)<br />
*ppv = (ICalc*)this;<br />
else if (iid == IID_IConnectionPoint)<br />
*ppv = (IConnectionPoint*)this;<br />
else if (iid == IID_IConnectionPointContainer)<br />
*ppv = (IConnectionPointContainer*)this;<br />
else<br />
{ ppv = NULL;<br />
return E_NOINTERFACE;<br />
}<br />
((IUnknown*)(*ppv))->AddRef();<br />
return S_OK;<br />
}<br />
ULONG _stdcall CCalc::NondelegatingAddRef()<br />
// standardowa implementacja<br />
ULONG _stdcall CCalc::NondelegatingRelease()<br />
// standardowa implementacja<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Implementacja interfejsu IConnectionPoint<br />
HRESULT _stdcall CCalc::GetConnectionInterface(IID __RPC_FAR *pIID)<br />
{ return E_NOTIMPL;<br />
}<br />
HRESULT _stdcall CCalc::GetConnectionPointContainer(IConnectionPointContainer<br />
__RPC_FAR *__RPC_FAR *ppCPC)<br />
{ return E_NOTIMPL;<br />
}<br />
HRESULT _stdcall CCalc::Advise(IUnknown __RPC_FAR *pUnkSink, DWORD __RPC_FAR<br />
*pdwCookie)<br />
{<br />
// Wartość cookie jest nieistot<strong>na</strong> przy jednym połączeniu<br />
*pdwCookie = 1;<br />
}<br />
// oto wskaźnik do interfejsu ujścia<br />
return pUnkSink->QueryInterface(IID_IOutgoingCalc,<br />
(void**)&g_pOutgoingCalc);<br />
HRESULT _stdcall CCalc::U<strong>na</strong>dvise(DWORD dwCookie)<br />
{<br />
g_pOutgoingCalc->Release();<br />
g_pOutgoingCalc = NULL;<br />
return S_OK;<br />
}<br />
HRESULT _stdcall CCalc::EnumConnections(IEnumConnections __RPC_FAR<br />
*__RPC_FAR *ppEnum)<br />
{ return E_NOTIMPL;<br />
}<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Implementacja interfejsu IConnectionPointContainer<br />
HRESULT _stdcall CCalc::EnumConnectionPoints(IEnumConnectionPoints __RPC_FAR<br />
*__RPC_FAR *ppEnum)<br />
{ return E_NOTIMPL;<br />
}<br />
HRESULT _stdcall CCalc::FindConnectionPoint(REFIID riid, IConnectionPoint<br />
__RPC_FAR *__RPC_FAR *ppCP)<br />
{<br />
if (riid == IID_IOutgoingCalc)<br />
return QueryInterface(IID_IConnectionPoint, (void**)ppCP);<br />
return E_NOINTERFACE;<br />
}<br />
///////////////////////////////////////////////////////////<br />
//<br />
// Class Factory I funkcje eksportowane<br />
// standardowa implementacja fabryki klas (jak w pierwszym przykładzie)
Zawieranie i agregacja<br />
Zawieranie (Containment)<br />
• Powtórne użycie (reuse)<br />
• Dziedziczenie implementacji<br />
• Przesłanianie tożsamości obiektów<br />
Zawieranie = Containment<br />
Agregacja = Aggregation<br />
ICalcScientific<br />
ICalc<br />
CalcScientific<br />
ICalc<br />
CalcSimple<br />
IUnknown<br />
IUnknown<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Agregacja (Aggregation)<br />
Punkty połączeń<br />
IUnknown<br />
SERWER<br />
ICalcScientific<br />
CalcScientific<br />
INondelegatingUnknown<br />
IUnknown<br />
IConnectionPoint<br />
ICalc<br />
CalcSimple<br />
KLIENT<br />
ujście<br />
(sink)<br />
IConnectionPoint<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Punkty połączeń<br />
interface IConnectionPoint : IUnknown<br />
{ // Wymień dostępne punkty połączenia<br />
HRESULT GetConnectionInterface([out] IID __RPC_FAR *pIID);<br />
// Podaj pojemnik punktów połączenia<br />
HRESULT GetConnectionPointContainer(<br />
[out] IConnectionPointContainer *ppCPC);<br />
// Przyjmij wskaźnik do mojego ujścia<br />
HRESULT Advise(<br />
[in] IUnknown *pUnkSink,<br />
[out] DWORD *pdwCookie);<br />
// Nie wywołuj już mojego ujścia<br />
HRESULT U<strong>na</strong>dvise([in] DWORD dwCookie);<br />
// Wymień połączenia<br />
HRESULT EnumConnections([out] IEnumConnections *ppEnum);<br />
};<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Punkty połączeń<br />
IConnectionPointContainer : public IUnknown<br />
{<br />
// Wymień dostępne punkty połączenia<br />
HRESULT EnumConnectionPoints([out] IEnumConnectionPoints<br />
*ppEnum);<br />
};<br />
// Z<strong>na</strong>jdź wskazany punkt połączenia<br />
HRESULT FindConnectionPoint([in] REFIID riid,<br />
[out] IConnectionPoint *ppCP);<br />
1
Wielowątkowość w Windows<br />
Wielowątkowość w COM<br />
Marek Mittmann<br />
Grudzień 2002<br />
Wątek (ang.(<br />
thread) ) to sekwencja<br />
wykonywa<strong>na</strong> przez proces, każdy<br />
proces ma do wyko<strong>na</strong>nia jeden lub<br />
więcej wątków<br />
Tam, gdzie wątki korzystają ze<br />
współdzielonych danych, koniecz<strong>na</strong><br />
jest synchronizacja<br />
Do synchronizacji wykorzystujemy:<br />
zdarzenia, sekcje krytyczne, semafory<br />
i wzajemne wykluczanie obiektów<br />
(mutex)<br />
Przedziały<br />
Typy przedziałów<br />
Przedział (ang.(<br />
apartment) ) to kolekcja<br />
obiektów wraz z ich kontekstami<br />
Każdy obiekt <strong>na</strong>leży do dokładnie<br />
jednego przedziału<br />
Przedziały określają zestaw zasad dla<br />
danej grupy obiektów, wymaganych do<br />
poprawnej pracy w środowisku<br />
wielowątkowym<br />
<br />
<br />
<br />
Jednowątkowe (STA)<br />
Mają po jednym wątku, proces może mieć<br />
wiele STA<br />
Synchronizowane przy pomocy kolejki<br />
komunikatów<br />
Wielowątkowe (MTA)<br />
Mają wiele wątków, proces może mieć<br />
tylko jeden MTA<br />
Brak synchronizacji<br />
Neutralne (NA)<br />
Proces może mieć jeden NA<br />
Proces<br />
Proces<br />
Przedziały a wątki<br />
STA<br />
STA<br />
STA<br />
MTA<br />
MTA<br />
<br />
<br />
<br />
Przedziały jednowątkowe<br />
Każdy STA ma tylko jeden wątek, w<br />
pojedynczym procesie może istnieć<br />
wiele STA<br />
Wszystkie komponenty nie obsługujące<br />
modelu wielowątkowego działają w<br />
głównym STA<br />
Wątek deklaruje obsługę modelu STA<br />
przez wywołanie CoInitializeEx z drugim<br />
parametrm równym<br />
COINIT_APARTMENTTHREAD
Przedziały jednowątkowe c.d.<br />
Przedziały wielowątkowe<br />
<br />
<br />
<br />
Wywołania metod w STA są<br />
automatycznie synchronizowane i<br />
dysponowane przy użyciu kolejek<br />
komunikatów<br />
Każdy komponent zewnątrzprocesowy,<br />
który obsługuje model STA, musi<br />
zawierać pętlę komunikatów<br />
Komponent wykonywalny może<br />
blokować wywołania metod w STA za<br />
pomocą filtrów komunikatów (interfejs<br />
IMessageFilter)<br />
<br />
<br />
<br />
W pojedynczym procesie może istnieć<br />
tylko jeden MTA, który z kolei może<br />
zawierać wiele wątków<br />
Wywołania metod w MTA nie są<br />
automatycznie synchronizowane<br />
Wątek deklaruje obsługę modelu STA<br />
przez wywołanie CoInitializeEx z drugim<br />
parametrm = COINIT_MULTITHREAD<br />
<br />
<br />
<br />
<br />
Przedziały neutralne<br />
W pojedynczym procesie może istnieć<br />
tylko jeden NA<br />
Nie ma stałych wątków, obiekty z NA są<br />
zawsze wykonywane przez wątek<br />
wywołujący<br />
Brak wbudowanej synchronizacji<br />
Przedziały neutralne są obsługiwane<br />
tylko przez komponenty<br />
wewnątrzprocesowe<br />
<br />
Przedziały a komponenty<br />
wewnątrzprocesowe<br />
Komponenty wewnątrzprocesowe nie<br />
wywołują CoInitializeEx, , obsługiwany model<br />
wielowątkowości deklarują za pomocą wpisu<br />
w rejestrze (klucz CLSID\InprocServer32<br />
InprocServer32).<br />
Dopuszczalne wartości to:<br />
Apartment – STA<br />
Neutral – NA<br />
Free – MTA<br />
Both – obsługuje STA, NA i MTA<br />
brak – komponent jednowątkowy<br />
Przypisywanie do przedziałów<br />
Współdziałanie przedziałów<br />
Typ przedziału<br />
twórcy<br />
Model wielowątkowości komponentu<br />
Nieokreślony Apartment Free Both Neutral<br />
Proces<br />
Główny STA główny STA główny STA MTA główny STA NA<br />
Obiekt<br />
Pośrednik<br />
Obiekt<br />
Lekki<br />
pośrednik<br />
STA główny STA STA<br />
wywołującego<br />
MTA<br />
STA<br />
wywołującego<br />
NA<br />
STA<br />
MTA<br />
MTA główny STA macierzysty STA MTA MTA NA<br />
Obiekt<br />
Lekki<br />
pośrednik<br />
Obiekt<br />
NA (wątek STA) główny STA STA<br />
wywołującego<br />
MTA NA NA<br />
STA<br />
NA<br />
NA (wątek MTA) główny STA macierzysty STA MTA NA NA
Współdziałanie przedziałów<br />
Typ przedziału twórcy<br />
Główny STA<br />
Model wielowątkowości komponentu<br />
Nieokreślony Apartment Free Both Neutral<br />
bezpośredni<br />
dostęp<br />
bezpośredni<br />
dostęp<br />
dostęp<br />
przez<br />
pośrednika<br />
bezpośred<br />
ni dostęp<br />
dostęp przez lekkiego<br />
pośrednika<br />
Przekazywanie interfejsów<br />
pomiędzy przedziałami<br />
// STA1<br />
IMyInterface* pMyInterface;<br />
CoCreateInstance(CLSID_MyCOMClass, NULL,<br />
CLSCTX_LOCAL_SERVER, IID_IMyInterface,<br />
(void**)&pMyInterface);<br />
void MyThread(IStream* pScream)<br />
{<br />
// Utwórz STA2<br />
CoInitializeEx(NULL,<br />
COINIT_APARTMENTTHREADED);<br />
STA<br />
MTA<br />
NA (wątek STA)<br />
dostęp przez<br />
pośrednika<br />
dostęp przez<br />
pośrednika<br />
dostęp przez<br />
pośrednika<br />
bezpośredni<br />
dostęp<br />
dostęp przez<br />
pośrednika<br />
dostęp przez<br />
lekkiego<br />
pośrednika<br />
dostęp<br />
przez<br />
pośrednika<br />
bezpośredni<br />
dostęp<br />
dostęp<br />
przez<br />
pośrednika<br />
bezpośred<br />
ni dostęp<br />
bezpośred<br />
ni dostęp<br />
bezpośred<br />
ni dostęp<br />
dostęp przez lekkiego<br />
pośrednika<br />
dostęp przez lekkiego<br />
pośrednika<br />
bezpośredni dostęp<br />
IStream* pStream;<br />
CoMarshalInterThreadInterfaceInStream(<br />
IID_IMyInterface, pMyInterface,<br />
&pStream);<br />
DWORD threadid;<br />
CreateThread(0, 0,<br />
(LPTHREAD_START_ROUTINE) MyThread,<br />
(void*)pStream, 0, &threadid);<br />
IMyInterface* pMyInterface;<br />
CoGetInterfaceAndReleaseStream(<br />
pStream, IID_IMyInterface,<br />
(void**) &pMyInterface);<br />
// ...<br />
pMyInterface->Release();<br />
NA (wątek MTA)<br />
dostęp przez<br />
pośrednika<br />
dostęp przez<br />
pośrednika<br />
dostęp<br />
przez<br />
lekkiego<br />
pośrednika<br />
bezpośred<br />
ni dostęp<br />
bezpośredni dostęp<br />
// ...<br />
pMyInterface->Release();<br />
CoUninitialize();<br />
}<br />
Podsumowanie<br />
Podsumowanie c.d.<br />
<br />
<br />
<br />
<br />
Każdy STA może mieć tylko jeden wątek<br />
Komponenty wykorzystujace wątki STA<br />
muszą pobierać i dysponować<br />
komunikaty ok<strong>na</strong><br />
Przekazywanie wskaźników interfejsów<br />
pomiedzy wątkami z różnych przedziałów<br />
powinno odbywać się za pośrednictwem<br />
szeregowania<br />
Każdy wątek korzystający z COM<br />
powinien wywołać CoInitializeEx<br />
<br />
<br />
<br />
<br />
Każdy obiekt jest związany tylko z<br />
jednym, ale przedział może zawierać<br />
wiele obiektów<br />
Główny STA jest tworzony przez wątek,<br />
który pierwszy wywoła CoInitializeEx z<br />
COINIT_APARTMENTTHREAD<br />
Proces może mieć wiele STA, ale tylko<br />
jeden MTA i jeden NA<br />
Dla obiektów wewnątrzprocesowych<br />
<strong>na</strong>leży zdefiniować w rejestrze<br />
ThreadingModel
Kategorie komponentów<br />
Programowanie składnikowe<br />
w modelu COM<br />
Jarosław Francik<br />
maj 2002<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Kategorie komponentów<br />
• Cel: usprawnienie wyszukiwania klas<br />
komponentów<br />
• Pozycje rejestru systemowego:<br />
– W HKEY_CLASSES_ROOT:<br />
• Component Categories – zestawienie kategorii (CATID)<br />
– dla poszczególnych klas:<br />
• Implemented Categories<br />
• Required categories<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Kategorie komponentów<br />
• Domyślne klasy<br />
moż<strong>na</strong> je tworzyć nie z<strong>na</strong>jąc CLSID, a tylko CATID!<br />
• Emulacja klas:<br />
klucz TreatAs<br />
funkcja CoTreatAsClass<br />
• Component Categories Ma<strong>na</strong>ger<br />
interface ICatRegister<br />
interface ICatInformation<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
1
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Programowanie składnikowe<br />
w modelu COM<br />
Jarosław Francik<br />
maj 2002<br />
Trwałość (Persistency)<br />
PODSTAWOWE INTERFEJSY TRWAŁOŚCI:<br />
• IPersist<br />
• IPersistStream<br />
• IPersistStreamInit<br />
• IPersistStorage<br />
• IPersistFile<br />
• IPersistPropertyBag<br />
PODSTAWOWY INTERFEJS IPERSIST<br />
interface IPersist : IUnknown<br />
{<br />
HRESULT GetClassID([out] CLSID *pClsId);<br />
};<br />
OBSŁUGA STRUMIENI<br />
interface IPersistStreamInit : IPersist<br />
{<br />
HRESULT IsDirty();<br />
HRESULT Load([in, unique] IStream *pStr);<br />
HRESULT Save([in, unique] IStream *pStr,<br />
[in] BOOL fClearDirty);<br />
HRESULT GetSizeMax([out]<br />
ULARGE_INTEGER *pSize);<br />
HRESULT InitNew();<br />
};<br />
ZAPIS TRWAŁEGO OBIEKTU<br />
// pobieramy clsid klasy<br />
CLSID clsid;<br />
hr = pPersistent->GetClassID(&clsid);<br />
// zapisujemy clsid (standardowa funkcja)<br />
hr = WriteClassStm(pStream, clsid)<br />
// zapisujemy dane obiektu<br />
hr = pPersistent->Save(pStream, TRUE);<br />
interface ISequentialStream : IUnknown<br />
{<br />
HRESULT Read([out, size_is(cb),<br />
length_is(*pRead)] void *p,<br />
[in] ULONG cb,<br />
[out] ULONG *pRead);<br />
HRESULT Write(<br />
[out, size_is(cb)] void *p,<br />
[in] ULONG cb,<br />
[out] ULONG *pWritten);<br />
};<br />
ODCZYT TRWAŁEGO OBIEKTU<br />
// pobieramy CLSID<br />
CLSID clsid;<br />
hr = ReadClassStm(pStream, &clsid);<br />
// tworzymy nowe wystąpienie obiektu<br />
IPersistStream *pPersistent;<br />
hr = CoCreateInstance(clsid, NULL,<br />
CLSCTX_INPROC_SERVER, IID_IPersistStream,<br />
(void**)pPersistent);<br />
PRACA Z ZESTAWAMI WŁAŚCIWOŚCI<br />
// Wciągamy dane obiektu<br />
pPersistent->Load(pStream);<br />
SPRYTNE FUNKCJE STANDARDOWE<br />
hr = OleSaveToStream(pPersistent, pStream);<br />
hr = OleLoadFromStream(pPersistent, pStream);<br />
interface IPersistPropertyBag : IPersist<br />
{<br />
HRESULT IsDirty();<br />
HRESULT Load([in] IPropertyBag *pPropBag,<br />
[in] IErrorLog *pErrorLog);<br />
HRESULT Save([in] IPropertyBag *pPropBag,<br />
[in] BOOL fClearDirty,<br />
[in[ BOOL bSaveAllProps);<br />
HRESULT GetSizeMax([out]<br />
ULARGE_INTEGER *pSize);<br />
HRESULT InitNew();<br />
};<br />
interface IPropertyBag : IUnknown<br />
{<br />
HRESULT Read([in] LPCOLESTR pPropName,<br />
[in, out] VARIANT *pVar,<br />
[in] IErrorLog *pErrorLog);<br />
};<br />
HRESULT Write([in] LPCOLESTR pPropName,<br />
[in] VARIANT *pVar);<br />
opracowanie: Jarosław Francik
Do czego służą monikery?<br />
Programowanie składnikowe<br />
w modelu COM<br />
Jarosław Francik<br />
czerwiec 2002<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Monikery<br />
• Zastosowanie – dwa w jednym:<br />
– inteligentne <strong>na</strong>zwy obiektów<br />
– inicjalizowanie obiektów<br />
• Co ma jedno do drugiego?<br />
class CPoint<br />
{<br />
CPoint (int x, int y);<br />
void Draw();<br />
};<br />
CPoint *p =<br />
new CPoint(10, 10);<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
interface IPoint<br />
{<br />
HRESULT Draw();<br />
};<br />
IPoint *p = NULL;<br />
CoCreateInstance(clsid,...<br />
IID_IPoint, (void**)&p);<br />
CoCreateInstance nie jest odpowiednikiem<br />
konstruktora: nie ma parametrów wywołania<br />
Monikery – istota działania<br />
Monikery standardowe<br />
<strong>na</strong>zwa obiektu<br />
MONIKER<br />
point:10,10<br />
arkusz.xls<br />
arkusz.xls!Sheet1!R1C1:R10C6<br />
216C83D7-4621-4ffd-A87D-61DD9C3D2A66<br />
plikowy<br />
elementowy<br />
klasowy<br />
złożony<br />
URL<br />
CreateFileMoniker<br />
CreateItemMoniker<br />
CreateClassMoniker<br />
CreateURLMoniker<br />
ścieżka i <strong>na</strong>zwa pliku<br />
obiekt zawarty w innym<br />
obiekcie<br />
„opakowanie” CLSID:<br />
obiekt klasy<br />
CreateGenericComposite superpozycja kilku<br />
monikerów<br />
URL<br />
instancja obiektu<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
wskaźnikowy CreatePointerMoniker<br />
OBJREF<br />
antymoniker<br />
CreateObjrefMoniker<br />
CreateAntiMoniker<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
identyfikuje obiekt<br />
w stanie aktywnym<br />
uszeregowany wskaźnik do<br />
interfejsu IUnknown<br />
odwrotność monikera<br />
Interfejs IMoniker<br />
Stosowanie <strong>na</strong>zw symbolicznych<br />
• Dziedziczy po IPersistStream<br />
• Ciekawsze funkcje składowe:<br />
GetDisplayName<br />
zwraca <strong>na</strong>zwę symboliczną<br />
ParseDisplayName<br />
przekształca <strong>na</strong>zwę symboliczną w moniker<br />
BindToObject<br />
dowiązuje obiekt <strong>na</strong>zywany przez moniker<br />
Reduce<br />
redukuje moniker do <strong>na</strong>jprostszej postaci<br />
ComposeWith<br />
łączy monikery tworząc moniker złożony<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
• Metoda IMoniker::ParseDisplayName<br />
• Funkcja MkParseDisplayName<br />
– dla podanego łańcucha tworzy moniker<br />
– przyjmuje dwa typy <strong>na</strong>zw:<br />
• ścieżka do pliku, np:<br />
c:\moje dokumenty\arkusz.xls<br />
• standardowa <strong>na</strong>zwa obiektu:<br />
ProgID:Nazwa-obiektu<br />
gdzie:<br />
ProgID – zarejestrowany prog id monikera<br />
Nazwa-obiektu – <strong>na</strong>zwa rozpoz<strong>na</strong>wal<strong>na</strong> dla monikera<br />
•przykład dla monikera klasowego:<br />
clsid:216C83D7-4621-4ffd-A87D-61DD9C3D2A66<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
1
Dowiązywanie obiektów<br />
Czas <strong>na</strong> kod…<br />
• Metoda IMoniker::BindToObject<br />
• Funkcja BindMoniker (nieco prostsza w użyciu)<br />
// tworzy kontekst wiązania<br />
IBindCtx *pBindCtx;<br />
hr = CreateBindCtx(0, &pBindCtx);<br />
monikerów raczej nie tworzymy<br />
przez CoCreateInstance!<br />
• Co robi IMoniker:: BindToObject?<br />
– tworzy instancję odpowiedniego obiektu<br />
– inicjalizuje ją<br />
• Jak IMoniker::BindToObject inicjalizuje obiekty?<br />
–zależy od typu monikera<br />
– moniker plikowy: wciąga z pliku; tworzony obiekt<br />
musi implementować IPersistFile<br />
– moniker klasowy: wywołuje CoGetClassObject<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
// utworzenie monikera<br />
ULONG chEaten;<br />
IMoniker *pMoniker = NULL;<br />
hr = MkParseDisplayName(pBindCtx, pszNazwa,<br />
&chEaten, &pMoniker);<br />
// dowiązuje obiekt<br />
ICoś *p<br />
pMoniker->BindToObject(pBindCtx, 0, IID_ICoś,(void**)&p);<br />
pMoniker->Release();<br />
pBindCtx->Release();<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
// monikery nie żyją długo…<br />
// konteksty wiązania też nie<br />
Kod: uproszczenia<br />
Monikery złożone<br />
• Dowiązanie obiektu bez jawnego kontekstu:<br />
zamiast:<br />
IBindCtx *pBindCtx;<br />
CreateBindCtx(0, &pBindCtx);<br />
pMoniker->BindToObject(pBindCtx, 0, IID_ICoś, (void**)&p);<br />
pBindCtx->Release();<br />
wystarczy:<br />
BindMoniker(pMoniker, 0, IID_ICoś, (void**)&p);<br />
moniker plikowy<br />
moniker<br />
elementowy<br />
moniker<br />
elementowy<br />
c:\program files\arkusz.xls Sheet1 R1C1:R10C6<br />
moniker złożony<br />
c:\program files\arkusz.xls!Sheet1! R1C1:R10C6<br />
• Funkcja CoGetObject<br />
robi wszystko: udostępnia obiekt <strong>na</strong> podstawie <strong>na</strong>zwy<br />
HRESULT CoGetObject(LPCWSTR pszDisplayName,<br />
BIND_OPTS *pBindOptions, IID &riid, void **ppv);<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Przykład: OLE Linking<br />
• Dokument złożony (kontener) zawiera utrwalone<br />
dane monikera plikowego dokumentu<br />
włączonego (linked)<br />
• Korzystając z interfejsu IPersistStream<br />
(który implementują wszystkie monikery)<br />
moż<strong>na</strong> z dokumentu utrwalonego odtworzyć<br />
obiekt monikera plikowego<br />
• Moniker plikowy identyfikuje swą <strong>na</strong>zwę (plik)<br />
i dowiązuje zapisany w nim dokument włączony<br />
• Dokument włączony, aby współpracować z<br />
monikerem plikowym, implementuje IPersistFile<br />
Podsumowanie<br />
• Monikery tworzymy za pomocą specjalnych<br />
funkcji (CreateXXXMoniker) lub podając <strong>na</strong>zwę<br />
symboliczną (MkParseDisplayName)<br />
• Monikery implementują IPersistStream, zatem<br />
możemy je przechowywać w strumieniach i<br />
magazy<strong>na</strong>ch<br />
• Utworzony lub odtworzony moniker dowiązuje<br />
<strong>na</strong>zywany przez siebie obiekt (i inicjalizuje go)<br />
• Po dowiązaniu obiektu moniker zazwyczaj jest<br />
usuwany<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
2
OLE: Pojęcia podstawowe<br />
Programowanie składnikowe<br />
w modelu COM<br />
Jarosław Francik<br />
czerwiec 2002<br />
OLE: Object Linking<br />
and Embedding<br />
• OLE – technologia polegająca <strong>na</strong> tworzeniu<br />
złożonych dokumentów<br />
• Przez pewien czas OLE było zbiorczą <strong>na</strong>zwą<br />
technologii z<strong>na</strong>nej obecnie jako COM<br />
• Dokument złożony (compound document)<br />
• Łączenie (linking) – dokument włączony<br />
z<strong>na</strong>jduje się w oddzielnym pliku<br />
• Osadzanie (embedding) – dokument osadzony<br />
jest przechowywany w obrębie dokumentu<br />
złożonego (w magazynie IStorage)<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
OLE: Pojęcia podstawowe<br />
• Kontener (container) – aplikacja obsługująca<br />
dokumenty złożone; klient<br />
• Serwer OLE –serwer udostępniający dokumenty<br />
osadzane lub łączone<br />
• Miniserwer OLE – serwer wewnątrzprocesowy,<br />
nie występuje jako osob<strong>na</strong> aplikacja<br />
(oferuje wyłącznie dokumenty osadzane)<br />
• Pełny serwer OLE (full server) – serwer<br />
zewnątzprocesowy<br />
Osadzanie (embedding)<br />
• Struktura obiektu magazynowego (IStorage)<br />
podsumowanie<br />
dokumentu<br />
root storage<br />
dokument<br />
MS MS Word<br />
arkusz<br />
kalkulacyjny<br />
pula pula obiektów<br />
magazyn obiektów osadzonych<br />
cache<br />
prezentacji<br />
CLSID<br />
Excel’a<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
kontener OLE<br />
obiekt<br />
kliencki<br />
(client site)<br />
Osadzanie (embedding)<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
stro<strong>na</strong> kontenera (klienta)<br />
IOleClientSite<br />
IAdviceSink<br />
IOleClientSite –udostępnia<br />
serwerowi sterowanie<br />
kontenerem<br />
SaveObject –zapisuje<br />
obiekt<br />
ShowObject –wymusza<br />
wyświetlenie obiektu<br />
RequestNewObjectLayout<br />
IAdviceSink –ujście<br />
różnego rodzaju notyfikacji<br />
OnDataChange<br />
OnViewChange<br />
OnRe<strong>na</strong>me<br />
OnSave<br />
OnClose<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IPersistStorage<br />
IOleCache2<br />
IOleCacheControl<br />
IViewObject2<br />
Osadzanie (embedding)<br />
stro<strong>na</strong> serwera<br />
serwer OLE<br />
data<br />
cache<br />
obiekt<br />
zawartości<br />
(content<br />
object)<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
1
Osadzanie (embedding)<br />
stro<strong>na</strong> serwera<br />
Osadzanie (embedding)<br />
stro<strong>na</strong> serwera<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IPersistStorage<br />
IOleCache2<br />
IOleCacheControl<br />
IViewObject2<br />
serwer in-proc<br />
data<br />
cache<br />
ujście<br />
IOleObject<br />
IDataObject<br />
IPersistStorage<br />
IAdviseSink<br />
serwer lokalny<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IPersistStorage<br />
IOleCache2<br />
IOleCacheControl<br />
IViewObject2<br />
• Obiekt danych: IDataObject<br />
– podstawa tzw. Uniform Data Transfer:<br />
dane przesyłane z miejsca w miejsce w<br />
standardowy sposób<br />
– podstawa mechanizmów OLE Clipboard<br />
i Drag and Drop<br />
–Ważniejsze funkcje:<br />
• GetData, SetData – transfer danych<br />
• QueryGetData – „z<strong>na</strong>sz taki format danych?”<br />
• EnumFormatEtc – wymienia formaty danych<br />
• DAdvice, DU<strong>na</strong>dvice – mechanizm podobny<br />
do punktów połączeń; nota bene:<br />
współpracuje z interfejsem IAdviceSink<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Osadzanie (embedding)<br />
stro<strong>na</strong> serwera<br />
Osadzanie (embedding)<br />
stro<strong>na</strong> serwera<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IPersistStorage<br />
IOleCache2<br />
IOleCacheControl<br />
IViewObject2<br />
• Trwałość obiektu: IPersistStorage<br />
–obsługa zapisywania – wykorzystywa<strong>na</strong><br />
przez kontener po to, by zapisać<br />
osadzony dokument w obrębie<br />
dokumentu złożonego<br />
• Kontener dostarcza magazyn danych<br />
IStorage<br />
system dostarcza standardową<br />
implementację IStorage poprzez funkcję:<br />
StgCreateStorageEx<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IPersistStorage<br />
IOleCache2<br />
IOleCacheControl<br />
IViewObject2<br />
• Interfejsy obsługujące cache:<br />
IOleCache2, IOleCacheControl<br />
obsługa obiektu „cache prezentacji”<br />
niezbęd<strong>na</strong> do komunikacji z serwerem<br />
lokalnym<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Osadzanie (embedding)<br />
stro<strong>na</strong> serwera<br />
Osadzanie (embedding)<br />
stro<strong>na</strong> serwera<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IPersistStorage<br />
IOleCache2<br />
IOleCacheControl<br />
IViewObject2<br />
• Reprezentacja wizual<strong>na</strong> obiektu:<br />
IViewObject2<br />
– automatycznie przejmowa<strong>na</strong> przez cache<br />
–Najważniejsze funkcje:<br />
• Draw<br />
• GetColorSet<br />
• GetExtent<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IPersistStorage<br />
IOleCache2<br />
IOleCacheControl<br />
IViewObject2<br />
• Obiekt OLE (IOleObject) i czasowniki<br />
– jeden z <strong>na</strong>jistotniejszych interfejsów,<br />
pozwalających <strong>na</strong> bezpośrednią<br />
komunikację z obiektem OLE<br />
– nigdy nie jest implementowany<br />
przez obiekt cache<br />
–Najważniejsze funkcje:<br />
• SetClientSite<br />
• DoVerb – wywołanie tzw. czasownika, np. w<br />
celu edycji osadzonego dokumentu<br />
• EnumVerbs<br />
• GetExtent/SetExtent<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
2
Osadzanie (embedding)<br />
Aktywacja w miejscu (in-place activation)<br />
• Dodatkowe interfejsy<br />
Łączenie (linking)<br />
• Struktura obiektu magazynowego (IStorage)<br />
• po stronie kontenera:<br />
IOleInPlaceSite: rozszerzenie IOleClientSite<br />
IOleInPlaceFrame: negocjacja wspólnego menu/toolbara<br />
IOleInPlaceUIWindow: dalsze szczsegółowe negocjacje<br />
• po stronie serwera:<br />
IOleInPlaceObject:<br />
IOleInPlaceActiveObject:<br />
podsumowanie<br />
dokumentu<br />
root storage<br />
dokument<br />
MS MS Word<br />
pula pula obiektów<br />
magazyn obiektów włączonych<br />
cache<br />
prezentacji<br />
moniker<br />
arkusza<br />
kalkulacyjnego<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Łączenie (linking)<br />
• Po stronie kontenera (klienta):<br />
–Obsługa łączenia przebiega po stronie kontenera<br />
w ramach tych samych interfejsów, co w przypadku<br />
osadzania.<br />
– Wykorzystuje się moniker, który jest zapisywany<br />
w dokumencie złożonym (magazynie) podobnie,<br />
jak osadzony dokument<br />
• Specjalne interfejsy:<br />
– IOleLink –związany z obsługą monikera<br />
– IPersistFile – wymagany przez monikery plikowe<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
3
Trochę historii…<br />
Programowanie składnikowe<br />
w modelu COM<br />
Jarosław Francik<br />
czerwiec 2002<br />
Kontrolki ActiveX<br />
• VBX – Visual Basic Extensions…<br />
• OLE Controls<br />
– rozbudowa<strong>na</strong> specyfikacja wymuszająca<br />
implementację wielu różnych interfejsów<br />
• ActiveX Controls<br />
– nowa potrzeba: przesyłanie kontrolek przez Internet<br />
–nowa, rozluźnio<strong>na</strong> specyfikacja – „lekkie kontrolki”<br />
ActiveX stał się flagowym okrętem Microsoftu<br />
nowe zamieszanie językowe: ActiveX = COM?<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Definicja ActiveX<br />
• Tylko dwa wymogi:<br />
– implementacja interfejsu IUnknown<br />
–zdolność do samorejestrowania się<br />
• Na podstawie tej definicji większość<br />
serwerów COM to ActiveX<br />
Definicja ActiveX<br />
• Dodatkowy wymóg:<br />
– rozszerzenie funkcjo<strong>na</strong>lności kontrolki ActiveX<br />
w określonych, standardowych obszarach wymaga<br />
bezwzględnego przestrzegania obowiązujących norm<br />
– nie musisz tego implementować, ale jeśli już chcesz,<br />
to musisz to zrobić ściśle wg <strong>na</strong>szych wytycznych<br />
• około 20 standardowych interfejsów, i liczba ta<br />
ciągle rośnie<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
A<strong>na</strong>tomia ActiveX<br />
A<strong>na</strong>tomia ActiveX<br />
Kontener<br />
• cztery obszary<br />
funkcjo<strong>na</strong>lności:<br />
– GUI<br />
– metody<br />
– zdarzenia<br />
– właściwości<br />
IOleInPlaceFrame<br />
IOleInPlaceUIWindow<br />
IOleInPlaceSite<br />
IOleClientSite<br />
IAdviseSink<br />
IOleControlSite<br />
IDispatch<br />
IPropertyNotifySink<br />
IDispatch (events)<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
IOleInPlaceActiveObject<br />
IOleInPlaceObject<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IViewObject2<br />
IOleCache2<br />
IPersistStorage<br />
IPersistStreamInit<br />
ISpecifyPropertyPages<br />
IConnectionPointContainer<br />
IConnectionPoint<br />
IProvideClassInfo2<br />
IDispatch<br />
IOleControl<br />
Kontrolka<br />
ActiveX<br />
Kontener<br />
IOleInPlaceFrame<br />
IOleInPlaceUIWindow<br />
IOleInPlaceSite<br />
IOleClientSite<br />
IAdviseSink<br />
IOleControlSite<br />
IDispatch<br />
IPropertyNotifySink<br />
IDispatch (events)<br />
• GUI:<br />
– zgrubsza ta sama<br />
funkcjo<strong>na</strong>lność, co w<br />
przypaku In-Place OLE<br />
(w wydaniu inprocess)<br />
– liczne kontrolki nie<br />
korzystają z cache<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
IOleInPlace-<br />
ActiveObject<br />
IOleInPlaceObject<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IViewObject2<br />
IOleCache2<br />
IPersistStorage<br />
IPersistStreamInit<br />
ISpecifyPropertyPages<br />
IConnectionPointContainer<br />
IConnectionPoint<br />
IProvideClassInfo2<br />
IDispatch<br />
IOleControl<br />
Kontrolka<br />
ActiveX<br />
1
A<strong>na</strong>tomia ActiveX<br />
A<strong>na</strong>tomia ActiveX<br />
Kontener<br />
IOleInPlaceFrame<br />
IOleInPlaceUIWindow<br />
IOleInPlaceSite<br />
IOleClientSite<br />
IAdviseSink<br />
IOleControlSite<br />
IDispatch<br />
IPropertyNotifySink<br />
IDispatch (events)<br />
• metody:<br />
– realizowane w postaci<br />
dispatch interfejsu<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
IOleInPlaceActiveObject<br />
IOleInPlaceObject<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IViewObject2<br />
IOleCache2<br />
IPersistStorage<br />
IPersistStreamInit<br />
ISpecifyPropertyPages<br />
IConnectionPointContainer<br />
IConnectionPoint<br />
IProvideClassInfo2<br />
IDispatch<br />
IOleControl<br />
Kontrolka<br />
ActiveX<br />
Kontener<br />
IOleInPlaceFrame<br />
IOleInPlaceUIWindow<br />
IOleInPlaceSite<br />
IOleClientSite<br />
IAdviseSink<br />
IOleControlSite<br />
IDispatch<br />
IPropertyNotifySink<br />
IDispatch (events)<br />
IOleInPlaceActiveObject<br />
IOleInPlaceObject<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IViewObject2<br />
IOleCache2<br />
IPersistStorage<br />
IPersistStreamInit<br />
ISpecifyPropertyPages<br />
IConnectionPointContaine<br />
• zdarzenia:<br />
– po stronie kontrolki:<br />
punkty kontrolne<br />
(connection points),<br />
informacja o typach<br />
IConnectionPoint r<br />
– po stronie kontenera: IProvideClassInfo2<br />
dy<strong>na</strong>micznie tworzony<br />
IDispatch<br />
Institute of dispatch Informatics, Silesian interfejs University of Technology, Gliwice, Poland IOleControl<br />
Kontrolka<br />
ActiveX<br />
A<strong>na</strong>tomia ActiveX<br />
A<strong>na</strong>tomia ActiveX<br />
Kontener<br />
• właściwości:<br />
– trwałość właściwości<br />
– obsługa w dispatch<br />
interface<br />
– property pages<br />
IOleInPlaceFrame<br />
IOleInPlaceUIWindow<br />
IOleInPlaceSite<br />
IOleClientSite<br />
IAdviseSink<br />
IOleControlSite<br />
IDispatch<br />
IPropertyNotifySink<br />
IDispatch (events)<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
IOleInPlaceActiveObject<br />
IOleInPlaceObject<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IViewObject2<br />
IOleCache2<br />
IPersistStorage<br />
IPersistStreamInit<br />
ISpecifyPropertyPages<br />
IConnectionPointContainer<br />
IConnectionPoint<br />
IProvideClassInfo2<br />
IDispatch<br />
IOleControl<br />
Kontrolka<br />
ActiveX<br />
Kontener<br />
IOleInPlaceFrame<br />
IOleInPlaceUIWindow<br />
IOleInPlaceSite<br />
IOleClientSite<br />
IAdviseSink<br />
IOleControlSite<br />
IDispatch<br />
IPropertyNotifySink<br />
IDispatch (events)<br />
IOleInPlaceActiveObject<br />
IOleInPlaceObject<br />
IOleObject<br />
IRun<strong>na</strong>bleObject<br />
IDataObject<br />
IViewObject2<br />
IOleCache2<br />
• interfejsy specyficzne:<br />
IPersistStorage<br />
– niewielkie interfejsy<br />
IPersistStreamInit<br />
ibejmujące<br />
ISpecifyPropertyPages<br />
funkcjo<strong>na</strong>lność<br />
IConnectionPointContainer<br />
IConnectionPoint<br />
nieobjętą przez<br />
IProvideClassInfo2<br />
pozostałe interfejsy<br />
IDispatch<br />
Institute of Informatics, Silesian University of Technology, Gliwice, PolandIOleControl<br />
Kontrolka<br />
ActiveX<br />
Podsumowanie<br />
• Kontrolki ActiveX w większości opierają się <strong>na</strong><br />
innych, z<strong>na</strong>nych technologiach (OLE,<br />
connection points, dispatch interfaces)<br />
• Peł<strong>na</strong> funkcjo<strong>na</strong>lność obejmuje <strong>na</strong> tyle dużą<br />
liczbę interfejsów, że w praktyce nigdy nie<br />
implementuje się ActiveX bez specjalizowanych<br />
<strong>na</strong>rzędzi<br />
• Ostatnie zdanie dotyczy w pewnym sensie całej<br />
technologii COM<br />
Institute of Informatics, Silesian University of Technology, Gliwice, Poland<br />
2
IPtr - Smart Interface Pointer<br />
// Use: IPtr spIX ;<br />
// Do not use with IUnknown; IPtr<br />
// will not compile. Instead, use IUnknownPtr.<br />
template class IPtr<br />
{<br />
public:<br />
// Constructors<br />
IPtr()<br />
{<br />
m_pI = NULL ;<br />
}<br />
IPtr(T* lp)<br />
{<br />
m_pI = lp ;<br />
if ( m_pI != NULL)<br />
{<br />
m_pI->AddRef() ;<br />
}<br />
}<br />
IPtr(IUnknown* pI)<br />
{<br />
m_pI = NULL ;<br />
if (pI != NULL)<br />
{<br />
pI->QueryInterface(*piid, (void **)&m_pI) ;<br />
}<br />
}<br />
// Destructor<br />
~IPtr()<br />
{<br />
Release() ;<br />
}<br />
// Reset<br />
void Release()<br />
{<br />
if (m_pI != NULL)<br />
{<br />
T* pOld = m_pI ;<br />
m_pI = NULL ;<br />
pOld->Release() ;<br />
}<br />
}<br />
// Conversion<br />
operator T*() { return m_pI ;}<br />
// Pointer operations<br />
T& operator*() { assert(m_pI != NULL) ; return *m_pI ;}<br />
T** operator&() { assert(m_pI == NULL) ; return &m_pI ;}<br />
T* operator->() { assert(m_pI != NULL) ; return m_pI ;}<br />
// Assignment from the same interface<br />
T* operator=(T* pI)<br />
{<br />
if (m_pI != pI)<br />
{<br />
IUnknown* pOld = m_pI ;<br />
m_pI = pI ;<br />
if (m_pI != NULL)<br />
{<br />
m_pI->AddRef() ;<br />
}<br />
if (pOld != NULL)<br />
{<br />
pOld->Release() ;<br />
}<br />
}<br />
return m_pI ;<br />
}<br />
// Assignment from another interface<br />
T* operator=(IUnknown* pI)<br />
{<br />
IUnknown* pOld = m_pI ;<br />
m_pI == NULL ;<br />
}<br />
// Save current value.<br />
// Assign new value.<br />
// Release the old interf<br />
// Save current value.<br />
// Query for correct interface.<br />
if (pI != NULL)<br />
{<br />
HRESULT hr = pI->QueryInterface(*piid,<br />
void**)&m_pI) ;<br />
assert(SUCCEEDED(hr) && (m_pI != NULL)) ;<br />
}<br />
if (pOld != NULL)<br />
{<br />
}<br />
return m_pI ;<br />
pOld->Release() ;<br />
// Release old pointer.<br />
// Boolean functions<br />
BOOL operator!() { return (m_pI == NULL) ? TRUE : FALSE ;}<br />
operator BOOL() const { return (m_pI != NULL) ? TRUE : FALSE ; }<br />
// GUID<br />
const IID& iid() { return *piid ;}<br />
private:<br />
// Pointer variable<br />
T* m_pI ;<br />
} ;<br />
opracowanie: Jarosław Francik
ATL: ACTIVE TEMPLATE LIBRARY<br />
KRÓTKI WSTĘP DO PROGRAMOWANIA<br />
JAROSŁAW FRANCIK<br />
Biblioteka ATL, rozprowadza<strong>na</strong> wraz z pakietem Visual Studio/C++, jest przez<strong>na</strong>czo<strong>na</strong> do tworzenia modułów COM.<br />
W porów<strong>na</strong>niu z MFC oferuje nie tylko większe możliwości w tym zakresie, ale przede wszystkim gwarantuje z<strong>na</strong>cznie<br />
mniejsze rozmiary skompilowanych modułów – co jest niezwykle istotne, szczególnie w przypadku tworzenia kontrolek<br />
ActiveX przez<strong>na</strong>czonych do instalowania przez Internet.<br />
Podobnie, jak w przypadku MFC, dogłębne poz<strong>na</strong>nie biblioteki ATL wymaga długiego przygotowania. Jed<strong>na</strong>k proste,<br />
ale funkcjo<strong>na</strong>lne moduły moż<strong>na</strong> tworzyć z<strong>na</strong>jąc jedynie <strong>na</strong>jważniejsze elementy tej biblioteki.<br />
Tworzone w oparciu o ATL klasy komponentów zapewniają obsługę standardowych interfejsów COM. Najprostszy z<br />
nich, zwany prostym obiektem (simple object) obsługuje interfejsy IUnknown i IClassFactory, tak więc programista nie<br />
musi (<strong>na</strong> ogół) dbać o ich implementację.<br />
TWORZENIE MODUŁÓW SERWERA COM<br />
Utworzenie modułu serwera COM wymaga wybrania opcji File|New i wskazania ATL COM Wizard jako typu projektu.<br />
W kolejnym oknie ustala się m.in. typ serwera (wewnątrzprocesowy – DLL lub zewnątrzproceoswy – EXE). Po zatwierdzeniu<br />
otrzymujemy szkieletowy moduł DLL lub EXE, wraz z typowymi elementami implementacji (np. w przypadku<br />
DLL funkcjami DllGetClassObject, DllCanUnloadNow, DllRegisterServer i DllUnregisterServer). Moduł ten<br />
nie zawiera początkowo żadnych klas. Poniżej przedstawiono sposób implementacji dwóch prostych klas COM.<br />
TWORZENIE PROSTYCH KOMPONENTÓW – LICZNIK ZWYKŁY<br />
Klasy komponentów tworzy się za pomocą <strong>na</strong>rzędzia dostępnego w menu poprzez Insert|New ATL Object. Następnie,<br />
korzystając z <strong>na</strong>rzędzia Class View moż<strong>na</strong> wprowadzać metody i właściwości.<br />
Poniżej przedstawiono szczegółowy przepis <strong>na</strong> stworzenie prostej klasy komponentów implementujących licznik zliczający<br />
w górę:<br />
1. Utwórz nowy projekt (File|New) wybierając pozycję ATL COM AppWizard. Przyjmij domyślne ustawienia<br />
kreatora (powstanie moduł DLL).<br />
2. Wprowadź nowy obiekt (właściwie: klasę): wybierz opcję Insert|New ATL Object.<br />
3. Gdy <strong>na</strong> ekranie pojawi się okno ATL Object Wizard, wybierz pozycję Objects i kliknij ikonę Simple Object, a<br />
<strong>na</strong>stępnie <strong>na</strong>ciśnij przycisk Next.<br />
4. Wpisz w pole tekstowe Short Name <strong>na</strong>zwę tworzonego komponentu. Kliknij OK.<br />
W tym momencie mamy już utworzoną klasę komponentu. Oz<strong>na</strong>cza to miedzy innymi, że zaimplementowany<br />
jest już interfejs IUnknown, IFactoryClass oraz IDispatch (utworzo<strong>na</strong> klasa ma interfejs podwójny (dual) – o<br />
czym zadecydowało jedno z ustawień w kreatorze).<br />
5. W obszarze ClassView wybierz utworzony dopiero co interfejs i wybierz w menu kontekstowym Add Method.<br />
6. W oknie dialogowym utwórz metodę Inc – bezparametrową. Powtórz operację dla metody Reset.<br />
7. Ponownie korzystając z ClassView utwórz właściwość Value. Określ dla niej typ long i wyłącz zaz<strong>na</strong>czenie<br />
pola Put Function (w ten sposób utworzymy właściwość tylko do odczytu).<br />
8. W obszarze ClassView wybierz klasę implementującą interfejs i wybierz opcję Add Member Variable. Dodaj<br />
zmienną składową long m_val.<br />
9. Dodaj inicjalizację zmiennej w konstruktorze: m_val = 0;<br />
10. W pliku implementacyjnym uzupełnij implementację metod (<strong>na</strong>leży dodać tylko wyróżnione linijki, pozostała<br />
część kodu jest wygenerowa<strong>na</strong> automatycznie.):<br />
STDMETHODIMP CCounter::Inc()<br />
{<br />
m_val++;<br />
return S_OK;<br />
}<br />
STDMETHODIMP CCounter::Reset()<br />
{<br />
m_val = 0;<br />
return S_OK;<br />
}<br />
STDMETHODIMP<br />
CCounter::get_Value(long *pVal)<br />
{<br />
*pVal = m_val;<br />
return S_OK;<br />
}<br />
W tym momencie klasa jest już gotowa do użycia. Moż<strong>na</strong> ją przetestować <strong>na</strong> przykład za pomocą niewielkiego programu<br />
w Visual Basicu.
TWORZENIE KOMPONENTÓW GENERUJĄCYCH ZDARZENIA – LICZNIK WSTECZ<br />
Zdarzenia są generowane za pośrednictwem interfejsu punktu połączenia IConnectionPoint. Poniżej przedstawiono<br />
klasę liczników zliczających w dół, wysyłających syg<strong>na</strong>ł po osiągnięciu zera:<br />
1. Utwórz projekt podobnie, jak w przypadku poprzedniej klasy. Możesz też utworzyć nową klasę w ramach tego<br />
samego modułu i projektu.<br />
2. Utwórz klasę liczników wstecz bazując <strong>na</strong> szablonie Simple Object. Uwaga: w kreatorze obiektów COM<br />
przejdź do zakładki Attributes i wskaż opcję Support Connection Points. Możesz też zaz<strong>na</strong>czyć opcję Support<br />
ISupportErrorInfo.<br />
3. Utwórz metodę Dec oraz właściwość Value (tym razem do odczytu i zapisu).<br />
4. Tak, jak poprzednio, utwórz zmienną m_val, w której przechowywa<strong>na</strong> będzie wartość atrybutu Value. Zainicjalizuj<br />
ją w konstruktorze.<br />
5. Stwórz implementację funkcji składowych:<br />
STDMETHODIMP CCounter::Dec()<br />
{<br />
m_val--;<br />
return S_OK;<br />
}<br />
STDMETHODIMP<br />
CCounter::get_Value(long *pVal)<br />
{<br />
*pVal = m_val;<br />
return S_OK;<br />
}<br />
STDMETHODIMP CCounter::put_Value(long<br />
newVal)<br />
{<br />
m_val = newVal;<br />
return S_OK;<br />
}<br />
Utworzo<strong>na</strong> klasa przypomi<strong>na</strong> podstawową klasę liczników. Obecnie dodamy obsługę IConnectionPoint.<br />
6. Od<strong>na</strong>jdź w ClassView pozycję odpowiadającą interfejsowi połączeń, np. _ICounterEvents.<br />
7. Za pomocą menu kontekstowego i opcji (uwaga!) AddMethod dodaj zdarzenie Zero. Return type ustaw <strong>na</strong> void.<br />
8. Skompiluj projekt. Następny punkt będzie realizowany w oparciu o utworzoną podczas tej kompilacji<br />
bibliotekę typów * .<br />
9. Uruchom implementację punktu połączeń: w tym celu w ClassView zaz<strong>na</strong>cz <strong>na</strong>zwę klasy liczników i wybierz<br />
opcję Implement Connection Point. Zaz<strong>na</strong>cz wyświetloną w okienku <strong>na</strong>zwę interfejsu. Ten krok <strong>na</strong>leży powtórzyć<br />
za każdym razem, gdy doda się nowe zdarzenie.<br />
W tym momencie zostanie stworzo<strong>na</strong> implementacja dla klasy połączenia (zob. plik *CP.H). W klasie tej dostęp<strong>na</strong><br />
jest metoda Fire_Zero wyzwalająca zdarzenie. Uwaga – zdarzenia <strong>na</strong>leży interpretować jako wywołania<br />
interfejsu, który zaimplementowany jest po stronie klienta.<br />
10. Zmień stosownie funkcję Dec:<br />
STDMETHODIMP CCounter::Dec()<br />
{<br />
m_val--;<br />
if (!m_val) Fire_Zero();<br />
return S_OK;<br />
}<br />
11. Jeżeli w p. 2 włączono opcję Support ISupportErrorInfo, możesz uruchomić mechanizm diagnostyki błędów,<br />
wprowadzając <strong>na</strong>stepującą linijkę kodu <strong>na</strong> początku metody Dec:<br />
STDMETHODIMP CCounter::Dec()<br />
{ if (m_val