|
|
zusammengestellt von C. Herbst als VS-Scheinaufgabe
|
|
Microsoft Distributed Component Object Model
Das folgende Tutorial soll einen Überblick über DCOM, seine Anwendung und Architektur geben. Da es sich bei DCOM um eine Erweiterung des Microsoft Component Object Models handelt, wird zunächst der grundlegende Aufbau sowie die Funktionsweise des COM beschrieben, worauf dann eine Erläuterung des DCOM folgt.
Beim COM handelt es sich um Microsofts Variante eines Models, daß die Zusammenarbeit verschiedener Applikationen ermöglichen soll. Es handelt sich um ein objektbasiertes Programmiermodell nach dem Client / Server - Prinzip. Hauptaufgabe des COM ist es, einem Client zu ermöglichen, auf die Dienste eines Servers zuzugreifen, unabhängig davon wer sie wann und mit welcher Sprache erstellt hat (sowohl die Clients als auch die Server). Um dies zu erreichen wird innerhalb des COM ein binäres Format festgelegt, wie die auszutauschenden Objekt im Speicher abzulegen sind. Dieses festgelegte Format ermöglicht es jeder Programmiersprache die in der Lage ist Objekte in der geforderten Form im Speicher abzulegen, COM - Objekte zu erstellen. Ein COM - Objekt stellt seine Dienste einem anderen COM - Objekt durch ein (oder auch mehrere) Interface zur Verfügung. Die Module in denen COM - Objekte enthalten sind (.EXE oder .DLL) werden als COM - Server bezeichnet.
Bei COM - Objekten handelt es sich um die Instanzen zuvor definierter Klassen. Ein Unterschied besteht allerdings in deren Bezeichnung, die nicht in umgangsprachlicher Form, sondern durch einen eindeutigen Identifikator (Class Identifier CLSID) geschieht. Es handelt sich hierbei um einen 128 - Bit Wert der sicherstellen soll, daß auch tatsächlich Eindeutigkeit im System vorliegt und somit Namenskonflikte vermieden werden. Der Identifikator für ein COM - Objekt muß bereits bei der Entwicklung festgelegt werden, da er für die späteren Nutzer lediglich in binärer Form vorliegen und somit nicht mehr geändert werden kann. Um die Erzeugung eines solchen eindeutigen Schlüssels zu ermöglichen unterstützt das COM eine entsprechende API mit der Bezeichnung "CoCreateGuid". Diese API kann zur CLSID - Generierung genutzt werden, wie dies zum Beispiel durch die Programme GUIDGEN.EXE sowie UUIDGEN.EXE geschieht, die dem Microsoft Visual C++ - Compiler beiliegen. Die so erstellten CLSID's werden in den Headerdateien abgelegt, da sie entweder nur von Entwickler des jeweiligen COM-Objektes benötigt werden, oder aber von Entwicklern die dieses Objekt in einem Ihrer Projekte weiterverwenden möchte.
Bild1 : GUIDGEN
Die Interfaces bilden die Schnittstelle zu einem COM - Objekt, da nur durch sie Aktionen mit einem solchen Objekt durchgeführt werden können. Bei diesen Interfaces handelt es sich um Gruppen inhaltlich gleicher Funktionen, was anhand folgender Abbildung eines TerminPlaner- COM-Objektes verdeutlicht werden soll:
Bild2 : Interface
Das COM - Objekt in der Abbildung besitzt zwei Interface : IKopiereDaten und ISetzeDaten ("I" ... Interface) mit den jeweiligen Funktionen. Wie das Objekt besitzt auch jedes Interface einen eindeutigen Identifikator (interface identifier IID) der auch mittels der oben erwähnten API und den entsprechenden Tools erzeugt werden kann und ebenso verwendet wird. [Sowohl die Gruppe der CLSID's als auch die der IID's gehören zu den sogenannten GUID's, (Globally Unique Identifiers) deren Format folgendermaßen festgelegt wurde:
typedef struct GUID
{
DWORD Data1; //32 Bit
WORD Data2; // 16 Bit
WORD Data3; // 16 Bit
Byte Data4[8]; //64 Bit
}
|
]
Beispiel:
// {CCB6B240-E444-11d2-A21F-D46029C1132C}
DEFINE_GUID(<<name>>,
0xccb6b240, 0xe444, 0x11d2, 0xa2, 0x1f, 0xd4, 0x60, 0x29, 0xc1, 0x13, 0x2c);
Der Zugriff auf die Funktionen geschieht mittels eines Zeigers auf das Interface. Um
beispielsweise auf KopiereDaten zuzugreifen wird ein Zeier auf das Interface IKopiere-
Daten benötigt. Um auf die Funktion SetzeDaten zugreifen zu können, wird ein Zeiger auf das
Interface ISetzeDaten benötigt. Der Wechsel des Zugriffes von einem Interface auf ein anderes
wird als "interface navigation" bezeichnet. Um diesen Wechsel zu ermöglichen MUSS jedes
Interface die Funktion "QueryInterface" implementieren. Diese Funktion benötigt zwei Paramter,
zum einen die IID des gewünschten Interfaces und zum anderen einen
Verweis, wo der Zeiger auf das Interface abgelegt werden soll.
Beispiel:
HRESULT hr; IKopiereDaten *pIKopiereDaten; // Zeiger auf das IKopiereDaten - Interface ISetzeDaten *pISetzeDaten; // Zeiger auf das ISetzeDaten - Interface //Wechsel des Interfaces von ISetzeDaten auf IKopiereDaten hr = pISetzeDaten->QueryInterface(IID_IKopiereDaten, &pIKopiereDaten); |
Neben QueryInterface existieren noch zwei Funktionen die jedes Interface implementieren muß : AddRef und Release. Diese drei Funktionen stellen die Grundfunktionalität jedes Interfaces dar. Sie wurden zusammengefaßt in einem Interface mit der Bezeichnung IUnknown . Um zu gewährleisten das jedes Interface diese Grundfunktionalität besitzt, muß jedes neue Interface von IUnknown abgeleitet werden (erben). Die Funktionen AddRef und Release werden zum sogenannten LifeTime - Management eines COM - Objektes benötigt. Ein COM - Objekt wird durch einen Client erzeugt sobald es benötigt wird und zerstört, sobald es nicht mehr benötigt wird. Da ein COM - Objekt mehrere Clients besitzen kann muß gewährleistet werden, daß es nicht von einem dieser Clients zerstört wird solange noch andere darauf zugreifen. Aus diesem Grund besitzt jedes Objekt einen sogenannten Referenz - Counter, der durch AddRef erhöht und durch Release verringert wird. Wird ein COM - Objekt erzeugt wird der Counter auf 0 gesetzt. Sobald ein Interface - Pointer mittels der QueryInterface - Funktion einem COM - Objekt "zugeordnet" wird, ruft das COM - Objekt die Funktion AddRef auf, um den ReferenzCounter zu erhöhen. Sobald ein Client die Nutzung eines Interfaces beendet ist es Aufgabe des Clients die Funktion Release aufzurufen. Beispiel :
class CBeispielObjekt : IUnknown
{
private:
ULONG cRef;
}
//AddRef
ULONG CBeispielObjekt::AddRef(void)
{
return ++cRef;
}
//Release
ULONG CBeispielObjekt::Release(void)
{
cRef--;
if (cRef == 0)
{
delete this;
}
}
|
Zur Definition eines Interfaces wird die Interface Definition Language (IDL) verwendet. Sie besitzt eine C-aehnliche Syntax und dient zur Definition von COM – Elementen wie Objekte, Interface und Type – Bibliotheken. Um beispielsweise ein Objekt zu beschreiben ist folgende Syntax notwendig : [attributes] elementname typename {memberdescriptions} Die Definition des ISetzeDaten Interfaces (siehe Bild 2) ist folgendermaßen möglich :
//
//SetzeDaten.idl
//
import "unknwn.idl";
//IID_ISetzeDaten
//attribute fuer das ISetzeDaten - interface
[
object,
uuid(e01e2040-5afc-11d3-8e80-00805f91cdd9),
helpstring("ISetzeDaten - Interface.")
]
//Deklaration des IsetzeDaten - Interface
interface ISetzeDaten: IUnknown
{
//liste von funktionsdefinitionen fuer durch das interface
//unterstuetzte funktionen
//
//[attributes] returntype [calling convention] funcname(params);
//
[propget, helpstring("Setzt den Namen des Termineintrags.")]
HRESULT SetzeName([in] LPSTR lpszName);
[propput, helpstring("Setzt den Namen des Termineintrags.")]
HRESULT SetzeDaten([in] LPSTR lpszName);
[propput, helpstring("Setzt den Namen des Termineintrags.")]
HRESULT SetzeDatum([in] LPSTR lpszDatum);
}
//LIBID_Terminplaner
//attribute der type library
[
uuid(e01e2041-5afc-11d3-8e80-00805f91cdd9),
helpstring("Terminplaner Type Library."),
version(1.0)
]
//Definition der Terminplaner type library
library Terminplaner
{
//CLSID_Terminplaner
//Attribute des Terminplaner objektes
[
uuid(e01e2042-5afc-11d3-8e80-00805f91cdd9),
helpstring("Terminplaner Object.")
]
//Definition des Terminplaner objektes
coclass Terminplaner
{
//Liste aller durch das Objekt unterstuetzter interface
[default] interface ISetzeDaten;
}
}
|
Nach Aufruf des dem Visual C++ - Compiler beiliegendem Tool MIDL.EXE und Übergabe
der Datei Terminplaner.idl werden die Dateien dlldata.c, terminplaner.h, terminplaner.tlb,
terminplaner_i.c sowie terminplaner_p.c generiert.
Terminplaner.tlb    : enthält die aktuellype Library
Terminplaner_i.c , Terminplaner.h : enthhaelten den aktuellen C++ - Quellcode fuer das ISetzeDaten – Interfaces des Terminplaner – Objektes.
Terminplaner_p.c, dlldata.c: enthalten Quellcode, um einen Proxy /Stub zum ein und
auspacken der Daten zu entwickeln (wird in spaeteren Kapitel
erlaeutert.
Bild3 : IN-PROCESS-SERVER
Ein Vorteil dieser in-process-server liegt in ihrer hohen Geschwindigkeit, resultierend aus der Tatsache, daß zum Zugriffe auf den Code des Servers keine Taskumschaltung erfolgen muß, da der Adressraum innerhalb des Client liegt. Die COM – Server die als .EXE Version erstellt wurden werden als out-process-server bezwichnet, da sie einen eigenen Adressraum besitzen sowie eigene Ressourcen reservieren können.
Bild4 : OUT-PROCESS-SERVER
Die reservierten Ressourcen werden zwischen den die Dienste des Servers in Anspruch nehmenden Clients aufgeteilt.
Das COM Aplication Programming Interface wird zum Zugriff auf die durch die COM – Library zur Verfügung gestellten Services benötigt. Um Funktionen des COM API zu kennzeichen erhielten Sie das Prefix “Co”, zum Beispiel : “CoCreateInstance”.
Zur Verwaltung der COM – Objekte besitzt das Betriebssystem eine Datenbank, die sogenannte system registry. In ihr ist hinterlegt, welche CLSID zu welchen COM – Server gehört. Sie besitzt einen hierarchischen Aufbau, wobei jeder Eintrag als Schlüssel bezeichnet wird. Jedem Schlüssel kann ein Wert zugeordnet werden, er kann außerdem weiter Schlüssel beinhalten.
Bild5 : Regedit
Die RPC/LPC - Mechanismen werden zum Zugriff auf die Funktionen des Interfaces eines COM
- Objektes benötigt. Wie oben erwähnt beinhaltet das Interface Funktionen', die zum Zugriff
auf ein COM - Objekt benötigt werden. Rein Softwaretechnisch handelt es sich hierbei um einen
Zeiger auf eine Tabelle, deren Inhalt aus Zeigern auf die Funktionen der aktuellen
Implementierung der Interfacefunktionen des COM - Objektes enthält.
Wird ein Zeiger auf ein Interface angefordert (siehe Beispiel: QueryInterface) erhält man also
einen Zeiger auf einen Zeiger, der auf eine Tabelle von Funktionszeigern zeigt.
Handelt es sich beim verwendeten COM-Server um einen »in-process-server«, zeigen die Zeiger
in der VTBL direkt auf die Methoden des Objektes.
Bild6 : Zugriff
Beim DCOM handelt es sich um eine Erweiterung des COM dahingehend, daß es nun COM Clients möglich ist Objekte zu bearbeiten die sich auf verschiedenen Rechnern befinden. Ermöglicht wird dies duch Nutzung des weiter oben bereits erwähnten RPC – Verfahrens. Da DCOM auf dem RPC – Verfahren aufsetzt war es möglich, zahlreiche verbindungsorientierte und verbindungslose Netzwerkprotokolle, wie etwa TCP, UDP,IPX, zu unterstützen.
| Name | Beschreibung |
|---|---|
| RPC_C_AUTHN_LEVEL_NONE | Keine Authentifizierung nötig |
| RPC_C_AUTHN_LEVEL_CONNECT | Authentifizierung bei Verbindungsaufbau |
| RPC_C_AUTHN_LEVEL_CALL | Authentifizierung sobald der Server einen RPC Call empfängt |
| RPC_C_AUTHN_LEVEL_PKT | Überprüft, daß alle Daten auch vom erwarteten Client sind |
| RPC_C_AUTHN_LEVEL_INTEGRITY | Wie oben + Überprüfung, daß Daten nicht modifiziert wurden |
| RPC_C_AUTHN_LEVEL_PRIVACY | alle obigen + Verschlüsselung |
Nachdem die Authentifizierung des Clients abgeschlossen ist werden die zur Nutzung des COM - Objektes vorgesehenen Zugriffsrechte auf dessen Schnittstellen, sowie die Zugriffrechte des Objektes auf die Ressourcen (fü den jeweiligen Client) festgelegt. Die folgenden Tabelle enthält die in DCOM verfügbaren Zugriffsebenen.:
| Name | Level |
|---|---|
| RPC_C_IMP_LEVEL_ANONYMOUS | Der Client bleibt gegenüber dem Server anonym. |
| RPC_C_IMP_LEVEL_IDENTIFY | Der Server legt die Zugriffsrechte jedes Clients auf das Objekt. |
| RPC_C_IMP_LEVEL_IMPERSONATE | Der Server legt für; jeden Client fest, auf welche Ressourcen das COM - Objekt Zugriff hat. |
| RPC_C_IMP_LEVEL_DELEGATE | Der Server ermöglicht es dem Client ausser den lokalen Ressourcen auch Anfragen an andere Server zu stellen.; |
In diesem Abschnitt werden die für die programmiertechnische Realisierung einer Client/Server
Kommunikation notwendigen Strukturen und Funktionen erläutert.
Um Informationen über einen "entfernten" COM - Server speichern zu können wird die
Struktur COSERVERINFO verwendet.
typedef struct _COSERVERINFO
{
DWORD dwReserved1;
LPWSTR pwszName;
COAUTHINFO *pAuthInfo;
DWORD dwReserved2;
} COSERVERINFO;
|
dwReserved1     : reserviert für zukünftige Erweiterung
pwszName    : zeigt auf den Namen des zu benutzenden Computers
pAuthInfo    : Zeiger auf eine COAUTHINFO - Struktur, legt activation security fest
dwReserved2         : reserviert für zukünftige Erweiterung
Die COAUTHINFO - Struktur enthält die Authentication - Einstellungen die für einen Aufruf des Client an den Server benötigt werden.
typedef struct _COAUTHINFO
{
DWORD dwAuthnSvc;
DWORD dwAuthzSvc;
LPWSTR pwszServerPrincName;
DWORD dwAuthnLevel;
DWORD dwImpersonationLevel;
COAUTHIDENTITY * pAuthIdentityData;
DWORD dwCapabilities;
} COAUTHINFO;
|
dwAuthnSvc : legt die Authentication - Service fest, enthält einen der oben aufgeführten
RPC_C_AUTHN_LEVEL_XXX - Werte
dwAuthzSvc : enthält einen der RPC_C_AUTHZ_xxx - Werte, legt die zu benutzenden
authorization service fest
pwszServerPrincName : NULL
dwAuthnLevel : legt den zu benutzenden Authentication - Level fest : RPC_C_AUTHN_LEVEL_xxx
dwImpersonationLevel : muß RPC_C_IMP_LEVEL_IMPERSONATE. sein
pAuthIdentityData : enthält einen Zeiger auf eine COAUTHIDENTITY - Struktur
dwCapabilities : muß EOAC_NONE sein
Die COAUTHIDENTITY - Struktur repräsentiert einen Benutzernamen und ein Passwort.
typedef struct _COAUTHIDENTITY
{
USHORT *User;
ULONG UserLength;
USHORT *Domain;
ULONG DomainLength;
USHORT *Password;
ULONG PasswordLength;
ULONG Flags;
} COAUTHIDENTITY;
|
User : Benutzername
UserLength : Länge des Benutzernamens
Domain : enthält den Domain oder Arbeitsgruppennamen
DomainLength : Länge des Namens
Password : ...
PasswordLength : ...
Flags : signalisiert, ob es sich bei den Strings um ANSI oder Unicode handelt,
SEC_WINNT_AUTH_IDENTITY_ANSI oder SEC_WINNT_AUTH_IDENTITY_UNICODE.
Um COM - Objekte zu erzeugen werden die Funktionen CoGetClassObject und CreateInstance verwendet. Voraussetung für die Erzeugung eines Objektes ist die Existenz einer dem Objekt entsprechenden CLSID in der System - Registry.
|
STDAPI CoGetClassObject(
REFCLSID rclsid, //CLSID des Objektes DWORD dwClsContext, //Context wie der Code des Objektes auszufüren ist // zur Aktivierung durch entfernten Server :CLSCTX_REMOTE_SERVER COSERVERINFO * pServerInfo, //zeigt auf den Rechner auf dem das Objekt instanziert werden soll REFIID riid, // Referenz auf den Identifizierer des Interfaces LPVOID * ppv //Address der Variable, die auf das zurückgesendete // Interface zeigt ); |
Die Funktion CreateInstance erzeugt ein uninitialisiertes Objekt.
|
HRESULT CreateInstance(
IUnknown * pUnkOuter, REFIID riid, void ** ppvObject ); |