Programmation système et COM/DCOM sous Windows

Transcription

Programmation système et COM/DCOM sous Windows
Programmation
système et COM/DCOM
sous Windows
2006/2007
Marius VASILIU
Programmation avancée
(non-graphique)
Évolution des techniques COM/DCOM
Avantages de l’automation
Langages de programmation, scripts
Approche objet en COM/DCOM, interfaces
API Win32 système
Mise en place de l’accès aux serveurs d’automation
Approche C++
Approche VBScript et JScript, comme scripts
indépendants ou dans des pages Web (dans
Internet Explorer)
COM/DCOM
Bref rappel historique
– DDE (Dynamic data exchange) : échange de données entre
une application client et une application maître (exemple
Matlab 4.x)
– OLE1 (Objet Link Embeded v1) : insertion d'un objet géré
par le serveur à l'intérieur du client (conteneur). La
modification se fait par click à l'intérieur de la fenêtre du
serveur, avec ses menus à lui (ex. Word 6 et l'Editeur
d'équations 1.0)
– OLE2 (Objet Link Embeded v2) : insertion d'un objet géré
par le serveur à l'intérieur du client (conteneur). La
modification se fait par click à l'intérieur de la fenêtre du
client, qui change ou fusionne ses menus avec le serveur.
COM/DCOM
Bref rappel historique
– COM : technologie de communication entre des objets
composants : un objet peut exposer plusieurs interfaces,
mais tous doivent supporter IUnknown. Une interface a
des propriétés (attributs), des méthodes avec des
paramètres normalisées, des constantes et des definitions
de type (typedefs). Elle est décrite en langage MIDL
(inspiré du C/C++). Communication limitée sur une seule
machine.
– DCOM : suite de COM étendue à la communication entre
des objets sur plusieurs machines (l'objet serveur peut
être à distance : LAN/WAN)
– COM+ : évolution de COM/DCOM sous WindowsXP/.Net
ActiveX / DirectX
Bref rappel historique
– ActiveX : classe de composants COM/DCOM qui
supportent normalement les normes OLE et ont deux
aspects : contrôles et documents. Les contrôles ActiveX
sont aussi les héritiers des anciens contrôles VBX de
VisualBasic. Essentiellement utilisés dans des documents
Web (HTML / VBScript / JScript), installation à distance
par protocole HTTP à partir d'un serveur.
– DirectX (Direct 3D/Draw/Input/Audio/Music/Play/Show) :
interface multimédia composée d'objets COM qui sert à
accélérer l'accès aux périphériques IHM: carte graphique /
son / réseau, joystick, codecs etc. Utilisé par les jeux
(locaux/réseau), applications multimédia (enregistrement,
streaming, DVD, réalité virtuelle, Win Media, Real Media).
Application
Win32 cliente
COM/DCOM
COM/DCOM
approche objet
Interface
DCOM
Interface
COM
Un client peut faire
appel à un serveur
DCOM situé sur un
autre PC.
réseau TCP/IP
Interface
DCOM
Interface
COM
COM/DCOM
approche objet
Un composant
COM/DCOM possède
plusieurs interfaces
COM/DCOM
Il possède au moins
l’interface IUnknown !
Pour répondre à
chaque protocole OLE1,
OLE2 ou ActiveX il doit
avoir d’autres interfaces
en plus.
Interface COM
Interface
COM
Objet OLE
ActiveX
DirectX
Interface
COM
Enregistrement des interfaces
COM/DCOM
L'interface COM/DCOM est une collection de :
–
–
–
–
méthodes / événements
attributs
types
constantes (énumérations)
Chaque objet COM/DCOM possède un CLSID (class ID) unique
de type GUID (global unique ID).
Chaque interface d'un objet COM/DCOM possède un IID
(interface ID) unique de type GUID.
Plusieurs interfaces sont regroupées dans des "Type Libraries"
sous différents formats : *.tlb, *.olb, *.tlh, *.tli, *.dll, *.exe
Tout est enregistré dans la base de registres Windows
Les GUIDs
Un GUID est une structure C de 16 octets (128 bits) définie
dans winnt.h :
typedef struct _GUID {
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE Data4[8];
} GUID;
Pour toute nouvelle interface on tire aléatoirement un GUID à
l'aide de GuidGen (une chance ~2-128 de conflit)
Pour le déclarer on peut utiliser la macro DEFINE_GUID
définie dans objbase.h :
#define DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
extern "C" const GUID name={l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
COM/DCOM
approche objet
Tout objet possède une interface (par défaut) IUnknown
l'application peut retrouver toute autre interface de l'objet
Méthodes de l'interface IUnknown
Incrémente le compteur d'utilisation de l'objet suite au lien
AddRef
dynamique (bind) à une application ou à un autre objet.
A la création de compteur est initialisé à 1.
Prototype : ULONG AddRef();
Décrémente le compteur d'utilisation de l'objet suite au lien
Release
dynamique (bind) à une application ou à un autre objet.
Si le compteur arrive à 0 l'objet est détruit automatiquement.
Prototype : ULONG Release();
Interroge sur la présence d'une interface COM riid. Si
QueryInterface
elle existe, le compteur est incrémenté et l'on peut l'utiliser immédiatement
par l'intermédiaire du pointeur retourné dans pi. Prototype : HRESULT
QueryInterface(REFIID riid, LPVOID *pi);
Exploration des interfaces
Utiliser l'outil d'exploration "OLE View" (Visual Studio/Outils)
Explorez les branches
–
–
–
–
Object Classes
Application IDs
Type Libraries
Interfaces
Rechercher dans la branche Interfaces l'interface appelée
IShellLinkDual. Relever les GUIDs de l'objet, de l'interface et de
la bibliothèque.
De quelles autres interfaces hérite-t-elle ?
Même chose pour l'interface IShellLinkDual2. Conclusion ?
Le langage de description des interfaces : MIDL dérivé de
C/C++.
Accès à l'interface COM
dans les langages interprétés
Seulement les interfaces qui héritent de l'interface IDispatch
sont accessibles aux langages interprétées Java, VB ou encore
les scripts (JScript, VBScript etc.).
IDispatch possède des méthodes spécifiques :
GetIDsOfNames : donnes les noms des paramètres
GetTypeInfo : donne des infos sur les types
GetTypeInfoCount : donne le nombre de paramètres
Invoke : permet d'invoquer toute autre méthode ou lire /
écrire un attribut
Avantage : appel ad-hoc pendant l'interprétation
Inconvénient : besoin d'une TypeLib enregistrée, très lent
et inefficace (négociation / vérification / appels)
Accès à l'interface COM
sous C++
Compatibilité binaire entre COM/DCOM et C++.
COM/DCOM
Interface d'un objet COM
Pointeur d'interface ip
(retourné par QueryInterface)
Méthode d'un objet COM
Nom de l'objet COM
Appel d'une méthode
Traduction dans un programme C++
Classe de base abstraite (toutes les méthodes pure virtual)
Pointeur vers la vtable de l'objet
(tableau d'adresses des méthodes)
Méthode d'un objet C++
Argument caché this à l'appel d'un méthode
Indirection implicite par vtable
Création / destruction
En C++ on crée et on détruit les objets en utilisant les opérateurs new et delete.
Sous COM/DCOM le système compte le nombre d'utilisations des objets (nombre
d'interfaces connectées) et gère leur création et leur destruction.
Comment avoir un pointeur
d'interface COM/DCOM
Attention : on peut avoir juste un pointeur vers l'interface
(pointeur vers une classe C++ en langage C++)
Création explicite d'un objet COM/DCOM :
à l'aide de CoCreateInstance
on demande en même temps le pointeur d'une interface
Interrogation sur une nouvelle interface d'un objet COM/DCOM
existant :
à l'aide de QueryInterface
il faut connaître l'ID de l'autre interface
Appel d'une méthode qui retourne un pointeur d'interface :
soit du même objet
soit d'un nouveau objet crée pendant l'appel
Appel d'une API dédiée : en DirectX par exemple.
Création d’un objet
COM sous C++
Il faut connaître le CLSID de l'objet COM voulu :
s'il est connu publiquement, il se trouve dans la doc ou dans un *.h
Il faut initialiser le système COM explicitement en appelant CoInitialize :
HRESULT CoInitialize(
LPVOID pvReserved ); // toujours nul
Appel explicite à la fonction CoCreateInstance / CoCreateInstanceEx
(DCOM):
STDAPI CoCreateInstance(
REFCLSID rclsid,
// le CLSID du futur objet
LPUNKNOWN pUnkOuter, // pointeur nul dans ce cas
DWORD dwClsContext, // = CLSCTX_INPROC_SERVER si
// l'objet est une DLL à exécuter avec le proc. courant
REFIID riid, // réf. de l'ID de l'interface choisie
LPVOID *ppv );// adresse de retour de l'interface
A la fin, la fermeture du système COM, en appelant CoUninitialize :
void CoUninitialize( );
Derrière une méthode
COM/DCOM ...
Au bas niveau, chaque méthode :
empile ses arguments dans un tableau d'arguments et
remplit un tableau de types d'arguments
Chaque type d'argument COM/DCOM est codé (énumération) :
Type
Val
VT_EMPTY
VT_NULL
VT_I2
VT_I4
VT_R4
VT_R8
VT_CY
VT_DATE
VT_BSTR
VT_DISPATCH
VT_BOOL
...
0
1
2
3
4
5
6
7
8
9
11
...
Signification
nothing
SQL style Null
2 byte signed int
4 byte signed int
4 byte real
8 byte real
currency
date
OLE Automation (Basic) string
IDispatch *
True=-1, False=0
...
dans
VARIANT
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
...
dans
comme
dans
TYPEDESC OLE property SafeArray
NON
NON
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
...
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
...
NON
NON
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
OUI
...
Derrière une méthode
COM/DCOM ...
Chaque argument est vu comme une structure VARIANT qui
possède le type (enum) et la valeur (union)
typedef struct tagVARIANT {
VARTYPE vt;
vt;
unsigned short wReserved1;
unsigned short wReserved2;
unsigned short wReserved3;
union {
BYTE bVal;
//
bVal;
short iVal;
//
iVal;
long lVal;
//
lVal;
float fltVal;
//
fltVal;
double dblVal;
;
//
dblVal
VARIANT_BOOL bVal;
//
bVal;
SCODE scode;
//
scode;
CY cyVal;
//
cyVal;
DATE date;
//
BSTR bstrVal;
//
bstrVal;
DECIMAL* pdecVal
//
IUnknown*
//
IUnknown* punkVal;
punkVal;
IDispatch*
//
IDispatch* pdispVal;
pdispVal;
SAFEARRAY* parray;
//
parray;
BYTE* pbVal;
//
pbVal;
short* piVal;
//
piVal;
long* plVal;
;
//
plVal
float*
//
float* pfltVal;
pfltVal;
double* pdblVal;
pdblVal;
VARIANT_BOOL* pboolVal;
pboolVal;
SCODE* pscode;
pscode;
CY* pcyVal;
pcyVal;
DATE* pdate;
pdate;
VT_UI1
BSTR* pbstrVal;
pbstrVal;
VT_I2
IUnknown**
IUnknown** ppunkVal;
ppunkVal;
VT_I4
IDispatch**
IDispatch** ppdispVal;
ppdispVal;
VT_R4
SAFEARRAY** pparray;
pparray;
VT_R8
VARIANT* pvarVal;
pvarVal;
VT_BOOL
void*
void* byref;
byref;
VT_ERROR
char cVal;
cVal;
VT_CY
unsigned short uiVal;
uiVal;
VT_DATE
unsigned long ulVal;
ulVal;
VT_BSTR
int intVal;
intVal;
VT_BYREF|VT_DECIMAL unsigned int uintVal;
uintVal;
VT_UNKNOWN
char* pcVal;
;
pcVal
VT_DISPATCH
unsigned short* puiVal;
puiVal;
VT_ARRAY|*
unsigned long* pulVal;
pulVal;
VT_BYREF|VT_UI1
int*
int* pintVal;
pintVal;
VT_BYREF|VT_I2
unsigned int*
int* puintVal;
puintVal;
VT_BYREF|VT_I4
};
VT_BYREF|VT_R4
} VARIANT;
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
VT_BYREF|VT_R8
VT_BYREF|VT_BOOL
VT_BYREF|VT_ERROR
VT_BYREF|VT_CY
VT_BYREF|VT_DATE
VT_BYREF|VT_BSTR
VT_BYREF|VT_UNKNOWN
VT_BYREF|VT_DISPATCH
VT_ARRAY|*
VT_BYREF|VT_VARIANT
Generic ByRef.
ByRef.
VT_I1
VT_UI2
VT_UI4
VT_INT
VT_UINT
VT_BYREF|VT_I1
VT_BYREF|VT_UI2
VT_BYREF|VT_UI4
VT_BYREF|VT_INT
VT_BYREF|VT_UINT
Support C++/ATL pour
COM/DCOM
Classes C++ pour l'accès aux méthodes, aux paramètres et aux
attributs des interfaces COM/DCOM.
Inclure le fichier d'entête <comdef.h> avant utilisation !
La classe _variant_t possèdes des constructeurs à partir de
pratiquement tous les types de VARIANT ainsi que des
extracteurs (opérateurs de typecast surchargés) vers ces types
La classe _bstr_t encapsule le type BSTR et possèdes des
constructeurs à partir des chaines C/C++ de type ASCII
permettant une conversion à la volée de const char* vers BSTR
La classe _com_ptr_t est un "smart pointer" car elle encapsule
un pointeur vers une interface COM/DCOM, en assurant la
gestion intelligente de sa création et de sa destruction
Création / utilisation directe
d'objets COM/DCOM
Exemple d'utilisation en C/C++ sans MFC ou ATL
– Exploration des liens avec l'objet COM/DCOM ShellLink
qui a le CLSID_ShellLink = {00021400-0000-0000-C000-000000000046}
– L'objet expose plusieurs interfaces :
IShellLink = {000214EE-0000-0000-C000-000000000046},
IPersistFile, IUnknown et d'autres
– Entête shlobj.h, shlguid.h ... pour les CLSID, IID ...
– Bibliothèque shell32.dll : point d'entrée
DLLGetClassObject
Ecrire une application Win32 console qui affiche toutes les
informations concernant un lien (chemin, ligne de commande,
paramètres, icône ...)
Faire la même chose pour tout un répertoire (*.lnk)
Création / utilisation directe
d'objets COM/DCOM
Version initiale
Initialiser la couche COM/DCOM
Créer un objet ShellLink avec une interface IShellLink
Obtenir / ouvrir une interface IPersistFile
Charger le contenu du fichier de lien *.lnk
Interroger l'interface IShellLink pour afficher les infos
Fermer correctement les interfaces et COM/DCOM
Version améliorée
Modifier l'application pour balayer un répertoire à
l'aide de FindFirstFile, FindNextFile et FindClose
Automation
Caractéristiques principales :
– Rôle : exposer les fonctionnalités d'une application aux
autres applications, même les scripts
– Hérite des normes DDE puis OLE et actuellement fait partie
intégrante de COM/DCOM
– Indépendante des autres caractéristiques OLE comme
l'activation sur place, intégration, affichage etc.
– Appels souples (pour scripts) : par interfaces COM
absolument nécessaires : IUnknown et IDispatch
– Appels pré connus (pour C/C++): VTBL binding si l'on
dispose des librairies de type (OLB, TLB etc.)
– Deux types d'exécution par automation :
In-process server : exécution des DLL
Out-of-process server : exécution des EXE
Automation :
application remote Word2K3
Réaliser une application Win32 Console qui va
commander WinWord par automation pour créer,
sauvegarder et imprimer un fichier infos.doc avec
des informations système.
Format du futur fichier infos.doc :
– Titre (1er paragraphe) : Informations système
(Times New Roman 24pts, centré)
– Tableau à 2 colonnes, 1ère colonne contient :
Nom ordinateur, Nom utilisateur, Version système, Service pack,
Répertoire système, Taille écran
Utiliser les API GetComputerName, GetSystemDirectory,
GetSystemMetrics, GetVersionEx, GetSystemInfo etc.
Automation sous C++/ATL:
application remote Word2K3
Créez une application Console Win32
Investiguer à l'aide de l'outil OLE/COM ObjectViewer
l'interface d'automation Word2K3 ainsi que ses
éventuelles dépendances
Utiliser VBAWD10.CHM comme fichier d'aide
Utiliser la directive #import pour traduire les
interfaces d'automation Word2K3 en classes C++ (de
type smart pointers) : msword9.olb
Inspecter les fichiers *.tlh et *.tli produits par le
compilateur. Quelles classes, quelles méthodes ?
Rajouter le nécessaire pour la compilation :
– vbe6ext.olb, mso.dll, stdole2.tlb: attention aux namespaces !
Automation Word2K3
Initialiser la couche COM/DCOM (CoInitialize ...)
De qui héritent toutes les classes d’interface ?
Déclarer/construire un objet app de type
_ApplicationPtr en passant "Word.Application"
comme paramètre :
_ApplicationPtr app("Word.Application");
app->Visible = VARIANT_TRUE;
Sleep(1000);
app->Quit();
Inspecter les méthodes propres de l'objet app et
les méthodes COM/DCOM surchargées.
Comment obtenir l'accès aux autres interfaces ?
Automation Word2K3
Inspecter et utiliser les interfaces suivantes :
– _Document, Paragraph, Range, Table, etc.
Utiliser le fichier d'aide VbaWD10.CHM pour plus
d'informations.
Créer des fonctions supplémentaires si cela vous
semble nécessaire : par exemple pour rajouter du
texte dans la case d'un tableau etc.
Comment sauvegarder le fichier (en évitant une
question sur l'existence d'un fichier avec le même
nom ...) ?
Comment sauvegarder le fichier en format HTML
(infos.htm) ?
Automation Excel2K3
Réaliser une autre application (ou juste une
nouvelle fonction) qui produit un résultat similaire
en utilisant Excel2003
Utiliser OLE/COM ObjectViewer pour savoir quelles
bibliothèques importer
Rechercher dans les répertoires d'installation
d'Office 2003 le fichier d'aide nécessaire
Créer des fonctions supplémentaires si cela vous
semble nécessaire : par exemple pour rajouter du
texte dans la case d'une feuille Excel.
Mêmes questions (que sous Word 2003) sur la
sauvegarde du fichier ...