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