Interopérabilité, isolation et remoting

Programming, System Architecture · course

Voir tous les documents en programmation

Interopérabilité,

isolation et remoting

Frédéric Claux - 2019

CC BY-NC-ND – [email protected]

Définitions

• Interopérabilité

• Faire exécuter du code compilé dans un autre langage

• Isolation

• Faire exécuter du code dans un contexte de droits particuliers

• Remoting

• Faire exécuter du code sur une autre machine

Interopérabilité

• Un programme écrit en langage X veut appeler une fonction écrite en

langage Y

• Comment faire ?

Intéropérabilité - clarification

• Nous allons développer le cas général

où la communication se passe dans le

même processus

Processus

C++

Java

Intéropérabilité - clarification

• …pas le cas où la communication doit être

réalisée entre 2 processus séparés

• Hors sujet pour nous d’essayer de

communiquer par l’intermédiaire

• d’un fichier

• d’un pipe

• d’un socket TCP

• d’un file mapping

• etc.

Processus

C++

Processus

Java

Intéropérabilité - clarification

• Hors sujet pour nous d’essayer de

communiquer par l’intermédiaire

• d’un fichier

• d’un pipe

• d’un socket TCP

• d’un file mapping

• etc.

Processus

C++

Processus

Java

Enjeux de l’interopérabilité

• Faire communiquer des composants hétérogènes

• Dévier le moins possible du confort du langage et environnement de

programmation

• Typage fort

• Pas basé sur des entrées/sorties

• On voudrait éviter de quitter notre confort au prétexte de la barrière

du langage, de la barrière du processus, ou des machines

Intéropérabilité

Processus

Module exécutable

Fonction

Fonction

Fonction

Intéropérabilité

Processus

Module exécutable

Fonction exportée

Fonction exportée

Fonction exportée

[…]

Module exécutable

Fonction exportée

Fonction exportée

Fonction exportée

[…]

Module exécutable

Fonction exportée

Fonction exportée

Fonction exportée

[…]

Module exécutable

Fonction exportée

Fonction exportée

Fonction exportée

[…]

Intéropérabilité

Processus

Module exécutable

Fonction exportée

main/WinMain

Fonction exportée

[…]

Module exécutable

Fonction exportée

Fonction exportée

Fonction exportée

[…]

Module exécutable

Fonction exportée

Fonction exportée

Fonction exportée

[…]

Module exécutable

Fonction exportée

Fonction exportée

Fonction exportée

[…]

Intéropérabilité

Processus

Module exécutable

Fonction exportée

main/WinMain

Fonction

Fonction

Module exécutable

Fonction exportée

Fonction exportée

Fonction exportée

[…]

libtruc

fonctionBidule

Fonction exportée

Fonction exportée

[…]

Module exécutable

Fonction exportée

Fonction exportée

Fonction exportée

[…]

Intéropérabilité

Processus

Module exécutable

fonctionMachin

main/WinMain

Fonction

Fonction

Module exécutable

Fonction exportée

Fonction exportée

Fonction exportée

[…]

libtruc

fonctionBidule

Fonction

Fonction exportée

[…]

Module exécutable

Fonction exportée

Fonction exportée

Fonction exportée

[…]

Communication entre modules

• L’OS fournit les services suivants:

• posix_spawn/CreateProcess

• création d’un espace d’adressage (espace mémoire)

• Chargement d’un premier module binaire (PE ou ELF) dedans

• Lecture des dépendances depuis le binaire PE/ELF

• Chargement de ces modules en dépendances

• Exécution de la fonction main ou WinMain du premier module

• Il permet de charger un module sur demande, après coup

• dlopen/LoadLibrary

Ecriture d’une fonction proxy

• dlsym/GetProcAddress

• Récupère l’adresse mémoire d’une fonction exportée

• int fonctionBidule()

{

}

typedef int (*FONCTION_BIDULE)(void);

HANDLE h = LoadLibary("libtruc.dll");

void *emplacementMemoire = GetProcAddress(h, "fonctionBidule");

FONCTION_BIDULE vraieFonctionBidule =

(FONCTION_BIDULE)emplacementMemoire;

int valeur_retour = vraieFonctionBidule();

return valeur_retour;

Génération de proxy par le compilateur

• extern __declspec(dllimport) int fonctionBidule();

• Le compilateur génère automatiquement (et gracieusement) le code

source de la fonction proxy dans ce cas

• extern __declspec(dllexport) int fonctionBidule();

si on souhaite exporter la fonction pour la rendre visible par les autres

modules

Langage C

Module exécutable

Fonction appelante

dllimport

Processus

Langage C

Module exécutable

Fonction appelée

(exportée)

dllexport

Génération de proxy par le compilateur

• Et si la fonction appelée n’est pas écrite en C ?

Langage C

Module exécutable

Publicité

Fonction appelante

dllimport

Processus

Langage ?

Module exécutable

Fonction appelée

(exportée)

• Cela n’a pas d’importance pour l’appelant

• L’export de la fonction relève de la spécification PE/ELF

• Rien à voir avec C/C++

Spécifique au langage C

• extern __declspec(dllimport) int fonctionBidule();

• Permet à un programme C de s’interfacer avec un export PE/ELF

• extern __declspec(dllexport) int fonctionBidule();

• Permet à un programme de déclarer une fonction sous

forme d’export PE/ELF

A ce stade

• Nous savons

• Déclarer une fonction C comme export PE/ELF

• Appeler une fonction PE/ELF depuis le C

• La spec PE ou ELF définissent juste

• Un nom de fonction

• Ne parle même pas des arguments

Comment cela se passe-t-il en C++?

• Noms décorés

• Aplanissement des noms de méthodes : nomclasse@nommethode

• class __declspec(dllexport) Foo

{

public:

void bar();

}

Module exécutable

Foo@bar

• Les compilateurs respectent ce mode opératoire

Autres langages: Java

• Java  PE

• Un programme Java peut consommer (appeler) des fonctions PE

• Deux façons de faire

• JNA, simple

• JNI, plus élaboré, mais permet d’exposer plus facilement des classes,

par opposition à des fonctions « plates »

JNA: Java Native Access

• Fonctions sont normalement exportées dans des bibliothèques

natives PE/ELF

• Déclarations « proxy » côté Java pour faire le pont

• La JVM

Déclaration d’une interface JNA

import com.sun.jna.Library;

import com.sun.jna.Native;

public interface LibNative extends Library

{

LibNative INSTANCE = (LibNative)Native.loadLibrary(

« bibliotheques.so|.dll », LibNative.class);

void test_jni(String message);

}

Code natif qui va être appelé

#include <iostream>

using namespace std;

extern "C" void test_jni(char *mystring)

{

cout << "Hello world";

}

Précautions à prendre

• extern « C »

• méthodologie d’export de fonctions C (et non C++)

• À faire si __cplusplus est défini (c’est le cas dès lors qu’on a un fichier .cpp)

• Convention d’appel de la fonction

• C/C++ utilisent généralement __cdecl

• L’appelant empile et dépile les arguments

• Windows utilise par défaut __stdcall pour la totalité du système

• Visual C++ utilise __stdcall par défaut en C++, sauf pour les fonctions acceptant un

nombre variable d’arguments en paramètres

• void __cdecl printf(« %d %d %d », …) // __cdecl est implicite de toute façon, __stdcall

inadapté ici

• public interface LibNative extends Library

• public interface LibNative extends StdCallLibrary

JNA: Java Native Interface

• Implémentation de méthodes de classes Java directement en C++

• La JVM fait automatiquement l’appel de la fonction C/C++ (PE/ELF)

package Foo;

#include <jni.h>

public class Bar

{

public native void test(int arg);

}

extern “C” JNIEXPORT jint JNICALL

Foo_Bar_test(JNIEnv *, jobject, jint);

javah, javac -h

• Mieux vaut ne pas écrire à la main les .H

et les faire générer…

• Compilateur C++ pourra se plaindre si le

mapping Java/C++ est soudainement brisé

(modification abusive du code Java)

• javah génère le fichier .H à partir du

fichier .class

package Foo;

public class Bar

{

public native void test(int arg);

• javac –h génère automatiquement le

fichier .H à partir du fichier java

(Java 1.8 ou ultérieur)

}

Hôte C++ pour JVM

• Un programme C++ instancie la JVM

• Charge du code dedans

• Appelle des méthodes

Calling Java code from C/C++ code

package fc.JNI

public class Demo

{

public static void hello(String name)

{

System.out.print("Hello " + name + " !");

}

}

Création d’une JVM en C++

JavaVMOption jvmopt[1];

jvmopt[0].optionString = "-Djava.class.path=.";

JavaVMInitArgs vmArgs;

vmArgs.version = JNI_VERSION_1_2;

vmArgs.nOptions = 1;

vmArgs.options = jvmopt;

vmArgs.ignoreUnrecognized = JNI_TRUE;

JavaVMInitArgs vm_args;

JNIEnv *jniEnv;

JavaVM *javaVM;

JNI_CreateJavaVM(&javaVM, &jniEnv, &vmArgs);

Calling Java code from C/C++ code

jclass jcls = env->FindClass("fc/JNI/Demo");

jmethodID methodId = env->GetStaticMethodID(jcls, "hello", "(Ljava/lang/String;)V");

jstring str = env->NewStringUTF("Frédéric");

env->CallStaticVoidMethod(jcls, methodId, str);

javaVM->DestroyJavaVM();

package fc.JNI

public class Demo

{

public static void hello(String name);

}

A noter

• Le code Java peut lui-même appeler du code C++ de l’hôte

• La JVM est dans module PE/ELF, le code C++ dans un autre

Intéropérabilité Java  SQL

• SELECT

salary

FROM

employee

JOIN

JOB USING(job_title)

EMPLOYEE

  • name
  • job_title

JOB

  • job_title
  • salary

• Obention du salaire d’un employé en fonction de

l’intitulé de son poste

Intéropérabilité Java  SQL

• Comment essayer de coder un « salaire à la tête du client »

• SELECT

<quelque chose qui dépend de ‘name’ et de ‘salary’>

FROM

employee

JOIN

JOB USING(job_title)

Intéropérabilité Java  SQL

• Comment essayer de coder un « salaire à la tête du client »

• Si le nom de l’employé commence par ‘F’, faisons-le gagner plus…

• SELECT

IF(SUBSTRING(name, 0, 1) == ‘F’, salary*1.5, salary)

FROM

employee

JOIN

JOB USING(job_title)

Intéropérabilité Java  SQL

• Comment essayer de coder un « salaire à la tête du client »

avec une fonction plus évoluée

• SELECT

FONCTION_COMPLEXE(name, salary)

FROM

employee

JOIN

JOB USING(job_title)

Passer par une fonction T/SQL ou PL/SQL

 bien, mais langage dédié austère

• CREATE FUNCTION

FONCTION_COMPLEXE (

Name STRING,

Salary INT)

RETURNS INT

BEGIN

[…]

RETURN calcul;

END

Interop SQL/code hôte

• Plusieurs solutions sur le marché

Publicité

• Oracle, H2: SQL/Java

• SQL Server : SQL/.NET

• A noter: non disponible avec MySQL

• Travaux pratiques: H2

Définition d’une fonction en Java (Oracle)

• public class SalaryRetriever

{

public static String getJobSalary(String empName, String jobTitle)

{

Connection conn = DriverManager.getConnection("jdbc:default:connection:");

String sql = "SELECT job_salary FROM jobs WHERE job_title = ?";

try

{

PreparedStatement pstmt = conn.prepareStatement(sql);

pstmt.setString(1, jobTitle);

ResultSet rs = pstmt.executeQuery();

if (rs.next())

return rs.getInteger("job_salary");

pstmt.close();

}

catch (SQLException e) { }

return -1; // job inconnu, donc salaire inconnu!

}

}

Définition du proxy SQL pour la fonction

CREATE OR REPLACE FUNCTION

get_job_salary(employeeName VARCHAR2, job VARCHAR2)

RETURN

VARCHAR2

AS

LANGUAGE JAVA

NAME

'SalaryRetriever.getJobSalary(java.lang.String, java.lang.String)

return

java.lang.String';

Appel de la fonction SQL implémentée en Java

SELECT

get_job_salary(employee_name, employee_job)

FROM

employee

SELECT

AVG(get_job_salary(employee_name, employee_job))

FROM

employee

Définition d’une fonction en Java (H2)

• package mypackage;

public class MyClass

{

public static boolean myMethod(int value)

{

[…]

}

}

Définition du proxy SQL (H2)

CREATE ALIAS

MY_METHOD FOR mypackage.MyClass.myMethod

Appel de la fonction SQL (H2)

SELECT

MY_METHOD(1234)

FROM

Interop Java/SQL avec H2

• Exemples

https://github.com/h2database/h2database/blob/master/h2/src/test

/org/h2/samples/Function.java

• Fonctions

SELECT MA_FONCTION(…) FROM …

• Tables dynamiques et paramétrées

SELECT … FROM MA_TABLE_JAVA(param1, param2)

Interopérabilité NN/MM bits

• J’ai 2 bibliothèques (fichiers .DLL/.so)

• L’une compilée en 32 bits

• L’autre compilée en 64 bits

• Est-il possible de les utiliser dans le même exécutable

(i.e. dans le même processus) ?

Interopérabilité 16/32/64 bits

• Le processeur ne peut pas physiquement exécuter du code de

modèles mémoires différents au sein du même processus

• Son mode d’exécution

• étroitement lié à l’espace d’adressage (16, 32 ou 64 bits)

• comporte des règles de décodage des instructions (préfixes d’adressage)

• Exception: mode « flat » du 80386

• Vrai mode 16 bits, avec 1 seul segment de données choisi de 32 bits

• Utilise le modèle mémoire et fonctionnement 16 bits

• Un seul registre permet d’accéder à toute la mémoire sur 4 Go (souvent GS)

• Activé en sortant brusquement du mode protégé 32-bits du 80386

• Peu documenté par Intel

Mode « flat » du 80386

• Utilisé par Windows 95/98/Me seulement

• Windows 9x autorise le chargement de modules 16 et 32 bits au sein

du même processus

• Recompilation du code avec le compilateur de thunk, fourni par

Microsoft

• Passerelles (proxy/stubs) entre le code 16 et 32 bits

• Chargement du code par la fonction LoadLibraryW32()

• Uniquement supportée par Windows 9x

• Protection de la mémoire limitée en mode flat

• Stabilité relative (légendaire) de Windows 95

Intéropérabilité 16/32/64 bits moderne

• Des modules de largeur de bits différents DOIVENT résider dans des

processus différents

• Il en est impossible autrement

• Comment communiquer ?

Processus 32 bits

Module exécutable

?

Processus 64 bits

Module exécutable

Comment communiquer ?

• Avec un fichier, file mapping, pipe, socket ? 

Processus 32 bits

Fonction

?

Processus 64 bits

Fonction

Comment communiquer ?

• Y a-t-il quelqu’un pour nous générer un proxy ?

(automatiquement)

Processus 32 bits

Fonction

Proxy

?

Processus 64 bits

Fonction

Object Request Broker (ORB)

• Orienté objet (avec état)

Processus 32 bits

?

Fonction

Objet

Proxy

Processus 64 bits

Objet (avec état)

Méthode

Object Request Broker (ORB)

• new MyObjet()

Processus 32 bits

Fonction

MyObjet

Proxy

Processus 64 bits

MyObjet

Création d’objet hors processus

• new MyObjet()

• Etape 1 : création distante (dans un processus distant)

Processus 32 bits

Fonction

new MyObject()

Processus 64 bits

MyObjet

Création d’objet hors processus

• new MyObjet()

• Etape 2 : création du stub

Processus 32 bits

Fonction

new MyObject()

Processus 64 bits

MyObjet

stub

MyObjet

Création d’objet hors processus

• Etape 3 : marshalling de l’objet vers le processus appelant et création

du proxy

Processus 32 bits

Fonction

new MyObject()

MyObjet

Proxy

Object

Reference

Marshalling

Processus 64 bits

MyObjet

stub

MyObjet

Appel d’une méthode

• Marche avec l’ID du processus et l’adresse mémoire (ID) de l’objet

distant

Processus 32 bits

Fonction

new MyObject()

MyObjet

Proxy

Object

Reference

Marshalling

Processus ID

Objet ID

Processus 64 bits

MyObjet

stub

MyObjet

Appel de la méthode hello()

Processus 32 bits

Publicité

Fonction

myObject.hello();

MyObject

Proxy

Processus 64 bits

MyObjet

stub

MyObjet

class MyObjectProxy

{

int remoteProcessID;

void *remoteObjectThisPointer;

void foo()

{

// Comment implémenter foo() ici ?

}

}

class MyObjectStub

{

MyObject *real_this_pointer;

void foo()

{

real_this_pointer->foo();

}

}

Appel de la méthode hello()

class MyObjectProxy

{

int remoteProcessID;

void *remoteObjectThisPointer;

MyObjectProxy()

{

// Recupération du nom du pipe

}

void foo()

{

// Sérialisation des appels

// sous forme de données dans le pipe

WritePipe(remoteObjectThisPointer);

WritePipeString(« foo »);

}

}

class MyObjectStub

{

MyObject *real_this_pointer;

void foo()

{

real_this_pointer->foo();

}

}

void main(int argc, char **argv)

{

// Creation d’un pipe nommé

while (true) {

// Lecture du pipe

void *obj = ReadPipePointer();

String methodName = ReadPipeString();

if (obj instanceof MyObject) {

if (methodName == « foo »)

((MyObject*)obj)->foo();

}

}

}

Différentes machines ?

Machine 1

Machine 2

Processus Machine 1

Fonction

myObject.hello();

MyObject

Proxy

Processus Machine 2

MyObjet

stub

MyObjet

 utilisation d’un socket TCP à la place d’un pipe nommé

Différentes threads appelants

Machine 1

Machine 2

Processus Machine 2

MyObjet

stub

MyObjet

synchronisation ?

Processus Machine 1

Thread 1

Fonction

myObject.hello();

MyObject

Proxy

Thread 2

Fonction

myObject.hello();

MyObject

Proxy

Appel de la méthode hello()

Processus

MyObjet

stub

MyObjet

• Sérialise les appels vers le ou

les objets

class MyObjectStub

{

MyObject *real_this_pointer;

void foo()

{

real_this_pointer->foo();

}

}

void main(int argc, char **argv)

{

// Creation d’un pipe nommé

while (true) {

// Lecture du pipe

void *obj = ReadPipePointer();

String methodName = ReadPipeString();

if (obj instanceof MyObject) {

if (methodName == « foo »)

((MyObject*)obj)->foo();

}

}

}

synchronisation 

Différents threads ?

Processus

Thread 1

Fonction

myObject.hello();

MyObject

Proxy

Thread 2

MyObjet

stub

MyObjet

utilisation de la boucle de message, propre à chaque thread

(USER32.DLL, PostThreadMessage/GetMessage/DispatchMessage)

Beaucoup plus rapide qu’un pipe inter-process ou qu’un socket TCP

Différents langages ?

Processus

Thread

Objet

Langage 1

Objet

Langage 2

En C++, noms décorés

Processus

C++

Module exécutable

MaClasse1@methode1

C++

Module exécutable

MaClasse2@methode2

• C++ spécifie des noms décorés, mais guère plus

En C++, noms décorés

Processus

C++

Module exécutable

class MaClasse1

{

int monChamp;

void maMéthode();

}

C++

Module exécutable

class MaClasse2

{

String monChamp;

void maMéthode2(

MaClasse1 obj);

}

Export de « classes » indépendantes du

langage

Processus

C++

Module exécutable

class MaClasse1

{

int monChamp;

void maMéthode();

}

Python

Module exécutable

class MaClasse2

{

String monChamp;

void maMéthode2(

MaClasse1 obj);

}

• Component Object Model (COM)

COM

• Interopérabilité (synchronisée ou non)

• inter-thread

• inter-process

Publicité

• inter-langage

• inter-modèle mémore (16/32/64 bits)

• inter-machines

Support de COM

• Visual Studio : tous les langages

• C++

• Visual Basic

• Intégralité des langages .NET (support offert par .NET lui-même)

• JavaScript, VBScript

• Python

• Java (feu Visual J++)

Utilisation

• C++, avec ATL

• class MaClasse : public

CComObject<MaClasse>

{

}

BEGIN_INTERFACE_MAP(MaClasse);

void mamethode()

{ … }

END_INTERFACE_MAP();

Utilisation

• Visual Basic

• Toute instance de classe est un objet COM

• JavaScript

• Toute instance de classe est un objet COM

• HTML

• Tout élément du DOM est un objet COM

• Office, Visual Studio lui-même

• Tout objet manipulé est un objet COM

Utilisation

• .NET

• [ComVisible(true)]

class MaClasse

{

}

Utilisation

• Windows

• Tout objet système est un objet COM

• Tout instance de driver est un objet COM

• HKCR\CLSID

• Des milliers, voire des millions de classes enregistrées

Larges possibilités

• Interopérabilité d’un objet C# depuis un document Word avec une

macro VB, faisant appel à du code C++ instanciant un navigateur

HTML passant une référence d’un nœud du DOM avec un expando

écrit un IronPython, le tout piloté par un script shell

• Sur des machines séparées ?

Ou en utilisant des processus différents ?

• C’est possible

Regedit, HKCR\CLSID

Interopérabilité C/C++ intrathread avec COM

• Cas général

• Spécification binaire de COM :

• table de fonction virtuelle standard C++

• convention d’appel __stdcall

• Pont C  C++ en transformant manuellement un appel __thiscall

en __stdcall

Interopérabilité C/C++ intrathread avec COM

• Class C++:

• Class MaClasse : public CComObject<MaClasse>

{

}

<méthodes>

• Appelant en C++ :

• MaClasse *obj = new CComObject<MaClasse>();

• obj->maMethode(arg1, arg2, …);

• Appelant en C:

• MaClass *c = CoCreateInstance(MaClass_ID);

• obj->lpVtbl->maMethode(obj, arg1, arg2, …);

• La table de fonction virtuelles en C++ n’est qu’une liste de pointeurs sur fonctions

• Convention d’appel __thiscall se fait « manuellement » en ajoutant ‘this’

Différentes machines avec Java

Machine 1

Machine 2

Processus Machine 1

Fonction

myObject.hello();

MyObject

Proxy

Processus Machine 2

MyObjet

stub

MyObjet

 Java RMI : Remote Method Invocation

Différentes machines : le cas Java

public interface RMIInterface extends java.rmi.Remote

{

public String helloTo(String name) throws RemoteException;

}

Interface utilisée par le client et le serveur

L’objet desservi par le serveur

public class ServerOperation

extends UnicastRemoteObject

implements RMIInterface

{

private static final long serialVersionUID = 1L;

protected ServerOperation() throws RemoteException {

super();

}

@Override

public String helloTo(String name) throws RemoteException{

return "Server says hello to " + name;

}

}

Le serveur lui-même

public class Server

{

public static void main(String[] args)

{

try {

LocateRegistry.createRegistry(1099);

Naming.rebind("//localhost/MyServer", new ServerOperation());

System.err.println("Server ready");

} catch (Exception e) { }

}

}

Le client

public static void main(String[] args)

throws MalformedURLException, RemoteException, NotBoundException

{

}

RMIInterface obj = (RMIInterface)

Naming.lookup("//localhost/MyServer");

String name = JOptionPane.showInputDialog("What is your name?");

String response = obj.helloTo(name);

JOptionPane.showMessageDialog(null, response);

Test sous Eclipse

• Trois projets

• Client

• Server

• Code partagé (définition de l’interface)

• Lancement du serveur

• Test du client

• Références d’objets autorisées si dérivés de l’interface RMI (Remote)

• Comme avec le Component Object Model

• JVM assure le marshalling des références d’objets

• Création des proxy/stub automatiquement

Extra slides

Analogie sur l’ouverture de fichier

Système

d’exploitation

POSIX

CreateFile

(Windows)

open

(Unix)

_open

fopen

fstream::open

FileReader

C

C++

Java

Couches d’abstraction

Locale installées

Locale système

GetLocaleInfoEx

API du système

d’exploitation

Locale

utilisateur

Locale CTYPE

API C

Locale

utilisateur

Locale

utilisateur

Locale

Locale

API C++ (STL)

API Java

Couches d’abstraction

Locale installées

Locale système

API du système

d’exploitation

Locale

utilisateur

Locale CTYPE

API C

Locale

utilisateur

Locale

utilisateur

Locale

Locale

API C++ (STL)

API Java