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