Verteilte Systementwicklung

Tutorial zu COM / DCOM

zusammengestellt von C. Herbst als VS-Scheinaufgabe

FIN, IVS, AG Softwaretechnik

DCOM

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.

1. COM

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.

2. COM - Objekte

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.

guidgen.exe

Bild1 : GUIDGEN

2.1 Interfaces

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:

Beispiel Interface

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.


3. COM - Server

Wie oben bereits erwähnt liegt die Klasse jedes COM - Objektes in binärer Form (entweder EXE oder DLL) vor und wird als COM - Server bezeichnet. Server die als DLL vorliegen werden direkt in den Adressraum des Client geladen. Solche Server werden als in-process-server bezeichnet, da sie keinen eigenen Adressraum, sondern den Adressraum des Client benutzen. Dies bedeutet auch, daß jeder Client "Besitzer" aller durch den Server reservierter Ressourcen ist.

in-process-server

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.

out-process-server

Bild4 : OUT-PROCESS-SERVER

Die reservierten Ressourcen werden zwischen den die Dienste des Servers in Anspruch nehmenden Clients aufgeteilt.


4. Systemintegration


Um das Betriebssystem in die Lage zu versetzen die COM – Funktionalität bereitstellen zu können wurden eine Reihe von Systemfunktionen implementiert, die in der COM – Library zusammengefaßt wurden. Diese Library enthält :

4.1. API's

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”.

4.2. Implementation Locator Service

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.

regedit.exe

Bild5 : Regedit

4.3 COM´s RPC und LPC Mechanismen

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.

vtbl

Bild6 : Zugriff



Handelt es sich bei dem verwendeten COM - Server um einen »out-of-process-server« ist der Aufwand zum Zugriff auf die Funktionen bedeutend größer, da der Server in einem eigenen Adressraum läuft, und somit kein Zeiger eines Client direkt darauf zugreifen kann. Um den Zugriff auf solche Server zu ermöglichen stellt das COM einen Proxy zur Verfügung, der im Adressraum des Client läuft. Wird ein Zeiger auf ein Interface angefordert erhält man also einen Zeiger auf diesen Proxy. Das entsprechend Gegenstück im Server wird als Stub bezeichnet und befindet sich im Adressraum des Servers. Die Kommunikation läuft also vom Client <==> Proxy <==> Stub <==> Server. Befinden sich Client und Server auf dem selben Rechner kommunizieren sie via Local Procedure Calls (LPC). Es handelt sich hierbei um eine Interprozesskommunikation die es einem Prozess ermöglicht, auf die Funktionen eines anderen Prozesses zuzugreifen. Befinden sich Server und Client auf verschiedenen Rechnern verwendet COM Remote Procedure Calls (RPC) zum Zugriff auf die Funktionen des Interfaces.


5. DCOM


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.

5.1. Sicherheitsmechanismen

Da DCOM den Zugriff auf „fremde“ Rechner sowie von „fremden“ Rechner zulässt mußten Sicherheitsmechanismen eingebaut werden , die die „unzweckmäßige“ Verwendung von Ressourcen verhindern. Zu diesem Zweck besitzt DCOM zwei Sicherheitsmechanismen: activation security und call security genannt. Mittels des activation security Meachnismus wird festgelegt (und überwacht) wer beispielsweise COM Server starten darf, der call security mechanismus dient der Regulierung des Zugriffs auf die Funktionen des Interfaces eines COM – Objektes.
Um überhaut Zugriff auf die benötigten Dienste zu bekommen muß sich der Benutzer erst authentifizieren. In folgender Tabelle befinden sich die durch DCOM unterstützten Authentification Levels :

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.;

5.2. Kommunikationsaufbau

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
);

Literatur

Email an mich
Zurück zur Hauptseite Verteilte Systeme
Zurück zur Hauptseite CBSE