Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Les développeurs d’applications s’attendent à pouvoir appeler des API Android natives et recevoir des appels ou réagir à des événements provenant des API Android à l’aide du code écrit dans l’un des langages gérées .NET. .NET pour Android utilise un certain nombre d’approches, au moment de la génération et au moment de l’exécution, pour ponter la machine virtuelle Java (ART dans android OS) et la machine virtuelle managée (MonoVM).
La machine virtuelle Java et la machine virtuelle managée coexistent dans le même processus ou la même application en tant qu’entités distinctes. Malgré le partage des mêmes ressources de processus, il n’existe aucun moyen direct d’appeler les API Java/Kotlin à partir de .NET, et il n’existe aucun moyen direct pour le code Java/Kotlin d’appeler des API de code managé. Pour activer cette communication, .NET pour Android utilise l’interface native Java (JNI). Il s’agit d’une approche qui permet au code natif (le code managé .NET étant natif dans ce contexte) d’inscrire des implémentations de méthodes Java, écrites en dehors de la machine virtuelle Java et dans des langages autres que Java ou Kotlin. Ces méthodes doivent être déclarées dans le code Java, par exemple :
class MainActivity extends androidx.appcompat.app.AppCompatActivity
{
public void onCreate (android.os.Bundle p0)
{
n_onCreate (p0);
}
private native void n_onCreate (android.os.Bundle p0);
}
Chaque méthode native est déclarée à l’aide du mot-clé native
, et chaque fois qu’elle est appelée à partir du code Java, la machine virtuelle Java utilise la bibliothèque JNI pour appeler la méthode cible.
Les méthodes natives peuvent être inscrites dynamiquement ou statiquement, en fournissant une bibliothèque partagée native qui exporte un symbole avec un nom approprié qui pointe vers la fonction native implémentant la méthode Java.
Wrappers Java pouvant être appelés
.NET pour Android inclut dans un wrapper les API Android en générant du code C# qui reflète les API Java/Kotlin. Chaque classe générée qui correspond à un type Java/Kotlin est dérivée de la classe Java.Lang.Object
(implémentée dans l’assembly Mono.Android
), qui la marque comme un type interopérable Java. Cela signifie qu’elle peut implémenter ou remplacer des méthodes virtuelles Java. Pour rendre l’inscription et l’appel de ces méthodes possibles, il est nécessaire de générer une classe Java qui reflète la classe managée et qui fournit un point d’entrée à la transition entre Java et la classe managée. Les classes Java sont générées pendant la génération de l’application, ainsi que pendant la génération de .NET pour Android, et sont connus sous le nom de Wrappers Java pouvant être appelés (JCW). L’exemple suivant montre une classe managée qui remplace deux méthodes virtuelles Java :
public class MainActivity : AppCompatActivity
{
public override Android.Views.View? OnCreateView(Android.Views.View? parent, string name, Android.Content.Context context, Android.Util.IAttributeSet attrs)
{
return base.OnCreateView(parent, name, context, attrs);
}
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
DoSomething(savedInstanceState);
}
void DoSomething(Bundle bundle)
{
// do something with the bundle
}
}
Dans cet exemple, la classe managée remplace les méthodes virtuelles OnCreateView
et OnCreate
Java trouvées dans le type AppCompatActivity
. La méthode DoSomething
ne correspond à aucune méthode trouvée dans le type Java de base et ne sera donc pas incluse dans le JCW.
L’exemple suivant montre le JCW généré pour la classe ci-dessus (avec certaines méthodes générées omises pour plus de clarté) :
public class MainActivity extends androidx.appcompat.app.AppCompatActivity
{
public android.view.View onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3)
{
return n_onCreateView (p0, p1, p2, p3);
}
private native android.view.View n_onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3);
public void onCreate (android.os.Bundle p0)
{
n_onCreate (p0);
}
private native void n_onCreate (android.os.Bundle p0);
}
Inscription
Les deux approches d’inscription de méthode s’appuient sur la génération de JCW, avec l’inscription dynamique nécessitant davantage de code à générer afin que l’inscription puisse être effectuée au moment de l’exécution.
Les JCW sont générés uniquement pour les types qui dérivent du type Java.Lang.Object
. La recherche de ces types est la tâche de Java.Interop, qui lit tous les assemblys référencés par l’application et ses bibliothèques. La liste retournée des assemblys est ensuite utilisée par diverses tâches, dont JCW.
Une fois tous les types trouvés, chaque méthode de chaque type est analysée pour rechercher celles qui remplacent une méthode virtuelle Java et doivent donc être incluses dans le code de classe wrapper. Le générateur vérifie également si la méthode donnée peut être inscrite statiquement.
Le générateur recherche des méthodes décorées avec l’attribut Register
, qui sont le plus fréquemment créées en appelant son constructeur avec le nom de la méthode Java, la signature de méthode JNI et le nom de la méthode du connecteur. Le connecteur est une méthode statique qui crée un délégué qui autorise par la suite l’appel de la méthode de rappel native :
public class MainActivity : AppCompatActivity
{
// Connector backing field
static Delegate? cb_onCreate_Landroid_os_Bundle_;
// Connector method
static Delegate GetOnCreate_Landroid_os_Bundle_Handler()
{
if (cb_onCreate_Landroid_os_Bundle_ == null)
cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate((_JniMarshal_PPL_V)n_OnCreate_Landroid_os_Bundle_);
return cb_onCreate_Landroid_os_Bundle_;
}
// Native callback
static void n_OnCreate_Landroid_os_Bundle_(IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState)
{
var __this = global::Java.Lang.Object.GetObject<Android.App.Activity>(jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
var savedInstanceState = global::Java.Lang.Object.GetObject<Android.OS.Bundle>(native_savedInstanceState, JniHandleOwnership.DoNotTransfer);
__this.OnCreate(savedInstanceState);
}
// Target method
[Register("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")]
protected virtual unsafe void OnCreate(Android.OS.Bundle? savedInstanceState)
{
const string __id = "onCreate.(Landroid/os/Bundle;)V";
try
{
JniArgumentValue* __args = stackalloc JniArgumentValue[1];
__args[0] = new JniArgumentValue((savedInstanceState == null) ? IntPtr.Zero : ((global::Java.Lang.Object)savedInstanceState).Handle);
_members.InstanceMethods.InvokeVirtualVoidMethod(__id, this, __args);
}
finally
{
global::System.GC.KeepAlive(savedInstanceState);
}
}
}
Le code ci-dessus est généré dans la classe Android.App.Activity
lorsque .NET pour Android est généré. Ce qui se passe avec ce code dépend de l’approche d’inscription.
Inscription dynamique
Cette approche d’inscription est utilisée par .NET pour Android lorsqu’une application est intégrée à la configuration de débogage ou lorsque Marshaler les méthodes est désactivé.
Avec l’inscription dynamique, le code Java suivant est généré pour l’exemple C# présenté dans les wrappers Java pouvant être appelés (avec certaines méthodes générées omises pour plus de clarté) :
public class MainActivity extends androidx.appcompat.app.AppCompatActivity
{
public static final String __md_methods;
static
{
__md_methods =
"n_onCreateView:(Landroid/view/View;Ljava/lang/String;Landroid/content/Context;Landroid/util/AttributeSet;)Landroid/view/View;:GetOnCreateView_Landroid_view_View_Ljava_lang_String_Landroid_content_Context_Landroid_util_AttributeSet_Handler\n" +
"n_onCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n" +
"";
mono.android.Runtime.register ("HelloAndroid.MainActivity, HelloAndroid", MainActivity.class, __md_methods);
}
public android.view.View onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3)
{
return n_onCreateView (p0, p1, p2, p3);
}
private native android.view.View n_onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3);
public void onCreate (android.os.Bundle p0)
{
n_onCreate (p0);
}
private native void n_onCreate (android.os.Bundle p0);
}
Le code qui participe à l’inscription est le constructeur statique de la classe. Pour chaque méthode inscrite pour le type (autrement dit, implémentée ou remplacée dans le code managé), le générateur JCW génère une chaîne unique qui contient des informations sur le type et la méthode à inscrire. Chaque chaîne d’inscription est arrêtée avec le caractère de nouvelle ligne et la séquence entière se termine par une chaîne vide. Toutes les chaînes d’inscription sont concaténées et placées dans la variable statique __md_methods
. La méthode mono.android.Runtime.register
est ensuite appelée pour inscrire toutes les méthodes.
Séquence d’appel d’inscription dynamique
Toutes les méthodes natives déclarées dans le type Java généré sont inscrites lorsque le type est construit ou consulté pour la première fois. C’est à ce moment que la machine virtuelle Java appelle le constructeur statique du type, en lançant une séquence d’appels qui se termine par toutes les méthodes du type inscrites auprès de la bibliothèque JNI :
mono.android.Runtime.register
est une méthode native, déclarée dans la classeRuntime
du code d’exécution Java de .NET pour Android et implémentée dans l’exécution native de .NET pour Android. L’objectif de cette méthode est de préparer un appel dans le code d’exécution managé de .NET pour Android.Android.Runtime.JNIEnv::RegisterJniNatives
reçoit le nom du type managé pour lequel inscrire des méthodes Java et utilise la réflexion .NET pour charger ce type suivi d’un appel pour mettre en cache le type. Il se termine par un appel à la méthodeAndroid.Runtime.AndroidTypeManager::RegisterNativeMembers
.Android.Runtime.AndroidTypeManager::RegisterNativeMembers
appelle la méthodeJava.Interop.JniEnvironment.Types::RegisterNatives
qui génère d’abord un délégué à la méthode de rappel native, à l’aide deSystem.Reflection.Emit
, et appelle la fonctionRegisterNatives
de JNI Java pour inscrire les méthodes natives pour un type managé.
Remarque
L’appel System.Reflection.Emit
est une opération coûteuse et est répété pour chaque méthode inscrite.
Pour plus d’informations sur l’inscription de type Java, consultez Inscription de type Java.
Marshaler les méthodes
L’approche d’inscription qui consiste à marshaler des méthodes tire parti de la capacité des bibliothèques JNIs à rechercher des implémentations des méthodes native
Java dans des bibliothèques natives. Ces symboles doivent avoir des noms qui suivent un ensemble de règles, afin que la bibliothèque JNI puisse les localiser.
L’objectif de cette approche est de contourner l’approche d’inscription dynamique, en la remplaçant par du code natif généré et compilé pendant la génération de l’application. Cela réduit le temps de démarrage de l’application. Pour atteindre cet objectif, cette approche utilise des classes qui génèrent du code natif et modifient des assemblys qui contiennent les méthodes inscrites.
Le classifieur des méthodes marshalées reconnaît le modèle d’inscription de méthode standard, qui se compose des éléments suivants :
- La méthode du connecteur, qui est
GetOnCreate_Landroid_os_Bundle_Handler
dans l’exemple dans Inscription. - Le champ de stockage délégué, qui est
cb_onCreate_Landroid_os_Bundle_
dans l’exemple Inscription. - La méthode de rappel native, qui est
n_OnCreate_Landroid_os_Bundle_
dans l’exemple dans Inscription. - La méthode cible virtuelle qui distribue l’appel à l’objet réel, qui est
OnCreate
dans l’exemple dans Inscription.
Chaque fois que le classifieur s’exécute, il reçoit le type déclarant des méthodes et l’instance d’attribut Register
qu’il utilise pour vérifier si la méthode inscrite est conforme au modèle d’inscription. Le connecteur, les méthodes de rappel natives et le champ de stockage doivent être privés et statiques pour que la méthode inscrite soit considérée comme candidate à l’inscription statique.
Remarque
Les méthodes inscrites qui ne suivent pas le modèle standard sont inscrites dynamiquement.
Avec les méthodes marshalées, le code Java suivant est généré pour l’exemple C# présenté dans des wrappers Java pouvant être appelés (avec certaines méthodes générées omises pour plus de clarté) :
public class MainActivity extends androidx.appcompat.app.AppCompatActivity
{
public android.view.View onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3)
{
return n_onCreateView (p0, p1, p2, p3);
}
private native android.view.View n_onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3);
public void onCreate (android.os.Bundle p0)
{
n_onCreate (p0);
}
private native void n_onCreate (android.os.Bundle p0);
}
Par rapport au code généré pour l’inscription dynamique, il n’existe aucun constructeur statique. Toutefois, le reste du code est identique.
Exigences de la bibliothèque JNI
La bibliothèque JNI spécifie une série de règles qui régissent la construction du nom du symbole natif. Un nom de méthode native est concaténé à partir des composants suivants :
- Chaque symbole commence par le préfixe
Java_
. - Un nom de classe complet altéré.
- Un caractère
_
sert de séparateur. - Un nom de méthode altéré.
- Éventuellement, un trait de soulignement double
__
et la signature d’argument de méthode altérée.
L’altération est une façon d’encoder certains caractères qui ne sont pas directement représentables dans le code source et dans le nom du symbole natif. La spécification JNI permet l’utilisation directe de lettres ASCII (majuscules et minuscules) et de chiffres, tandis que tous les autres caractères sont représentés par des espaces réservés ou encodés sous forme de codes de caractères Unicode hexadécimaux 16 bits :
Séquence d'échappement | Désigne |
---|---|
_0XXXX | un caractère Unicode XXXX, tout en minuscules. |
_1 | Le caractère _ . |
_2 | Le caractère ; dans les signatures. |
_3 | Le caractère [ dans les signatures. |
_ | Les caractères . ou / . |
La génération de noms de symboles JNI est effectuée par l’une des tâches de génération .NET pour Android tout en générant le code source de la fonction native.
JNI prend en charge un format court et un format long du nom du symbole natif. Le format court est recherché en premier par la machine virtuelle Java, suivi du format long. Les formats longs doivent être utilisés uniquement pour les méthodes surchargées.
Génération de code de représentation intermédiaire LLVM
L’une des tâches de génération .NET pour Android utilise l’infrastructure de générateur de représentation intermédiaire LLVM pour générer des données et du code exécutable pour tous les wrappers de méthodes marshalées. Considérez le code C++ suivant, qui sert de guide pour comprendre le fonctionnement de l’appel du runtime de la méthode marshalée :
using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr);
static get_function_pointer_fn get_function_pointer;
void xamarin_app_init (get_function_pointer_fn fn) noexcept
{
get_function_pointer = fn;
}
using android_app_activity_on_create_bundle_fn = void (*) (JNIEnv *env, jclass klass, jobject savedInstanceState);
static android_app_activity_on_create_bundle_fn android_app_activity_on_create_bundle = nullptr;
extern "C" JNIEXPORT void
JNICALL Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2 (JNIEnv *env, jclass klass, jobject savedInstanceState) noexcept
{
if (android_app_activity_on_create_bundle == nullptr) {
get_function_pointer (
16, // mono image index
0, // class index
0x0600055B, // method token
reinterpret_cast<void*&>(android_app_activity_on_create_bundle) // target pointer
);
}
android_app_activity_on_create_bundle (env, klass, savedInstanceState);
}
La fonction xamarin_app_init
est sortie une seule fois et est appelée par le runtime .NET pour Android deux fois au démarrage de l’application. La première fois qu’elle est appelée, c’est pour passer get_function_pointer_fn
, et la deuxième fois qu’elle est appelée, c’est juste avant de donner le contrôle à la machine virtuelle Mono, pour passer un pointeur à get_function_pointer_fn
.
La fonction Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2
est un modèle répété pour chaque fonction native Java, chaque fonction ayant son propre jeu d’arguments et son propre champ de stockage de rappel (ici android_app_activity_on_create_bundle
).
La fonction get_function_pointer
prend des index dans plusieurs tables en tant que paramètres. L’un concerne les pointeurs MonoImage*
et l’autre les pointeurs MonoClass*
, qui sont tous générés au moment de la génération de l’application et permettent une recherche très rapide au moment de l’exécution. Les méthodes cibles sont récupérées par leur valeur de jeton, au sein du MonoImage*
et de la classe spécifiés (essentiellement un pointeur vers une image d’assembly managée en mémoire).
La méthode identifiée à l’aide de cette approche doit être décorée dans le code managé avec l’attribut UnmanagedCallersOnly
afin qu’elle puisse être appelée directement, comme s’il s’agissait d’une méthode native elle-même, avec une surcharge de marshalling managée minimale.
Réécriture d’assembly
Les assemblys managés, y compris Mono.Android.dll
, qui contiennent des types Java, doivent être utilisables avec l’inscription dynamique et avec les méthodes marshalées. Toutefois, ces deux approches ont des exigences différentes. On ne peut pas supposé que chaque assembly aura du code conforme aux méthodes marshalées. Par conséquent, une approche est requise pour garantir que le code répond à cette exigence. Pour ce faire, lisez les assemblys et modifiez-les en modifiant la définition des rappels natifs et en supprimant le code qui n’est plus utilisé par les méthodes marshalées. Cette tâche est effectuée par l’une des tâches de génération .NET pour Android appelées pendant la génération de l’application après que tous les assemblys sont liés, mais avant la génération des mappages de type.
Les modifications exactes appliquées sont les suivantes :
- Suppression du champ de stockage du connecteur.
- Suppression de la méthode du connecteur.
- Génération d’une méthode de wrapper de rappel native, qui intercepte et propage des exceptions non prises en charge lancées par le rappel natif ou la méthode cible. Cette méthode est décorée avec l’attribut
UnmanagedCallersOnly
et appelée directement à partir du code natif. - Si vous le souhaitez, générez du code dans le wrapper de rappel natif pour gérer les types non blittables.
Après les modifications, l’assembly contient l’équivalent du code C# suivant pour chaque méthode marshalée :
public class MainActivity : AppCompatActivity
{
// Native callback
static void n_OnCreate_Landroid_os_Bundle_(IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState)
{
var __this = global::Java.Lang.Object.GetObject<Android.App.Activity>(jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
var savedInstanceState = global::Java.Lang.Object.GetObject<Android.OS.Bundle>(native_savedInstanceState, JniHandleOwnership.DoNotTransfer);
__this.OnCreate(savedInstanceState);
}
// Native callback exception wrapper
[UnmanagedCallersOnly]
static void n_OnCreate_Landroid_os_Bundle__mm_wrapper(IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState)
{
try
{
n_OnCreate_Landroid_os_Bundle_(jnienv, native__this, native_savedInstanceState)
}
catch (Exception ex)
{
Android.Runtime.AndroidEnvironmentInternal.UnhandledException(ex);
}
}
// Target method
[Register("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")]
protected virtual unsafe void OnCreate(Android.OS.Bundle? savedInstanceState)
{
const string __id = "onCreate.(Landroid/os/Bundle;)V";
try
{
JniArgumentValue* __args = stackalloc JniArgumentValue[1];
__args[0] = new JniArgumentValue((savedInstanceState == null) ? IntPtr.Zero : ((global::Java.Lang.Object)savedInstanceState).Handle);
_members.InstanceMethods.InvokeVirtualVoidMethod(__id, this, __args);
}
finally
{
global::System.GC.KeepAlive(savedInstanceState);
}
}
}
Wrappers pour les méthodes avec des types non blittables
L’attribut UnmanagedCallersOnly
requiert que tous les types d’arguments et le type de retour de méthode soient blittables. Parmi ces types, le type bool
est couramment utilisé par les classes managées implémentant des méthodes Java. Il s’agit actuellement du seul type non blittable rencontré dans les liaisons, et est donc le seul pris en charge par le réécriture d’assembly.
Chaque fois qu’une méthode avec un type non blittable est rencontrée, un wrapper est généré pour celle-ci afin qu’elle puisse être décorée avec l’attribut UnmanagedCallersOnly
. Cette erreur est moins sujette à l’erreur que la modification du flux IL de la méthode de rappel native pour implémenter la conversion nécessaire. Un exemple de cette méthode est Android.Views.View.IOnTouchListener::OnTouch
:
static bool n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent(IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e)
{
var __this = global::Java.Lang.Object.GetObject<Android.Views.View.IOnTouchListener>(jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
var v = global::Java.Lang.Object.GetObject<Android.Views.View>(native_v, JniHandleOwnership.DoNotTransfer);
var e = global::Java.Lang.Object.GetObject<Android.Views.MotionEvent>(native_e, JniHandleOwnership.DoNotTransfer);
bool __ret = __this.OnTouch(v, e);
return __ret;
}
Cette méthode retourne une valeur bool
et a donc besoin d’un wrapper pour caster correctement la valeur de retour. Chaque méthode de wrapper conserve le nom de la méthode de rappel native, mais ajoute le suffixe _mm_wrapper
à celui-ci :
[UnmanagedCallersOnly]
static byte n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent__mm_wrapper(IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e)
{
try
{
return n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_(jnienv, native__this, native_v, native_e) ? 1 : 0;
}
catch (Exception ex)
{
Android.Runtime.AndroidEnvironmentInternal.UnhandledException(ex);
return default;
}
}
L’instruction return du wrapper utilise l’opérateur ternaire pour caster la valeur booléenne en 1 ou 0, car la valeur de bool
sur l’exécution managée peut prendre une plage de valeurs : 0
pour false
, -1
ou 1
pour true
, et != 0
pour true
.
Étant donné que le type bool
en C# peut être de 1, 2 ou 4 octets, il est casté en type de taille connue et statique. Le type managé byte
est utilisé puisqu’il correspond au type jboolean
Java/JNI, défini comme un type 8 bits non signé.
Chaque fois qu’une valeur d’argument doit être convertie entre byte
et bool
, un code équivalent à la comparaison argument != 0
est généré. Par exemple, pour la méthode Android.Views.View.IOnFocusChangeListener::OnFocusChange
:
[UnmanagedCallersOnly]
static void n_OnFocusChange_Landroid_view_View_Z(IntPtr jnienv, IntPtr native__this, IntPtr native_v, byte hasFocus)
{
n_OnFocusChange_Landroid_view_View_Z(jnienv, native__this, native_v, hasFocus != 0);
}
static void n_OnFocusChange_Landroid_view_View_Z(IntPtr jnienv, IntPtr native__this, IntPtr native_v, bool hasFocus)
{
var __this = global::Java.Lang.Object.GetObject<Android.Views.View.IOnFocusChangeListener>(jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
var v = global::Java.Lang.Object.GetObject<Android.Views.View>(native_v, JniHandleOwnership.DoNotTransfer);
__this.OnFocusChange(v, hasFocus);
}
Attribut UnmanagedCallersOnly
Chaque méthode de rappel native de méthodes marshalées est décorée avec l’attribut UnmanagedCallersOnly
pour pouvoir appeler le rappel directement à partir du code natif avec une surcharge minimale.
Marshaler la séquence d’appels d’inscription des méthodes
Ce qui est commun à l’inscription dynamique et l’inscription des méthodes marshalées est la résolution de la cible de fonction native effectuée par le runtime de la machine virtuelle Java. Dans les deux cas, la méthode déclarée dans une classe Java en tant que native
est recherchée par la machine virtuelle Java lors de la première stratégie juste-à-temps du code. La différence réside dans la façon dont cette recherche est effectuée.
L’inscription dynamique utilise la fonction JNI RegisterNatives
au moment de l’exécution, qui stocke un pointeur dans la méthode inscrite à l’intérieur de la structure qui décrit une classe Java dans la machine virtuelle Java.
Les méthodes marshalées, cependant, n’inscrivent rien auprès de la bibliothèque JNI. Au lieu de cela, elles s’appuient sur l’approche de recherche de symboles de la machine virtuelle Java. Chaque fois qu’un appel à une méthode native
Java est effectué juste-à-temps et n’est pas inscrit précédemment à l’aide de la fonction JNI RegisterNatives
, la machine virtuelle Java recherche les symboles dans l’image d’exécution du processus et, après avoir trouvé un symbole correspondant, utilise un pointeur vers celui-ci comme cible de l’appel de méthode native
Java.