25 Zasady programowania ole i


Rozdział 25
Zasady programowania OLE i COM
Podstawy modelu COM
Tworzenie serwera automatyzacji OLE
Serwery i kontenery OLE
Programowanie w oparciu o komponenty
Wraz ze wzrostem komplikacji zagadnień związanych z projektowaniem programów w
tak złożonym środowisku jak Windows, korzystającym z wielu różnych interfejsów API (od
ang. application programing interface, interfejs programowania aplikacji) pojawiła się
potrzeba standaryzacji i uproszczenia zagadnień związanych z programowaniem w tym
środowisku, która zaowocowała powstaniem technologii zwanej programowaniem w oparciu
o komponenty (ang. component-based programming).
Przyszłość COM - COM+
Następnym etapem rozwoju COM będzie COM+. Microsoft obiecuje, że COM+ uprości znacznie
programowanie COM, tak aby obiekty COM działały w podobny sposób jak obiekty C++ i co za tym
idzie, mogły być tworzone i usuwane za pomocą słów kluczowych języka C++ new i delete. Mimo że
obecnie Visual C++ nie obsługuje jeszcze COM+, odpowiedni pakiet oprogramowania może pojawić
się na rynku w każdej chwili. : \ : ; i'' ^ , y:.^ : ,
Komponenty (ang. components) są małymi fragmentami oprogramowania (podobnie do
egzemplarzy obiektów klas), które za pośrednictwem odpowiednio zdefiniowanych
interfejsów wykonują ściśle określone zadania. Inaczej niż egzemplarze obiektów klas,
komponenty nie są ściśle powiązane z określonym programem lub komputerem. Można je
pisać w różnych językach, ponieważ mogą porozumiewać się z programami i komponentami
napisanymi w innych językach za pośrednictwem interfejsów.


654 Poznaj Visual C++ 6
Microsoft rozwinął tę technologię do obecnego poziomu i dziś nosi ona nazwę COM (od
ang. Component Objęci Model, model programowania w oparciu o obiekty komponentów).
Być może spotkaliście się z innymi terminami związanymi z tym modelem, takimi jak OLE
(ang. Objęci Linking and Embeding, osadzanie i łączenanie obiektów) czy kontrolki Active X.
Należy jednak pamiętać, że w obu przypadkach są to tylko implementacje techniki COM.
Sama metoda COM jest niezależnym od języka i platformy sprzętowej standardem,
definiującym, w jaki sposób różne obiekty mogą kontaktować się ze sobą za pomocą wspólnie
akceptowanego protokołu.
Najważniejszą rzeczą w obiektach COM są ich interfejsy. Komponenty są swego rodzaju
zamkniętymi skrzynkami zawierającymi implementacje specyficznych, zależnych od języka
programowania funkcji wykonujących zadania, do których realizacji obiekt został
zaprogramowany. Aby móc korzystać z zawartych w komponentach możliwości, potrzebne
narzędzie, które pozwoli na przesyłanie komponentowi odpowiednich parametrów i odbieranie
wyników wykonywanych wewnątrz operacji. Tym narzędziem pośredniczącym między
programem a obiektem komponentu jest interfejs.
W ten sposób można zdefiniować nawet cały interfejs API. Pisząc aplikację możemy
polecić programowi kontaktować się z komponentem będącym serwerem (ang. server)
programu za pośrednictwem obopólnie akceptowanych interfejsów. Nie ma przy tym żadnego
znaczenia w jakim języku programowania serwer został napisany. Możemy również napisać
własne komponenty serwery (o ile zachowamy zgodność definicji interfejsu) i sprzedawać je
jako alternatywę dla innych komponentów istniejących na rynku.
Interfejs Messaging API (MAPI) jest dobrym przykładem zastosowania techniki COM.
MAPI jest po prostu zbiorem standardowych interfejsów obiektów COM wykorzystywanych
przy tworzeniu programów pocztowych. Każdy może pisać własne obiekty COM wykonujące
zadania takie jak przechowywanie wiadomości, transport wiadomości i dostarczających
adresów odbiorców wiadomości. Program Microsoft Exchange jest w znacznej mierze właśnie
zbiorem takich komponentów serwerów, jest jednak również wiele innych implementacji tych
komponentów. Sam kod komponentów może różnić się znacznie w zależności od programu,
ale wszystkie obiekty COM korzystać będą z tych samych interfejsów. Oznacza to, że
programy klienty (ang. client programs) korzystające z usług tych komponentów, takie jak
Microsoft Outlook, mogą wysyłać, odbierać i przechowywać wiadomości za pomocą
kompatybilnych z MAPI komponentów dowolnej firmy. Podobnie dowolny producent opro-
gramowania może dostarczyć własne programy klienty (i wielu tak robi), które korzystać będą
z Microsoft Exchange lub innych komponentów serwerów, nie wiedząc nawet, z którego z
nich korzystają. Jedynym koniecznym warunkiem jest to, aby zarówno programy jak i
komponenty korzystały z identycznych definicji interfejsów zebranych w MAPI.
PATRZ TAKŻE
Jak korzystać w aplikacjach z kontrolek ActiveX mówimy w rozdziale 9.
Jak tworzyć kontrolki ActiveX mówimy w rozdziale 26.
Więcej informacji na temat MAPI znaleźć można w rozdziale 28.


Zasady programowania OLE i COM 655
Interfejsy COM
Interfejs jest definicją zbioru funkcji i ich parametrów. Każdy obiekt COM ma przy-
najmniej jeden interfejs, niektóre jednak oferują kilka, każdy z innym zestawem funkcji.
Konwencja nazewnictwa interfejsów
Interfejsy COM, podobnie jak inne elementy programu, nazywane są w oparciu o funkcję, którą
wykonują. Jednak zgodnie z powszechnie przyjętą konwencją, nazwy interfejsów są zazwyczaj
poprzedzane literą I. Przykładowo: lUnknown, IDis-patch, IMoniker czy IMessageFilter.
Obiekty COM można pisać w każdym języku, który umożliwia pisanie odpowiednich interfejsów.
Niektóre języki są jednak do tego lepiej przystosowane niż inne. Przykładowo język Java działa tu
bardzo dobrze, ponieważ każdy z obiektów języka Java może mieć kilka interfejsów, co w naturalny
sposób pasuje do obiektów COM. Obiekty COM ukrywają za tym interfejsem właściwy kod, więc
program przyzywający funkcję zdefiniowaną w interfejsie uruchomi jej implementację przechowywaną
w obiekcie, która wykona zlecone zadanie.
Standardowa struktura interfejsu COM jest taka sama jak struktura tablicy funkcji wirtualnych
języka C++. Oznacza to, że możemy definiować i implementować interfejsy COM korzystając z
mechanizmu tablicy funkcji wirtualnych.
Zazwyczaj funkcje wirtualne wykorzystujemy, żeby dostarczyć możliwości pokrywania w klasie
pochodnej funkcji klasy bazowej (wpisując w deklaracji funkcji w klasie bazowej słowo kluczowe
virtual). Gdy pokrywamy funkcję wirtualną klasie przypisywana jest tablica funkcji wirtualnych z tą
klasą - vtable.
Tablice funkcji wirtualnych
W języku C++ każdy egzemplarz obiektu przechowywanego w pamięci posiada związaną z nim
tablicę funkcji wirtualnych (aczkolwiek niektóre z tych tablic mogą nie zawierać żadnych elementów i
mieć zerową długość). Każdy element tablicy przechowuje wskaźnik do kodu implementującego
funkcję wirtualną. Zawsze gdy wzywana jest w tym obiekcie funkcja wirtualna, tablica dostarcza w
zależności od potrzeb albo adres funkcji klasy bazowej, albo adres funkcji klasy pochodnej.
W języku C++ możemy zadeklarować klasę zawierającą tylko i wyłącznie funkcje
wirtualne. Klasa taka nazywana jest klasą abstrakcyjną (ang. abstract ciass). Możemy
oczywiście utworzyć obiekt klasy abstrakcyjnej, częściej jednak klasa abstrakcyjna wyko-
rzystywana jest w języku C++ do tworzenia interfejsów COM. Wygląda wtedy mniej więcej
tak:


656 Poznaj Visual C++ 6
ciass lUnknown
{
public:
virtual HRESULT Querylnterface(REFIID riid, LPVOID FAR* ppv0bj)=0;
virtual ULONG AddRef()=0;
virtual ULONG Release()=0;
}
Microsoft stworzył takie definicje interfejsów C++ dla wszystkich obiektów COM, które
obsługują interfejsy, możemy jednak spotkać się również z funkcjami wbudowanymi w
makroinstrukcje, które wykonywać będą dokładnie to samo:
STDMETHOD(Querylnterface)(THIS_ REFIID riid, LPVOID FAR* ppv0bj) PURE;
STDMETHOD_(ULONG, AddRef)(THIS) PURE;
STDMETHOD_(ULONG, Release)(THIS) PURE;
Każdy obiekt COM musi implementować interfejs lUnknown zawierający przedstawione
tutaj trzy metody (funkcje), a każdy interfejs musi implementować kod tych trzech funkcji
zanim przejdzie do definiowania własnych funkcji specyficznych dla niego. Funkcje te pełnią
kilka zadań:
AddRef () - kiedy klient interfejsu otrzymuje wskaźnik do interfejsu, wewnątrz interfejsu
uruchamiany jest mechanizm zwany zliczaniem odwołań (ang. reference coun-ting)
notujący, ilu klientów odwołuje się w danej chwili do interfejsu.
Relase () - kiedy klient pozbywa się wskaźnika do interfejsu, rachunek odwołań jest
zmniejszany o jeden. Kiedy liczba zliczanych odwołań osiągnie zero, obiekt interfejsu jest
niszczony (zazwyczaj niszczy się sam).
Querylnterface () - klient używający wskaźnika do danego interfejsu może spytać o
wskaźnik do innego interfejsu obiektu COM, przesyłając identyfikator (tutaj riid)
poszukiwanego interfejsu. Jeśli taki interfejs istnieje, funkcja AddRef () zwraca wskaźnik
ppv0bj. O identyfikatorach interfejsu (riid) powiemy za chwilę.
Dzięki temu, że te trzy podstawowe funkcje są implementowane przez każdy interfejs,
możemy napisać program klienta pytający o egzemplarz obiektu COM (automatycznie
uruchamiający funkcję AddRef ()). Pomiędzy różnymi interfejsami obiektu poruszamy się za
pomocą funkcji Querylnterface (). Kiedy skończymy pracę z obiektem, dla każdego wskaźnika
do obiektu, który zdobyliśmy, wzywamy funkcję Relase, która poleca obiektowi zniszczyć się,
oczyszczając pamięć (chyba, że jakiś inny program jeszcze z niego korzysta). Funkcje te są
uniwersalne dla wszystkich obiektów COM.
Równocześnie implementując obiekt COM musimy napisać kod, który zliczać będzie
odwołania i zwracać wskaźnik do odpowiedniego interfejsu lub wartość NULL, jeśli interfejs
nie istnieje.


Zasady programowania OLE i COM 657
Oczyszczanie pamięci po obiektach COM
Ponieważ to na obiekcie COM spoczywa odpowiedzialność za zliczanie odwołań do niego, to właśnie
on musi dbać o usuwanie się z pamięci wtedy, gdy wszystkie korzystające z niego programy klienty
przestaną się do niego odwoływać i licznik odwołań osiągnie zero. W C++ możemy taki efekt uzyskać
usuwając wskaźnik this, co spowoduje oczyszczenie pamięci zajmowanej przez obiekt. Po
samozniszczeniu obiektu nie wolno w żadnym wypadku korzystać ze zmiennych składowych obiektu,
ani wykonywać na nim żadnych operacji, ponieważ odpowiednia lokacja w pamięci została już
wyczyszczona.
Interfejs, który zawierałby tylko te trzy wspomniane tutaj funkcje, nie byłby szczególnie przydatny,
dlatego zwykle interfejsy traktują tę definicje jako bazę, dołączając do niej własne funkcje
umożliwiające interfejsowi realizowanie określonych zadań.
Istnieje wiele sposobów implementacji kodu funkcji z tablicy vtable. Jednym z prostszych jest
wyprowadzenie normalnej (nie abstrakcyjnej) klasy C++ z klasy definiującej interfejs. Następnie w
wywiedzionej klasie implementujemy odpowiednie metody.
Identyfikatory interfejsów, identyfikatory klas i identyfikatory GUID
Jak mogliśmy się przekonać, funkcja Oueryinterface () zwraca wskaźnik do innego interfejsu, który
również współpracuje z danym obiektem COM, kiedy tylko prześlemy jej potrzebny nam identyfikator
interfejsu. Każdy interfejs ma swój unikalny identyfikator IID, a każda klasa COM identyfikator CLSID.
Identyfikatory te to 128-bitowe liczby gwarantujące, że żaden z identyfikatorów w żadnym obiekcie
COM na świecie się nie powtórzy. Identyfikatory te nazywane są identyfikatorami GUID (od ang.
Globally unique ID, globalnie unikalny identyfikator).
Pisząc obiekt COM lub definiując nowy interfejs możemy wykorzystać do tworzenia tych
globalnych identyfikatorów program guidgen.exe. Program ten można znaleźć w katalogu ...Visual
Studio\VC98\Tools\Bin\. Po uruchomieniu programu generuje nowy identyfikator GUID i daje
możliwość wyboru pomiędzy czterema różnymi formatami identyfikatora (rysunek 25.1). Wybieramy
format najbardziej przystający do naszych potrzeb i klikamy przycisk C^opy, aby skopiować numer do
schowka. Jeśli wciśniemy przycisk New GUID program guidgen.exe wybierze kolejny unikalny
identyfikator. Nie musimy się obawiać, że przez przypadek wylosuje taką samą liczbę, musielibyśmy żyć
naprawdę bardzo długo.
Korzystanie z programu guidgen.exe
Może się zdarzyć, że w innej wersji Visual C++ program guidgen.exe znajdować się będzie w zupełnie
innym katalogu. W takiej sytuacji możemy użyć polecenia Find menu T\)ols Eksploratora Windows,
które go dla nas odnajdzie.


658 Poznaj Visual C++ 6
Kiedy już znajdziemy program, możemy dołączyć go jako dodatkowe narzędzie do menu Tools
kompilatora Visual Studio za pomocą znajdującego się w tym menu polecenia Customize. W oknie
dialogowym Customize wybierając kartę Tools, aby dodać program guidgen.exe do listy narzędzi.
Create GUID




Choose the desirad format below. then select
"Copy" to copythe results to the dipboard (the
results can then ba pasted into your source
code), Choose "Exit" when done.
:- GUID Format---..-- .-- - ,,-....-.--"._-
..-
j r l. IMPLEMENT_OLECREATE(...)
l r 2. DEFINE_GUID(".)
| <~ i. staticconststruct GUID {...}
l (T 0. Registry Fem-nat (ie. {ewc-or Result ^^.....^ . l {0793B920-CF75-
11d1-8647-004095A12AF9}





Rysunek 25.1. Program guidgen.exe podczas tworzenia globalnego identyfikatora GUID. Typowy
identyfikator GUID w formacie Registry wyglądać będzie mniej więcej tak:
(0793B920-CF75-lldl-8647-004095A12AF9)
Oczywiście w razie potrzeby może również zostać zapisany zupełnie inaczej. Przykła-
dowo, w kodzie C++ ten sam numer identyfikacyjny będzie wyglądać tak:
// (0793B920-CF75-lldl-8647-004095A12AF9) DEFINE_GUID
(name,
Ox793b920, Oxcf75, Oxlldl, 0x86, 0x47, 0x0, 0x40, 0x95, Oxal, Ox2a, Oxf9) ;
Jak wielka może być liczba 128-bitowa?
Dysponując 128 bitami możemy przedstawić dowolną liczbę z przedziału od O do 340 282
366 920 938 460 000 000 000 000 000 000 000.


Zasady programowania OLE i COM 659
Nie każdy potrafi zapamiętać 128-bitową liczbę, dlatego interfejsom i klasom przypisywane są
również łatwiejsze do zapamiętania nazwy robocze. Przykładowo, interfejs lUnk-nown można opisać
jako lDD_lUnknown i za pomocą makroinstrukcji DEFINR_GUID można powiązać takie nazwy ze 128-
bitowym numerem identyfikacyjnym, tworząc statyczną strukturę, którą można następnie przesyłać
funkcji Querylnterface () jako równoważnik identyfikatora interfejsu; Przykładowo, poniższy fragment
kodu zamienia bieżący wskaźnik do interfejsu piDraw na wskaźnik plDD2 do drugiego interfejsu
!DD_IDirectoryDraw2:
IDirectDraw2* plDD2 = NULL;
HRESULT hr = pIDraw->QueryInterface(IID_IDirectDraw2, (LPVOID *)&pIDD2) ;
(Obiekt DirectDrawjest po prostu przykładowym obiektem COM. Więcej informacji na temat jego
zastosowań można znaleźć w rozdziale 28).
Klasy COM poprzedzane są z kolei przedrostkiem CLSID (od globalnego identyfikatora klasy).
Tak więc obiekt DirectoryDraw implementujący kod interfejsu IID_IDirectDraw2 nosić będzie
przykładowo nazwę CLSID_DirectDraw.
Oba identyfikatory, interfejsu i klasy, wyrażone są za pomocą liczb GUID - ich prawdziwe wartości
znaleźć można w pliku DDRAW.h:
DEFINE_GUID( CLSID_DirectDraw,
OxD7B70EEO,0x4340,OxllCF,OxBO,0x63,0x00,
0x20,OxAF,OxC2,OxCD,0x35 );
DEFINE_GUID( IID_IDirectDraw2,
OxB3A6F3EO,Ox2B43,OxllCF,OxA2,OxDE,0x00,
OxAA,0x00,0x89,0x33,0x56 );
PATRZ TAKŻE
Przykład wykorzystania obiektu DirectDraw w funkcji CoCreateinstance () lub też
samodzielnego wykorzystania tego obiektu znaleźć można w rozdziale 28.
Tworzenie egzemplarzy obiektów COM
Kiedy tworzymy egzemplarz obiektu COM, działający proces serwera (ang. server
process) uruchomi obiekt i będzie go podtrzymywał. Proces serwera może być w tym
przypadku naszym programem klientem, biblioteką DLL, plikiem .exe w lokalnym kom-
puterze lub programem innego połączonego z naszym komputera. Każda z tych kategorii nosi
nazwę kontekstu (ang. context); ich krótki opis można znaleźć w tabeli 25.1.


660 Poznaj Visual C++ 6
Tabela 25.1. Różne konteksty wykorzystywane przez działający obiekt COM
Kontekst Znacznik Opis





Serwer z bieżącego
procesu (ang. Inproc
server)
Lokalny serwer (ang.
Localserver)
Serwer z innego
komputera (ang.
Remote server)
CLSCTX INPROC SERVER
CLSCTX LOCAL SERVER
CLSCTX REMOTE SERVER
Kod obiektu działa w ramach tego samego
procesu co przyzywający go program klient
Kod obiektu działa wewnątrz innego programu
.exe, w innym procesie, ale na tym samym
komputerze co program klient
Kod obiektu działa na zupełnie innym
komputerze niż przywołujący go program






Rejestr systemu Windows służy do wiązania identyfikatora globalnego klasy (CLSID
GUID) z plikami .dli lub .exe, które w razie potrzeby tworzyć i zarządzać będą obiektami
COM. Wszystkie klasy COM zarejestrowane w danym momencie w systemie Windows
figurują w postaci identyfikatorów w gałęzi Rejestru HKEY_CLASES_ROOT\CLSID. Pod
każdym, z nich znajduje się folder (ang. key) dla każdego z różnych typów zarejestrowanych
serwerów -InprocServer32, LocalServer32 lub RemoteServer32 (i inne). W każdym z
folderów można znaleźć nazwę pliku .dli lub .exe, który przechowuje kod odpowiedzialny za
tworzenie i zarządzanie obiektem COM.
Przykładowo, na rysunku 25.2 widać okno programu Registry Editor, w którym widać
identyfikator klasy obiektu DirectDraw i jego wewnątrzprocesowy plik obsługujący biblioteki
DLL, ddraw.dll.
Registty Editor





Data
"ddraw.dll"
"Bolh"
gi-Q {D70532'10-CE69-1100^777-000001143C57} _t] \ Name B Q
{D7B7gEEO-j310-11 CF-BOS3-0020AFC2CD35} ^(Default) , ^-OiBSSSBsS
.,^ThreadingModel Bs Q {D824F8<0-ABCB-101B-3B38-OOAAOOOC4F5D}-
i..(l 'S O {DBCE2480-C732-101 B-BE72-BA78E9AD5B27 : _J {DCBF8E30-
9A.IF-HCF-92SE-OOAA0057AD67)) J -tJ





Rysunek 25.2. Program Registry Editor wyświetlający zarejestrowany identyfikator CLSID
Serwery implementują specjalny obiekt COM zwany obiektem klasy (ang. Ciass Objęci). Obiekty
klasy podobne są do innych obiektów COM, jednakże implementują standardowo interfejs iclassFactory.
Przyzwanie funkcji Createinstance () na tym interfejsie tworzy klasę (obiekt) odpowiedniego typu.
Zazwyczaj jednak tworzymy obiekty COM za pomocą funkcji CoCreateinstance () biblioteki COM, aby
utworzyć egzemplarz obiektu lub za pomocą funkcji coGetdassObject (), pobierając wskaźnik do obiektu
fabryki klas. Fabryki klas powinny być w gruncie rzeczy nazywane fabrykami obiektów, ponieważ
tworzą obiekty nie klasy (to programiści tworzą klasy).


Zasady programowania OLE i COM 661
CoCreateInstance() is defined as:
STDAPI CoCreateInstance(REFCLSID rcisid,
LPUNKNOWN pUnkOuter, DWORD dwClsContext,
REFIID riid, LPVOID* ppv);
Tworzenie obiektów komponentów na komputerach połączonych przez sieć
Aby utworzyć obiekt komponentu w takiej sytuacji, należy użyć funkcji CoCreate-lnstanceEx ().
Funkcja ta korzysta z techniki DCOM (Distributed COM) umożliwiającej komunikację między
komputerami w sieci za pośrednictwem RPC (Remote Procedurę Calis, zdalne wywoływanie
procedury). Komputery klienty muszą jednak zostać skonfigurowane za pomocą programu
DCOMCNFG.EXE, żeby móc rejestrować serwery COM z innego komputera.
Pierwszy parametr, rcisid, jest wskaźnikiem do globalnego identyfikatora klasy, której egzemplarz
jest nam potrzebny. Drugiemu parametrowi, pUnkOuter, powinniśmy normalnie przypisać wartość NULL.
Parametr ten wykorzystywany jest w zaawansowanej technice COM zwanej agregacją. Trzeciemu
parametrowi powinniśmy przypisać znacznik CLSCTX_SERVER. Parametr ten, dwClsContext definiuje
potrzebny nam typ serwera. Znacznik CLSCTX_SERVER jest kombinacją znaczników przedstawionych w
tabeli 25.1. Czwarty parametr, riid, jest odwołaniem do globalnego identyfikatora interfejsu potrzebnego
nowemu obiektowi COM. Ostatni parametr, ppv, jest wskaźnikiem do wskaźnika interfejsu w aplikacji
kliencie, któremu przypisany zostanie wskaźnik do interfejsu nowego obiektu COM.
Przykładowo, aby utworzyć egzemplarz obiektu DirectDraw i zdobyć wskaźnik do interfejsu
lDirectDraw2, należy wpisać poniższy fragment kodu:
IDirectDraw2* pIDirectDraw2 = NULL;
HRESULT hr = CoCreateInstance(&CLSID_DirectDraw, NULL,
CLSCTX_ALL, &IID_IDirectDraw2, (void**)&pIDirectDraw2);
Jeśli funkcji uda się wykonać zadanie, utworzony zostanie nowy obiekt DirectDraw, a wskaźnik
pDirectDraw2 wskazywać będzie interfejs IDirectDraw2 nowego obiektu COM.
Pośredniczące biblioteki DLL i szeregowanie
Kiedy program klient uzyskuje wskaźnik do interfejsu obiektu COM, nie ma dla niego znaczenia,
gdzie w pamięci jest zlokalizowany rzeczywisty obiekt. Jednak gdy obiekt COM znajduje się poza
procesem programu klienta, wskaźnik nie może wskazywać bezpośrednio tablicy vtable funkcji
interfejsu; zamiast tego wewnątrz naszego procesu tworzona jest atrapa (ang. stub), która wygląda dla
programu klienta tak jak wskaźnik do lokalnego interfejsu. Kiedy przyzywamy funkcje za
pośrednictwem tego wskaźnika interfejsu w tle uru-


662_____________________________________Poznaj Visual C++ 6
chomiona zostaje technika zwana szeregowaniem (ang. marshaling), łącząca aplikację klienta i obiekt
COM za pośrednictwem pośredniczącej biblioteki DLL (ang. proxy DLL).
Wersje pośredniczących bibliotek DLL
Jeśli dokonujemy szeregowania między komputerami za pośrednictwem DCOM, musimy upewnić się,
że w obu komputerach znajduje się ta sama wersja pośredniczącej biblioteki DLL.
Każdy interfejs jest również zarejestrowany w Rejestrze Windows w gałęzi
HKEY_CLASSES_ROOTMnterface, identyfikowany przez swój globalny identyfikator. Wewnątrz tych
identyfikatorów przechowywane są foldery takie jak ProxyStubClsid32, przechowujące identyfikatory
klas podające lokalizację pośredniczących bibliotek DLL, które mogą szeregować funkcje interfejsu.
Pośrednicząca biblioteka DLL jest odpowiedzialna za przekształcenie parametrów do uniwersalnej
postaci, w której nadawać się będą do transmisji między komputerami. Pośrednicząca biblioteka DLL
może następnie skorzystać ze zdalnego odwołania do procedury (RPC), aby przyzwać funkcję obiektu
COM z innego komputera. Pośredniczące biblioteki DLL mogą być tworzone automatycznie poprzez
napisanie definicji w języku IDL (ang. Interface Definition Language, język definiowania interfejsów) i
przepuszczenie ich przez kompilator Microsoftu IDL (MIDL). Kompilator przygotuje następnie
odpowiedni kod odwołań RPC i kod odpowiedzialny za szeregowanie, nadający się do umieszczenia w
pośredniczącej bibliotece DLL.
Wersje interfejsów
Dawny problem związany z koniecznością zapewnienia zgodności różnych wersji oprogramowania
jest teraz rozwiązany w prosty sposób. Po wypuszczeniu programu w świat wystarczy tylko w
następnych jego wersjach pozostawiać niezmieniony interfejs, z którego korzystały poprzednie wersje.
Oznacza to, że nowsze wersje obiektów COM muszą implementować nowsze wersje interfejsu,
pozostawiając stare wersje nienaruszone. Starsze programy klienci, które potrafią korzystać tylko ze
starego interfejsu, będą przywoływać po prostu stare interfejsy, podczas gdy nowsze programy będą
przyzywać nowe wersje interfejsu.
Standardowa konwencja nazywania interfejsów polega na dodawaniu po nazwie interfejsu numeru
wersji. Przykładowo, jeśli oryginalny interfejs IClassFactory zostanie udoskonalony, aby obsługiwać
licencjonowanie obiektów, fabryka klas może zostać zaopatrzona w nowy interfejs ldassFactory2 z
kilkoma dodatkowymi metodami wykonującymi nowe funkcje fabryki klas. Programy klienty będą teraz
przyzywać interfejs IClassFactory, a następnie za pomocą funkcji Querylnterface () sięgać do nowszego
interfejsu ldassFactory2. Jeśli obiekt COM nie oferuje takiego interfejsu, funkcja Queryln-terface ()
zwraca kod S_FALSE, a zmiennej wskaźnika poszukiwanego interfejsu przypisywana jest wartość NULL.


Zasady programowania OLE i COM 663
Niezależnie od wersji interfejsu, obiekty COM mogą korzystać z tej samej wewnętrznej
implementacji funkcji, które w starej i nowej wersji pozostaną niezmienione.
Automatyzacja OLE
Termin OLE (Object Linking i Embeding, łączenie i osadzanie obiektów) pierwotnie
oznaczał możliwość dołączania obiektów różnych typów do dokumentów innego typu, na
przykład arkuszy kalkulacyjnych Excela do dokumentów Worda.
Dokumenty, które obsługują operacje osadzania i łączenia nazywane są dokumentami
złożonymi (ang. compound). W licznych aplikacjach natknąć się możemy w menu Insert to
samo polecenie Object. Aplikacje, które obsługują tę opcję, zawierają wspomniane wcześniej
dokumenty złożone, dzięki czemu można dołączać do dokumentu obiekty z serwerów
zarejestrowanych na liście programów serwerów OLE.
Dołączane do dokumentu obiekty mogą być albo osadzane (ang. embeded), który to
termin oznacza, że są one zachowywane razem z dokumentem, albo dołączane (ang. lin-ked)
co oznacza, że do bieżącego pliku dołączane jest odwołanie do innego pliku (zwane
monikerem, ang. moniker), który załączany będzie do dokumentu.
Obecnie termin OLE ma znacznie szersze znaczenie, ponieważ obejmuje również technikę
przeciągnij i upuść (ang. drag-and-drop) oraz automatyzację OLE (ang. OLE automation) -
termin ten odnosi się do sytuacji, gdy program przywołuje funkcje innych programów
działających w tle, tak że użytkownik nie ma pojęcia, że korzysta z innego programu.
Microsoft Word jako serwer automatyzacji
Jeśli mamy w komputerze programy Microsoft Outlook i Microsoft Word, łatwo zauważymy, że
tworząc e-mail za pomocą programu Outlook możemy korzystać z tych samych opcji sprawdzania
pisowni, z których korzystaliśmy w Wordzie. Jest to przykład sytuacji, w której Word działa jako
serwer automatyzacji dla programu pocztowego Outlook. Edytor tekstu sprawdza pisownię
przygotowywanej wiadomości, działa jednak w tle, niewidoczny dla użytkownika.
Jak działa interfejs przesyłający
Wszystkie możliwości automatyzacji wywodzą się z jednego z głównych interfejsów COM
iDispatch. Obiekt COM oferujący interfejs przesyłający przechowuje tablicę metod (ang. methods,
zwykłych funkcji), zdarzeń (ang. events, funkcji, które rejestrują związane z serwerem operacje wewnątrz
programu klienta) i właściwości (ang. properties, funkcji które odczytują lub zmieniają wartość
określonej zmiennej obiektu COM). Programy klienci mogą sięgać do zapisanych w tej tablicy narzędzi
podczas działania pro-


664_____________________________________Poznaj Visual C++ 6
gramu (dynamicznie - technika ta zwana jest późnym wiązaniem, ang. łatę binding), aby uzyskać
informacje lub skorzystać z możliwości obiektu automatyzacji.
Dualne interfejsy
Przy okazji technik OLE i COM możemy trafić na jeszcze jeden termin: dualne interfejsy (ang. dual
intelfaces). Obiekt COM może obsługiwać dualne interfejsy udostępniając dostęp do swoich funkcji
interfejsu bezpośrednio przez interfejs COM oraz przez interfejs przesyłający. W ten sposób oferujemy
programowi klientowi możliwości dwóch języków programowania. Szybkie programy C++ mogą
sięgać do obiektów bezpośrednio przez interfejsy COM, a Visual Basie i języki skryptowe mogą sięgać
robić to w oparciu o wolniejszy interfejs przesyłający.
Interfejs przesyłający dodaje do klasy lUnknown cztery dodatkowe funkcje.
" GetTypeinfoCount () - pozwala programowi klientowi sprawdzić, czy informacje o typie są dostępne.
GetTypeinfo () zwraca informacje o typie.
GetDsOfNames () - zwraca pozycje tablicy (zwane identyfikatorami przesyłania, ang. dispatch IDs)
odpowiadające nazwom metod, zdarzeń i właściwości.
lnvoke () - przyzywa jedną z funkcji tablicy, wymaga identyfikatora funkcji i struktury z jej
parametrami VARIANT (struktura VARIANT zostanie opisana za chwilę).
Interfejs przesyłający jest często wykorzystywany w OLE, przykładowo w kontrol-kach Active X,
dokumentach OLE i przez Visual Basie Scripting. Ten system polegający na odnajdywaniu funkcji w
wewnętrznej tablicy jest znacznie wolniejszy od bezpośredniego przyzywania funkcji w interfejsie,
pozwala jednak na większą swobodę. Można utworzyć bibliotekę informacji o typach zapisaną w pliku
.tlb. Biblioteki typów pozwalają Visual C++ szybko skonstruować szkieletowe klasy (zwane
sterownikami przesyłania, ang. dispatch driver). Te szkieletowe klasy akceptują przesyłane parametry i
przekształcają je w tablicę VARIANT po czym przyzywają funkcję lnvoke (), która przywołuje właściwy
obiekt automatyzacji.
Warianty
Ponieważ ta sama funkcja lnvoke () przyzywana jest dla wielu różnych funkcji automatyzacji OLE,
musi mieć jakiś sposób na przesyłanie parametrów (zmiennych) różnych typów w zależności od
przyzywanych funkcji. Robi to przesyłając przyzywanym funkcjom strukturę DISPARAMS wskazującą
tablicę struktur VARIANT. Struktura VARIANT przechowuje po prostu dane różnego typu, które mogą być
szeregowane (ang. marshaling) przez OLE. Struktura ta przechowuje również znacznik typu wariantu
(VARTYPE), zwany vt, informujący, który typ jest wykorzystywany. W tabeli 25.2. pokazane zostały
niektóre z licznych typów, które mogą być przechowywane w strukturze VARIANT. Struktura


Zasady programowania OLE i COM 665
VARIANT może również przechowywać wskaźnik do obiektu, co oznaczane jest poprzez
poprzedzenie nazwy zmiennej literą p (na przykład pival) i dodanie do znacznika typu
znacznika VT_BYREF (na przykład VT_I 4 | VT_BYREF).
Przesyłanie tablic wariantów
Korzystając z klasy COleSafeYariant możemy przesyłać funkcjom OLE całe tablice zawierające dane
typu wariantowego. Klasa ta pozwala zdefiniować typ elementu, liczbę i wymiary tablicy, pozwalając
w jednym wezwaniu przesyłać wielkie bloki danych. Więcej informacji na temat korzystania z tablic
COleSafeArray można znaleźć w dokumentacji Microsoftu.
Tabela 25.2. Niektóre typy danych akceptowanych przez warianty
Typ

Nazwa

Znacznik typu

Opis

unsigned char

bVal

VTUI1

Jednobajtowa wartość bez znaku

short

iVal

VTI2

Dwu bajtowa liczba ze znakiem

long

lVal

VT 14

Czterobajtowa liczba ze znakiem

float

fltVal

VTR4

Czterobajtowa liczba zmiennoprzecinkowa

double

dbVal

VTR8

Ośmiobajtowa liczba zmiennoprzecinkowa

BOOL

boolVal

VT BOOL

Czterobajtowa wartość TRUE lub FALSE

SCODE

scode

VTERROR

Kod błędu obiektów COM

DATĘ

datę

VTDATE

Liczba zmiennoprzecinkowa kompatybilna z klasą
COieDateTime




BSTR
bstrVal
lUnknown punkVal
IDispatch pdispval
VT_BSTR Łańcuch kompatybilny z łańcuchami Visual Basica,
pozwalający się konwertować na łańcuch CString
VT_UNKNOWN Wskaźnik do interfej sulUnknown VT_DISPATCH
Wskaźnik do interfejsu IDispatch






Typy przechowywane w wariantach (ang. vańants) są całkowicie niezależne od języka i zrozumiałe
dla każdego języka pozwalającego programować technikę OLE. Konwersji łańcucha CString biblioteki
MFC w łańcuch BSTR (łańcuch Visual Basica) można dokonać za pomocą funkcji AllocSysString () i
SetSysString () klasy CString, które odpowiednio alokują nowy łańcuch BSTR i wydobywają istniejący
łańcuch BSTR z obiektu CString. Odwrotną konwersję łańcucha BSTR w łańcuch języka C++ można
wykonać za pomocą przekształceń (char*) i (const char*).




666___________________________________Poznaj Visual C++ 6
Klasa coleDateTime akceptuje również bezpośrednie przesłanie struktury VARIANT jako
parametru konstruktora lub też typu DATĘ wydobytego za pomocą przekształcenia (DATĘ) .
Typy wariantowe wykorzystywane są nie tylko przez interfejsy przesyłające. Pojawiają się
również w innych miejscach OLE jako bardzo wygodny środek przechowywania i
transferowania danych różnych typów.
Tworzenie serwera automatyzacji
Liczne aplikacje, takie jak Word, Excel i nawet sam kompilator Developer Studio, mogą
również służyć jako serwery automatyzacji. Aplikacje takie oferują aplikacjom klientom
interfejs przesyłający, który może być wykorzystywany do przywoływania specjalistycznych,
oferowanych przez te aplikacje funkcji.
VB Scripting (Visual Basie Scripting), narzędzie wykorzystywane do pisania makro-
instrukcji, korzysta z tych metod przy tworzeniu i manipulowaniu dokumentami serwerów.
Kiedy piszemy makroinstrukcję dla jednego z tych programów, tak naprawdę piszemy
skrócony formularz Visual Basica, który może przyzywać funkcje interfejsu przesyłającego
dowolnego serwera automatyzacji.
Serwery automatyzacji posiadają oprócz globalnego identyfikatora także skróconą nazwę,
która może być wykorzystana do odnajdywania ich identyfikatorów. Nazwy te prze-
chowywane są w rejestrze w folderze HKEY_CLASES_ROOT zawierającym foldery
odpowiadające globalnym identyfikatorom klas serwerów automatyzacji. Przykładowo
kompilator Microsoft Visual Studio widnieje pod następującą (wiążącą go z odpowiednim
identyfikatorem) nazwą:
HKEY_CLASSES_ROOT\MSDEV.APPLICATION\CLSID =
{FB7FDAE2-89B8-11CF-9BE8-OOAOC90A632C}
Klient może teraz za pomocą funkcji CLSiDFromString (), przesyłając jej skróconą
nazwę (MSDEV.APPLICATION) i wskaźnik do struktury CLSID, pobrać globalny identyfikator
klasy CLSID (FB7FDAE2-89B8-11CF-9BE8-OOAOC90A632C), a następnie zapisać go w
strukturze.
Przeglądanie tablicy działających obiektów
Działające serwery automatyzacji często rejestrują się w tablicy działających obiektów (ang. Running
Object Table), aby programy klienci automatyzacji mogły łączyć się z działającym egzemplarzem
obiektu. Rejestrują się używając tzw. monikera, który jest złożoną nazwą pozwalającą identyfikować
zarówno programy, dokumenty złożone, jak i zbiory danych. Rejestr działających obiektów można
obejrzeć za pomocą narzędzia ...\Microsoft\Visual Studio\Common\Rotview.exe. Po uruchomieniu
program Rotview.exe wyświetli działające w danym momencie w systemie (i zarejestrowane) obiekty.
Kompilator Visual Studio rejestruje swój identyfikator klasy jako moniker postaci: ! (FB7FDAE2-
89B8-HCF-9BE8-OOAOC90A632C). Jeśli załadujemy dokument Worda zobaczymy, że procesor
tekstu zostanie zarejestrowany w tej tablicy.


Zasady programowania OLE i COM 667
Program klient może teraz za pomocą funkcji CoCreateinstance (), wymagającej jedynie wskaźnika
do interfejsu przesyłającego, utworzyć działający w tle egzemplarz programu Developer Studio.
Programy .exe serwerów automatyzacji mogą być uruchamiane ze znacznikiem wiersza poleceń
/Automation, który zapobiega wyświetleniu ich okna na ekranie. W tym trybie z serwerem można się
kontaktować tylko za pośrednictwem interfejsu przesyłającego. W ten sposób zachowuje się Word, Excel
i inne serwery automatyzacji, oferując użytkownikowi swoje funkcje bez informowania go o tym, że
działają.
Szkielet aplikacji dla serwera automatyzacji można utworzyć za pomocą kreatora AppWizard przez
zaznaczenie na stronie trzeciej opcji Automation (rysunek 25.3).
MFC AppWiMid - Step 3 of 6


li
T
L
u













La









Whal compound docurrient supportwould you
iike to l indude?
ff None
r Sontainer
r' M|nl-seiver
i" Full-iewr
<~ Bolhcontainet^d serwer
r : .
r ,- . ;. - : :,i111 ;


What o'her supponwou!d yoii tike to ineSude?
17 ^ytomalion:
F Aiai'^eXCorttrols
Rysunek 25.3. Dodawanie obsługi automatyzacji za pomocą kreatora AppWizard
Jeśli utworzymy szkielet aplikacji oferujący obsługę automatyzacji, zauważymy, że w programie
pojawi się kilka dodatkowych plików. Jednym z nich jest plik .reg zawierający odpowiednie informacje
wpisywane podczas rejestracji nowego serwera automatyzacji. Plik ten potrzebny nam będzie tylko
wtedy, gdy zamierzamy instalować serwer na innych komputerach za pośrednictwem programu
instalacyjnego. Serwer utworzy również automatycznie odpowiednie hasło w Rejestrze, jeśli zostanie
uruchomiony normalnie, bez opcji wiersza poleceń /Automation.
Kreator AppWizard zdefiniuje również automatycznie nowy globalny identyfikator klasy dla
dokumentu, który znaleźć będzie można w nowym pliku .reg. Przykładowo, jeśli utworzymy szkielet
serwera automatyzacji o nazwie Autoserver, w pliku .reg pojawią się następujące informacje dla Rejestru:
HKEY_CLASSES_ROOT\Autoserver.Document = Autose Document
HKEY_CLASSES_ROOT\Autoserver.Document\CLSID =
{D11ED783-CFD8-11D1-931D-444553540000} HKEY_CLASSES_ROOT\CLSID\{Dl1ED783-CFD8-
11D1-93 ID-44 45535400 00} =
Autose Document


668_____________________________________Poznaj Visual C++ 6
HKEY_CLASSES_ROOT\CLSID\(D11ED783-CFD8-11D1-931D-4445535400 00)\ProgId =
Autoserver.Document
HKEY_CLASSES_ROOT\CLSID\{D11ED783-CFD8-11D1-931D-4445535400 00}\LocalServer32 =
AUTOSERVER.EXE
Kolejnym dodanym przez kreator plikiem jest plik .odl. Plik ten zawiera język ODL
(Object definition language), który wykorzystywany będzie do tworzenia biblioteki typów dla
serwera automatyzacji. Kiedy będziemy dodawać do serwera automatyzacji nowe metody i
właściwości, w pliku tym pojawiać się będą nowe hasła opisujące parametry wykorzystywane
przez nowe funkcje. Nie musimy się martwić o obsługę ani o kompilację tego pliku, bowiem
jest on obsługiwany przez kreator CIassWizard i kompilowany automatycznie w momencie
budowania projektu.
Biblioteki typów, programy MkTypLib i MIDL
W przeszłości biblioteki typów były kompilowane z plików .odl (języka ODL) za pomocą narzędzia
MkTypLib. Podobny do niego język IDL (.idi) był wykorzystywany do definiowania interfejsów, nie
potrafił jednak tworzyć bibliotek typów. Microsoft w końcu zracjonalizował tę sytuację rozszerzając
możliwości pliku .idi tak, aby mógł on również definiować informacje zawarte w bibliotece typów.
Nowy kompilator Microsoft IDL (MIDL) potrafi kompilować zarówno pliki .idi, jak i .odl (czyniąc
zbytecznym kompilator MkTypLib). Mimo iż pliki .odl są nadal wykorzystywane, nie są już
niezbędne, ponieważ zawarte w nich deklaracje mogą być umieszczane w plikach .idi.
Listing 25.1 przedstawia przykładowy plik .odl dla serwera automatyzacji Autoserver. Serwer ten
posiada tylko jedną metodę SquareRoot () zadeklarowaną w linii 26 w interfejsie przesyłającym
lAutoserver, który z kolei deklarowany jest w liniach 13-28.
Przedstawiony tutaj program Autoserver posiada metodę SquareRoot (), która zwraca po prostu
pierwiastek kwadratowy z przesłanej mu liczby.
Listing 25. l. LST26_1 .ODL - plik ODL dla serwera automatyzacji Autoserver
1 // autoserver.odl : z niego powstanie biblioteka typów
2
3 // Plik ten zostanie przekształcony przez kompilator MIDL,
4 // aby utworzyć bibliotekę typów (autoserver.tlb).
5
6 [ uuid(DHED784-CFD8-llDl-931D-444553540000) , version(1.0) ]
7 library Autoserver
8 {
9 importlib("stdole32.tlb");


Zasady programowania OLE i COM ________________________669
10
11 // Podstawowy interfejs przesyłający dla CAutoseryerDoc
12
13 [ uuid(DHED785-CFD8-HDl-931D-444553540000) ]
14 dispinterface IAutoserver
15 {
16 properties:
17 // UWAGA - CIassWizard zachowa informację o własności
18 // Zachowaj ostrożność podczas edycji tej sekcji.
19 //((AFX_ODL_PROP(CAutoserverDoc) O
20 //}}AFX_ODL_PROP
21
22 methods:
23 // UWAGA - CIassWizard zachowa informację o metodzie
24 // Zachowaj ostrożność podczas edycji tej sekcji.
25 //((AFX_ODL_METHOD(CAutoserverDoc) @
26 [id(l)] double SquareRoot(doubie dInputVal);
27 //)}AFX_ODL_METHOD
28 .} ;
29
30 // Informacje o klasach dla obiektu CAutoserverDoc
31

32 [ uuid(DllED783-CFD8-llDl-931D-444553540000) ]
33 coclass Document
34 {
35 [default] dispinterface IAutoserver;
36 };
37 //((AFX_APPEND_ODL}}
38 //}}AFX_APPEND_ODL}}
39 };
O Kreator CIassWizard dodaje właściwości OLE tutaj występujące jako metody get i set
umożliwiające sięganie do zapisanych w nich wartości
Tutaj kreator CIassWizard dodaje metody OLE
W tej sekcji dokument deklarowany jest jako klasa z jednym interfejsem przesyłającym
Kreator AppWizard, aby uruchomić automatyzację OLE, dodaje do standardowego kodu
źródłowego szkieletu aplikacji wiele dodatkowych elementów. Przykładowo, funkcja


670_____________________________________Poznaj Visual C++ 6
Initinstance () klasy aplikacji (CMyServerApp) musi inicjować biblioteki OLE i COM, co też robi
przyzywając funkcję Afx01elnit ().
W dołączonym przez kreatora kodzie można również znaleźć linię łączącą szablon dokumentu z
nową zmienną składową klasy C01eTemplateServer, m_server:
m_server.ConnectTeinplate(cisid, pDocTemplate, TRUE);
Ten szablon serwera wywiedziony został z klasy cobjectFactory, która jest implementacją
fabryki klas dla klas OLE. Aplikacje klienty korzystać będą z tej fabryki przy tworzeniu nowego
obiektu dokumentu serwera za pośrednictwem funkcji CoCreateln-stance() lub
funkcjiCoGetCIassObject() iCreateinstance().
Argumenty Automation i Embedded wiersza poleceń
Kiedy aplikacja klient przyzywa funkcję aplikacji będącej serwerem automatyzacji, serwer
automatyzacji (taki jak na przykład Word) musi zostać uruchomiony w tle. OLE załatwia to w ten
sposób, że uruchamia program serwera ze znacznikiem /Automations jako parametrem wiersza
poleceń. Przyzywana z funkcji initinstan-ce () serwera funkcja parseComandLine () przypisuje
obiektowi ccommandLineinfo znacznik m_bRunAutomated. Aplikacja może następnie sprawdzić ten
znacznik, aby stwierdzić, czy program jest uruchamiany jako serwer aplikacji, czy jako samodzielna
aplikacja. Jeśli program zostanie uruchomiony z parametrem wiersza poleceń /Embedded, aplikacja
będzie wiedziała, że jest wykorzystywana do obsługi obiektu osadzonego w dokumencie złożonym.
Jeśli aplikacja jest wzywana przez aplikację klienta jako serwer automatyzacji, może zarejestrować
dowolne obiekty automatyzacji OLE jako gotowe do działania, za pomocą następujących linii w funkcji
Initinstance ():
if (cmdinfo.m bRunEmbedded l| cmdinfo.m bRunAutomated) (
C01eTemplateServer::RegisterAll() ;
return TRUE;
}
Jeśli nie, możemy uruchomić serwer automatyzacji jako normalną aplikację. Wówczas następujące
linie kodu utworzą odpowiednie hasła w Rejestrze systemu Windows:
m_server.UpdateRegistry(OAT_DISPATCH_OBJECT) ;
COleObjectFactory::UpdateRegistryAll() ;
Jeśli przyjrzymy się utworzonej przez kreator CIassWizard klasie dokumentu, również tam
znajdziemy dodatkowy kod związany z automatyzacją.
Identyfikator interfejsu przesyłającego dla dokumentu jest deklarowany jako stała
(static const):


Zasady programowania OLE i COM 671
static const IID IID_IAutoserver =
( Oxdlled785, Oxcfd8, Oxlldl, ( 0x93, Oxld, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0 } };
Sam interfejs przesyłający jest definiowany za pomocą kilku utworzonych przez kreator
makroinstrukcji:
BEGIN_INTERFACE_MAP(CAutoserverDoc, CDocument)
INTERFACE_PART(CAutoserverDoc, IID_IAutoserver, Dispatch) END_INTERFACE_MAP()
Metody w mapie przesyłania (ang. dispatch map) są automatycznie dodawane przez
kolejny zestaw makroinstrukcji, który obsługuje mapę. Przykładowo, metoda SquareRoot ()
dodana do dokumentu programu Autoserver będzie wyglądać mniej więcej tak:
BEGIN_DISPATCH_MAP(CAutoserverDoc, CDocument)
//{(AFX_DISPATCH_MAP(CAutoserverDoc)
DISP_FUNCTION(CAutoserverDoc, "SquareRoot", SquareRoot, VT_R8, VTS_R8)
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
Łańcuch "SquareRoot" definiuje interfejs przesyłający razem z nazwą metody, a znaczniki VT R8 i
VTS R8 definiują typy parametrów i wartości zwracanych przez funkcję (jako ośmiobajtowe liczby
zmiennoprzecinkowe).
Parametry makroinstrukcji DISP_FUNCTION
Pierwszy parametr makroinstrukcji DISP_FUNCTION definiuje nazwę klasy, która implementuje funkcję.
Drugi parametr definiuje zewnętrzną nazwę funkcji -tę nazwę widzieć będą klienci automatyzacji
poprzez interfejs przesyłający. Trzeci parametr podaje nazwę implementującej funkcji wewnątrz
serwera. Czwarty parametr definiuje typ danych VARIANT zwracany przez funkcję. Ostatni parametr
jest listą oddzielonych spacjami typów danych parametru VARIANT przesyłanych funkcji. Parametry
te definiowane są (w pliku AFXDISP.H) jako łańcuch binarnych znaków przekształcany później w
indeksy definiujące typ danych VARIANT.
Nie należy zmieniać tych makroinstrukcji ręcznie, kreator CIassWizard zrobi to za nas,
kiedy będziemy dodawać nowe metody automatyzacji.
Dodawanie metod serwera automatyzacji za pomocą kreatora CIassWizard
1. Aby przywołać kreator CIassWizard, wciśnij CtrI+W lub w menu yiew wybierz polecenie
CIassWizard.
2. Wybierz kartę Automation i upewnij się, że w liście kombinowanej Ciass Name wybrana
jest klasa dokumentu.


672 Poznaj Visual C++ 6
3. Kliknij przycisk Add Method, aby dodać nową metodę.
4. Wprowadź zewnętrzną nazwę metody (ang. extemal name), dla aplikacji klientów, taką jak
SqareRoot (pierwiastek kwadratowy) dla serwera CAutoseryerDoc (rysunek 25.4).
5. Teraz powinnieneś zobaczyć wewnętrzną nazwę (ang. intemal name) metody, służącą do
wzywania przedstawionego niżej zdarzenia. Nazwę tę możemy zmieniać, zazwyczaj
jednak pozostawia się tutaj nazwę wpisaną domyślnie przez kreator.
6. Wybierz typ danych zwracanych przez nową metodę w liście kombinowanej Retum Type
(tutaj double).
Add Method




Extemal
name:
SquareRoot
Interna!
narne:


|SquareRoot
double
Implementation
r :11,,:, i
Parameterlist
Rysunek 25.4. Dodawanie nowej metody do serwera automatyzacji za pomocą kreatora Ciass-
Wizard
7. W liście Parameter List wpisz nazwę (taką jak d!nputVal) pierwszego parametru
przesyłanego przez uruchamiane zdarzenie do aplikacji właściciela.
8. W kolumnie Type listy parametrów wprowadź typ zmiennej (w przykładzie Autose-rver
double).
9. Powtarzaj kroki 7 i 8 dla każdego kolejnego parametru, który powinien być przesłany z
aplikacji klienta do nowej funkcji serwera automatyzacji.
10. Aby dodać nową metodę, kliknij OK. Metoda powinna pojawić się na liście zewnętrznych
nazw funkcji wyświetlanych na stronie Automation kreatora CIassWizard.
11. Aby rozpocząć edycję nowej metody automatyzacji, kliknij przycisk ĘditCode.
Po dodaniu metody możemy wpisać w niej dowolny kod wykonujący zadanie, do którego
serwer automatyzacji jest przeznaczony.


Zasady programowania OLE i COM 673
Metoda SquareRoot () programu Autoserver zwraca po prostu pierwiastek kwadratowy z
liczby jej przesłanej, przywołując funkcję matematyczną sqrt ():
tinclude "math.h" double CAutoserverDoc::SquareRoot(double d!nputVal)
{
return sqrt(d!nputVal);
)
Do skonstruowania metody potrzebna jest zarówno przedstawiona wyżej implementacja,
jak i opisane wcześniej czynności wykonywane w kreatorze CIassWizard. Kreator
automatycznie dodaje do mapy rozsyłania i pliku .odl linie niezbędne do utworzenia informacji
o typie dla nowej metody serwera automatyzacji OLE.
Jeśli zbudujemy teraz serwer automatyzacji, zobaczymy, że plik .odl został skompilowany
razem z naszym kodem źródłowym. W katalogu Debug (lub Relase) pojawi się dodatkowo
nowy plik .llb przechowujący definicje biblioteki typów dla nowych metod interfejsu
przesyłającego.
Biblioteki typów
Należy pamiętać, że o ile statyczne biblioteki (.lib) i biblioteki dynamiczne (.dli)
przechowują kod programu, biblioteki typów (.tlb) przechowują tylko parametry i definicje
zwracanych typów dla serwera automatyzacji. Właściwy kod programu znajduje się
wewnątrz samego serwera. Przykładowo, jeśli załadujemy bibliotekę typów dla Worda,
znajdziemy tam tylko informacje wymagane do przesyłania parametrów do funkcji Worda i
informacje o tym, jakie wyniki dostaniemy z powrotem. Do przywoływania tych funkcji
potrzebować będziemy zainstalowanej wersji Worda.
Możemy wykorzystać bibliotekę typów do utworzenia klasy sterownika przesyłania OLE
w programie klienta (co pokażemy dalej). Biblioteki typów możemy również udostępniać
programistom, którzy chcą w swoich programach przywoływać nasz serwer automatyzacji.
Przykładowo, biblioteki typów dla Worda można ściągnąć ze stron interne-towych Microsoftu
(uww.microsoft. com) i za ich pomocą utworzyć program klienta korzystający z metod Worda.
Tworzenie klienta automatyzacji
Możemy uruchamiać serwery automatyzacji i przywoływać ich metody z dowolnej aplikacji klienta.
Pierwsza rzecz, którą należy w tym celu zrobić, jest inicjowanie bibliotek OLE. Robimy to dodając po
prostu do funkcji initinstance () odwołanie do funkcji Afx01elnit():
BOOL CAutoclintApp::Initinstance() (
Afx01elnit();


674_____________________________________Poznaj Visual C++ 6
Aby uprościć przyzywanie metod serwera automatyzacji, możemy polecić kreatorowi CIassWizard
utworzyć z biblioteki typów (pliku .tlb) klasę sterownika przesyłania (ang. dispatch dii v e r).
Tworzenie klasy sterownika przesyłania na bazie biblioteki typów za pomocą kreatora
CIassWizard
1. Aby przywołać kreator CIassWizard, wciśnij klawisze Cirl+W lub w menu yiew wybierz polecenie
CIassWizard.
2. Wybierz kartę Automation.
3. Kliknij przycisk Add Ciass i w rozwijanym menu wybierz polecenie From a Type Library.
4. Odszukaj odpowiedni plik biblioteki typów, który będzie najpewniej przechowywany między plikami
.tlb, .olb lub .dli.
5. Gdy już odnajdziesz odpowiedni plik, kliknij go dwukrotnie i wybierz opcję Open, aby wyświetlić
okno dialogowe Confinn Ciasses.
6. Możesz zmienić standardowe nazwy klas i plików implementacji tworzonych z importowanych klas
wybierając klasę i wpisując odpowiednie nazwy w polach edycji Ciass Name, Header File i
Implementation File.
7. Kliknij OK, aby zaimportować wybrane klasy. Zostaną one dodane do tworzonego projektu.
Bibliotekę typów z poprzedniego przykładu (autoserwer.tłb), możemy wykorzystać do utworzenia
plików autoserver.cpp i autoserver.h, zawierających klasę sterownika przesyłania. Klasa sterownika
rozsyłania posiada funkcje typu Invoke () wywołujące (za pośrednictwem pomocniczej funkcji)
odpowiednią funkcję interfejsu przesyłającego serwera automatyzacji, definiowaną za pomocą
wspomnianego wcześniej identyfikatora tablicy.
Przykładowo, funkcja programu Autoserver SqareRoot () będzie w klasie sterownika przesyłającego
wyglądać tak:
double IAutoserver::SquareRoot(double d!nputVal) (
double result;
static BYTE parms[] = VTS_R8;
InvokeHelper(Oxl, DISPATCH_METHOD, VT_R8,
(void*)sresult,
parms, d!nputVal) ;
return result;
)
Pierwszy parametr (0x1) funkcji lnvokeHelper () powyżej jest identyfikatorem funkcji SquareRoot
() z tablicy funkcji interfejsu przesyłającego serwera automatyzacji. Drugi parametr jest znacznikiem
informującym, że przyzywana funkcja jest metodą, a nie


Zasady programowania OLE i COM 675
zdarzeniem, czy właściwością. Trzeci parametr definiuje typ zwracanych przez metodę wyników
przechowywany w zmiennej result. Czwarty parametr, parms, definiuje format parametrów. Następujące
po nim parametry są parametrami przyzywanej funkcji, które można za pomocą funkcji pomocniczej
lnvokeHelper() spakować w tablicę DISPARMS, przygotowując je do użycia w funkcji przyzywającej
lnvoke ().
Zanim przyzwiemy odpowiednie funkcje serwera automatyzacji, musimy zdobyć wskaźnik do
obiektu interfejsu przesyłającego. Jeśli przyjrzymy się definiującemu klasę sterownika przesyłania
plikowi nagłówka, zobaczymy, że klasa ta została wywiedziona z klasy coieDispatchDriver. Klasa ta
posiada funkcje składowe pomagające zdobyć wskaźnik do interfejsu przesyłającego.
Przyzywamy metodę CreateDispatch () klasy sterownika przesyłania, podając jej zrozumiałe dla
techniki OLE powiązanie z odpowiednią klasą (przykładowo "Auto-server. document"). Funkcja
CreateDispatch () znajdzie w Rejestrze odpowiedni identyfikator klasy i utworzy egzemplarz obiektu
poszukiwanego serwera automatyzacji. Jeśli to się powiedzie, funkcja zwróci wartość TRUE i będzie
można przywoływać metody sterownika.
Odnajdywanie obiektów automatyzacji w Rejestrze
Obiekty automatyzacji można znaleźć w Rejestrze w folderze HKEY_CLASSES_ ROOT. Wewnątrz
każdego folderu obiektu znajduje się folder CLSID zawierający standardowy łańcuch ze 128-bitowym
globalnym identyfikatorem CLSID identyfikującym klasę automatyzacji. , ^
:
Listing 25.2 przedstawia fragment kodu tworzący egzemplarz nowej importowanej klasy sterownika
przesyłania. Następnie tworzy interfejs przesyłania i przyzywa jedną z metod serwera automatyzacji.
Możemy napisać opartą na oknie dialogowym aplikację Autoclient, która przetestuje kod serwera
automatyzacji. Kod przedstawiony w listingu 25.2 dodamy na końcu funkcji OninitDialogO (w pliku
autoclientDIg.cpp). Dyrektywa #include z linii 2 listingu powinna zostać dołączona na początku pliku.
Jeśli teraz zbudujemy i uruchomimy aplikację, zobaczymy okno komunikatu widoczne na rysunku
25.2. W tle automatycznie uruchamiany jest serwer automatyzacji, co umożliwia korzystanie z funkcji
wyliczającej pierwiastek kwadratowy.
Listing 25.2. LST26_2.CPP - inicjowanie i przyzywanie funkcji serwera automatyzacji za pośrednictwem
klasy sterownika przesyłania
1 // Załącz nagłówek klasy sterownika przesyłania
2 #include "autoserver.h"
3


676 ___ _____________ __________________Poznaj Visual C++ 6





4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//.. Deklaracja funkcji
l f ** Inicjuj klasę sterownika przesyłania
IAutoserver IAutoserver;
// ** Utwórz egzemplarz serwera i połącz go
// ** z jego interfejsem przesyłającym
if (IAutoserver.CreateDispatch("Autoserver.document")) O
{
// ** Przyzwij metodę SquareRot serwera
double dNumber = 36.0;
double dRoot = IAutoserver.SquareRoot(dNumber);
// ** Wyświetl wynik
CString strMsg;
strMsg.Format("Root of %f = %f\n",dNumber,dRoot) ;
AfxMessageBox(strMsg) ;





O Funkcja CreateDispatch () odnajduje globalny identyfikator klasy odpowiadający zdefiniowanej
nazwie i przyzywa funkcję CoCreateinstance () przywołującą interfejs przesyłający
W linii 2 listingu 25.2 załączamy plik nagłówka zawierający definicję klasy sterownika przesyłania
(utworzonej z biblioteki typów). W liniach 6-21 przyzywamy serwer automatyzacji. Egzemplarz obiektu
klasy sterownika przesyłania deklarowany jest w linii 7. W Unii 11, aby utworzyć obiekt serwera
automatyzacji i przechować wskaźnik do jego interfejsu przesyłającego (w zmiennej składowej
m_lDispatch), przyzywana jest funkcja składowa CreateDispatch (). Jeśli połączenie z interfejsem się
powiedzie, w linii 15 przyzywana jest metoda SqareRoot (), a następnie otrzymana od niej wartość
pierwiastka zapisana w zmiennej dRoot jest w linii 20 wyświetlana w oknie komunikatu. Efekt widać na
rysunku 25.5.
Kiedy klasa interfejsu lAutoserwer wyjdzie poza zakres kompetencji klasy sterownika, interfejs
rozsyłania zostanie automatycznie usunięty przez funkcję destruktora klasy.
Możemy także usunąć sterownik przesyłania za pomocą jego funkcji składowej Rela-seDispatch ().
Sterownik posiada również metody AttachDispatch () i Detach-Dispatch (), automatycznie przyłączające
i odłączające wskaźnik do obiektu serwera automatyzacji.


Zasady programowania OLE i COM 677







!:B 51e Edit YIBW Insert Project Debug Tools Wind
i| iii iś H i0 ' %i : Q '


; i r-
Aiitn'-

,pt'i ,
vyv-xf;.::
.;.:"

'liJ'Ailn^^prr,bers)J| ^OnInifDialog .^J^''' ; |1^ ^ .^ ! : ,.
fes^^t



^^i^i:^
^

^SS:!^i^s;
;:'i^











,:;' a

S
it



';" i;' ; i

pen(IMenu(
MF

.SEPARATOR) ;



^ ^'o B a p

pendMenu(MFSTRIN8. IDMABOUTBOX. strAhoutMenu);



)

Sat
thi.}

iccii for thi3 dialoa

Th
^^r^ "002 this autotaatŁcally :?^



// wnsn '-.
Setlconfm

iicon^^RuEp^^OZiEElliBi^HBH"



/''

y, Setlcon('mhlcon. FALSE);





S

/f\ Rootof 36.000000 ' 6.000000 ' 'ItiDO: Acid ;3%tra ir-:';i
^J'



y

S

I
A
u
-f

tosarv9
(lAutos

r IAutoserver;
erver.Creatf?Di

r""

"OK"" ""l|

) )





(













'":'





double

dNurober - 36.0;













dulible

dRoot IAutoserver.SquareRoot(

dNumber);



CString strMsg; ";'

atrMsg.Foniiat("Boot of
%f\n",dbJiiinher,dRoot5; '^

h











" .:..^^"^::.:i..^

;"...:-;..,... ,: - :';'iiiE

^
Conte

>
t

"l

J
l

NamB.^^.illllf.^

V6!U&
^^II^SMWS^II^iw^^^y^^fcf;

'1
Nome





Vnlue



*









.(











-







'is'
B

a i i>.*" .^.^^.^ .^^^^^^^^^^^^^^.... .^.^^







[1 "^rn\wachi ^^waziiiwaisagsswga^iafc

BBiSE^fiE^fŁ ;:[i:jii:LB81il^Sll^ttlRriS]?SS' Rysunek
25.5. Przykład ilustrujący działanie automatyzacji OLE
Kontenery OLE, serwery i miniserwery
Serwer OLE jest aplikacją, którą można uruchamiać wewnątrz okna aplikacji kontenera.
Przykładowo, arkusze kalkulacyjne Excela mogą być dodawane i edytowane wewnątrz Worda.
Program Word jest w tym przypadku kontenerem OLE a Excel serwerem OLE.
Dokumenty aktywne
Razem z terminami pojemnik OLE i kontener OLE pojawia się też często termin dokument aktywny. Ta
stosunkowo nowa technika związana z kontrolkami ActiveX rozszerza możliwości architektury
kontener/serwer OLE, pozwalając osadzonemu obiektowi przejąć kontrolę nad całym obszarem
roboczym kontenera (a nie tylko nad małym wycinkiem) i bezpośrednio manipulować obramowaniem,
menu i paskami narzędziowymi kontenera. Więcej informacji na ten temat można znaleźć w
dokumentacji Microsoftu w materiałach związanych z interfejsem IGleDocument i pokrewnymi.
Aplikacje mogą równocześnie pełnić obie role, być zarówno pojemnikami, jak i ser-
werami OLE. Dobrym przykładem są tu programy Word i Excel. Możemy zarówno uru-
chamiać Excela wewnątrz Worda, jak i na odwrót.


678_____________________________________Poznaj Visual C++ 6
Normalny serwer może działać jako obiekt wspomagający inny program oraz jako
samodzielna aplikacja. Miniserwery natomiast można uruchamiać tylko wewnątrz programu
kontenera.
Kontenery OLE obsługują polecenie Object (Obiekt) menu Insert (Wstaw). Serwerem
OLE jest na przykład, aplikacja WordPad systemu Windows. Możemy użyć programu
WordPad, żeby obejrzeć listę innych serwerów OLE. Klikamy polecenie Object w menu
Insert. Pojawi się lista serwerów OLE, które można załączyć do dokumentu WordPad.
Dołączany obiekt serwera OLE może być edytowany wewnątrz prostokątnego obszaru
ograniczonego utworzoną z ukośnych linii grubą ramką, nazywaną również wewnętrznym
obramowaniem (ang. in-place frame). Proces ten nosi nazwę wewnętrznej (ang. in-place
actiyation) aktywacji i jest inicjowany przez przesłanie serwerowi przez kontener wartości
znaczników zwanych słowami (ang. verbs). Kiedy obiekt serwera jest nieaktywny, ramka
znika, a odpowiedni obraz wewnątrz ramki wyświetlany jest w takiej postaci w jakiej był, gdy
ramka ostatni raz była aktywny (jest teraz przechowywany i wyświetlany przez odpowiedni
metaplik). Gdy obiekt jest aktywny, ramka jest ponownie wyświetlana, a menu serwera
dodawane jest do menu kontenera.
Dane osadzonego dokumentu mogą być serializowane przez dokument kontenera za
pomocą standardowych funkcji serializujących dokumentu. Dokument kontenera przesyła
obiektowi CArchive dokumenty serwerów, a obiekt dokona serializacji danych serwera.
Dokumenty złożone: osadzanie i dołączananie
Chociaż osadzane dokumenty są przechowywane całkowicie wewnątrz dokumentu złożonego
aplikacji, dołączane obiekty zachowane są jako monikery. Moniker to mały obiekt, który
jednoznacznie definiuje miejsce, w którym naprawdę znajdują się dane i sposób ich wyświetlania,
kiedy oglądamy je wewnątrz aplikacji klienta.
Bazowy szkielet serwera OLE i kontenera OLE można utworzyć za pomocą kreatora AppWizard,
niemniej aby działały prawidłowo, pewne rzeczy trzeba zaprogramować własnoręcznie.
Bazowy szkielet serwera OLE uzupełniany jest o dwie dodatkowe klasy umożliwiające wewnętrzną
edycję. Jedna z tych klas to klasa elementu (ang. item) serwera wywodzona z klasy CO!eServerltem.
Służy ona do reprezentowania osadzonego w kontenerze elementu i dostarcza funkcji OnDraw ()
odmalowujących na żądanie kontenera osadzony obiekt. Klasa ta posiada również funkcję OnDoVerb()
obsługującą polecenia wyświetlenia, ukrycia lub zamknięcia osadzonego obiektu wysyłane przez
kontener. Kontener może również kontaktować się z serwerem za pośrednictwem oferowanego przez
element serwera interfejsu lOleObject.
Drugą dodatkową klasą jest okno obramowujące wewnętrznego obiektu wywodzone z klasy
colelPFrameWnd. Klasa ta jest wykorzystywana do zamieszczania kodu imple-


Zasady programowania OLE i COM____ __ __ __ 679
mentującego prywatne paski narzędziowe serwera i polecenia menu w miejsce poleceń kontenera w
momencie, gdy uaktywniany jest serwer.
Do szkieletu kontenera dodawana jest tylko jedna dodatkowa klasa wywodzona z klasy
COleClientItem. Klasa ta posiada liczne funkcje pomagające utworzyć i obsługiwać elementy osadzone
w dokumencie złożonym. Dostarcza również dodatkowe połączenie między kontenerem a serwerem,
umożliwiając komunikację za pomocą słów przez funkcję DoYerb () oraz kilku funkcji przeznaczonych
do obsługi właściwej pozycji elementu serwera i jego statusu (aktywacji, dezaktywacji).
Dopisując własny kod do tych dwóch klas serwera i klasy kontenera możemy zaprogramować
bardziej zaawansowane możliwości edycji elementu serwera osadzonego w kontenerze. Warto przy tym
wiedzieć, że ani serwer nie musi wiedzieć, w jakim obiekcie jest osadzany, ani kontener, jaki obiekt
gości. Tak długo jak serwer i jego klienty stosują się do tych samych standardów, kontener i serwer mogą
być łączone bez problemów, co sprawi, że dla użytkownik będzie miał wrażenie, iż korzysta z jednej
aplikacji.


Wyszukiwarka

Podobne podstrony:
Zasady Programowania Strukturalnego
Zasady programowania imprez turystycznych
25 CNC programming
8 Zasady programowania w języku stykowo przekaźnikowym materiały wykładowe
Zasady programowania treningu
25 wymowek programistow
zasady programu senses K1009
JavaScript Zasady programowania obiektowego
Zasady rachunkowości w zakresie prawa podatkowego w Polsce
Fundacje i Stowarzyszenia zasady funkcjonowania i opodatkowania ebook
zestawy cwiczen przygotowane na podstawie programu Mistrz Klawia 6
Ogolne zasady proj sieci wod kan
Międzynarodowy Program Badań nad Zachowaniami Samobójczymi
Zasady Huny Pigułka
52 (25)

więcej podobnych podstron