Opération du chargeur du jeu d’API

Important

Les informations contenues dans cette rubrique s’appliquent à toutes les versions de Windows 10 et ultérieures. Nous allons faire référence à ces versions ici en tant que « Windows », en appelant toutes les exceptions si nécessaire.

Les ensembles d’API s’appuient sur la prise en charge du système d’exploitation dans le chargeur de bibliothèque pour introduire une redirection d’espace de noms de module dans le processus de liaison de bibliothèque. Un nom de contrat d’ensemble d’API ne nomme pas de fichier. Le chargeur effectue une redirection au moment de l’exécution de ce nom de contrat vers le fichier binaire hôte qui contient l’implémentation.

Lorsque le chargeur rencontre une dépendance sur un ensemble d’API au moment de l’exécution, il consulte les données de configuration dans l’image pour identifier le binaire hôte de cet ensemble d’API. Ces données de configuration sont appelées schéma du jeu d’API. Le schéma est assemblé en tant que propriété du système d’exploitation, et le mappage entre les ensembles d’API et les fichiers binaires peut différer selon les fichiers binaires inclus sur un appareil donné. Le schéma permet à une fonction importée d’être routée correctement sur différents appareils, même lorsque le module qui héberge l’implémentation a été renommé, fractionné ou refactorisé.

Comment les importations atteignent une implémentation

Un binaire peut atteindre une implémentation d’ensemble d’API de deux façons, décidé par le nom dans sa table d’importation :

  • Importation directe de l’ensemble d’API. Le fichier binaire importe un nom de contrat d’ensemble d’API. Le chargeur résout ce nom via le schéma du jeu d’API sur le binaire hôte sur l’appareil actuel.
  • Importation de module héritée. Le fichier binaire importe un nom de module Windows hérité, tel que samplefeature.dll. Sur une édition fournie par ce module, le chargeur le lie directement. Sur une édition qui l’a remplacée, un redirecteur inverse portant le même nom redirige l’importation vers un jeu d’API, que le chargeur résout ensuite via le schéma.

Parmi ces noms, ceux-ci se retrouvent dans votre table d’importation est généralement déterminé par la bibliothèque à laquelle vous liez plutôt que par la source que vous écrivez. Consultez Windows bibliothèques de parapluies.

Préférez le nom de contrat du jeu d’API pour le code qui cible les versions actuelles de Windows. Le chargeur le résout directement vers l’hôte, sans transfert entre eux. Importez le nom du module hérité lorsque vous avez besoin d’un binaire unique qui s’exécute également sur Windows versions publiées avant l’existence du jeu d’API. Le transfert inverse conserve ce fichier binaire fonctionnant sur les éditions où le module hérité a été remplacé.

Importation directe de l’ensemble d’API

La résolution est une séquence en trois étapes :

  1. Votre fichier binaire importe un nom de contrat d’ensemble d’API ou en transmet un à LoadLibrary.
  2. Le chargeur recherche le contrat dans le schéma du jeu d’API sur l’appareil actuel et recherche le binaire hôte auquel le schéma le mappe.
  3. Le chargeur charge ce fichier binaire hôte et lie la fonction importée à l’exportation de l’hôte.

Étant donné que le mappage réside dans le schéma plutôt que dans le système de fichiers, la même importation peut être résolue en différents fichiers binaires sur différents appareils :

Appareil api-win-core-samplefeature mappe à
Appareil qui inclut la fonctionnalité samplefeature.dll
Appareil qui fournit une implémentation refactorisé samplefeaturecore.dll
Appareil qui n’inclut pas la fonctionnalité Non mappé

Les samplefeature noms utilisés ici sont des noms illustrant un composant de Windows fictif.

Le binaire consommateur n’est pas conscient de l’hôte auquel il était lié. C’est le point du mécanisme : le contrat est stable, tandis que le module qui implémente est libre de passer d’un appareil à l’autre.

Une importation d’un nom de contrat est résolue dans une seule opération, sans module de redirecteur intermédiaire impliqué. Il s’agit du formulaire le plus efficace et du chemin normal du code écrit par rapport aux ensembles d’API.

Noms des ensembles d’API et suffixe .dll

Étant donné que les mappages sont conservés dans le schéma plutôt que sur le disque, un nom d’ensemble d’API qui se termine par .dll ne fait pas référence à un fichier de ce nom. La partie .dll n’est qu’une convention d’affectation de noms, transférée de la façon dont les noms de module sont orthographiés dans une table d’importation. Le nom de l’ensemble d’API est plus semblable à un alias, ou un nom virtuel, pour un fichier DLL physique.

Lorsqu’une opération de chargeur reçoit un nom commençant par api- ou ext-, le chargeur l’achemine vers le runtime du jeu d’API, une extension du chargeur qui résout les contrats via le schéma. Le runtime du jeu d’API analyse le nom par les règles d’affectation de noms des ensembles d’API plutôt que comme nom de fichier. Par conséquent, le suffixe .dll ne fait pas partie du nom du contrat qui est résolu. Incluez le suffixe lorsque vous travaillez à partir d’un nom tel qu’il apparaît dans une table d’importation ; sinon, vous pouvez le laisser hors service.

Le chargeur résout les deux formes de nom de contrat, un nom de contrat versionné et un alias de contrat, via le même schéma. Pour connaître les conventions qui régissent ces noms, consultez les noms de contrat de l’ensemble d’API.

La stabilité du nom n’est pas la même que la disponibilité

Un nom d'ensemble d'API est stable sur les appareils Windows, dans le sens où le même nom identifie toujours le même contrat où il est reconnu. Il s’agit d’une propriété de l’espace de noms, et non d’un appareil particulier.

Un contrat donné peut être absent d’un appareil ou présent, mais pas mappé à un hôte. Rien sur le nom ne vous indique quoi. Pour savoir si l’implémentation est en fait là, consultez Détecter la disponibilité du groupe d’API.

Quelle résolution nécessite

Pour qu’un appel via un ensemble d’API atteigne une implémentation, tous les éléments suivants doivent contenir :

  • Le contrat est présent dans le schéma sur l’appareil actuel.
  • Le schéma mappe le contrat à un fichier binaire hôte et cet hôte peut être chargé.
  • L’hôte exporte la fonction spécifique que votre binaire appelle.

Quand l’un de ces éléments ne tient pas, où les surfaces d’échec dépendent de la façon dont vous avez importé l’ensemble d’API :

Style d’importation Comportement lorsque le contrat ne peut pas être résolu
Importation statique Le processus ne démarre pas. Le chargeur résout les importations statiques avant l’exécution de l’un de vos codes.
Importation chargée en retard Le processus démarre normalement. La résolution est différée au premier appel à l’API, où votre code peut gérer l’échec.

Une exportation manquante est signalée séparément d’un contrat manquant ; un binaire qui importe une fonction que l’hôte n’exporte pas échoue avec une erreur de point d’entrée manquante.

Qu’est-ce qu’une charge réussie ne vous indique pas ?

La résolution lie un contrat à un hôte. Elle n’évalue pas l’état d’une fonctionnalité individuelle dans ce contrat.

Un contrat peut organiser ses fonctionnalités disponibles individuellement en groupes nommés. Un groupe peut être indisponible sur un appareil même si le contrat qui l’exécute normalement, car le chargeur lie à la granularité du contrat et ne consulte pas l’état du groupe lorsqu’il est lié. C’est délibéré : refuser de lier un hôte est irrécupérable à une importation statique, donc le chargeur prend le chemin permissif et laisse la question plus fine à l’appelant.

La conséquence de votre code est qu’une charge réussie ou un appel LoadLibrary réussi n’est pas une preuve qu’une fonctionnalité particulière est disponible. Posez cette question explicitement avec une requête de disponibilité. Consultez Détecter la disponibilité du groupe d’API.

Ensembles d’API facultatifs et chargement différé

Si votre application appelle un ensemble d’API qui n’est peut-être pas présent, une vérification de disponibilité ne suffit pas : avec une importation statique, le processus ne démarre pas, de sorte que l’exécution n’atteint jamais la vérification.

Pour conserver le chemin de code facultatif accessible, configurez le module qui contient l’API facultative pour le chargement différé, ou résolvez la cible dynamiquement avec LoadLibrary et GetProcAddress une fois qu’une requête de disponibilité réussit. Pour plus d’informations sur les deux approches, consultez Détecter la disponibilité du groupe d’API.

Transfert inverse

Bien que les noms des ensembles d’API fournissent un espace de noms stable pour les modules sur les appareils, il n’est pas toujours pratique de convertir chaque binaire dans ce système. Une application peut avoir été utilisée depuis de nombreuses années, et la recompilation de ses fichiers binaires peut ne pas être réalisable. Certaines applications doivent également continuer à s’exécuter sur les systèmes créés avant l’introduction de jeux d’API spécifiques.

Pour prendre en charge cela, les éditions qui ne contiennent pas les modules d'origine incluent un ensemble de redirecteurs inverses : les fichiers binaires de compatibilité qui portent les noms de modules introduits à l'origine sur les PC Windows et qui redirigent leurs exportations vers des ensembles d'API.

Une édition de bureau complète fournit les modules d’origine, de sorte qu’une importation d’un nom de module hérité est liée au module comme il l’a toujours fait. Sur une édition qui a remplacé ce module, le redirecteur inverse portant le même nom couvre l’écart.

L’opération de chargeur se comporte comme suit :

  1. Le chargeur est présenté avec une dépendance sur un nom de module Windows PC hérité qui n'est pas présent sur l'appareil.
  2. Le chargeur localise un redirecteur inverse qui porte ce nom de module et le charge.
  3. Le redirecteur inverse redirige la fonction importée vers un jeu d’API.
  4. Le chargeur résout ce jeu d’API par le biais du schéma, comme décrit précédemment dans cette rubrique.

Conceptuellement, le mappage ressemble à ceci :

DLL importée : samplefeature.dll

  • Sur une édition avec le module d’origine : samplefeature.dll
  • Sur une édition qui l’a remplacée : samplefeature.dll redirecteur inverse ->api-win-core-samplefeature -samplefeaturecore.dll>

La limite de ce chemin d’accès est la couverture de l’exportation. Un redirecteur inverse transporte uniquement les exportations qui ont des équivalents de l’ensemble d’API. Il n’exporte donc pas nécessairement chaque fonction que le module d’origine a effectuée. Un binaire qui importe une fonction que le redirecteur inverse ne transporte pas ne parvient pas à charger avec une erreur de point d’entrée manquante.

Le transfert inverse est également une raison de ne pas traiter une résolution réussie comme une preuve qu’une implémentation est présente. Un appel GetProcAddress sur un nom de module hérité peut retourner un pointeur de fonction valide qui se résout en un stub retournant une erreur. Disponibilité des requêtes explicitement à la place. Consultez Détecter la disponibilité du groupe d’API.

Note

Le transfert inverse couvre uniquement un sous-ensemble de l’aire d’API Win32. Il n'autorise pas les applications qui ciblent les versions de bureau de Windows à s'exécuter sur tous les appareils Windows. Si votre fichier binaire cible les versions actuelles de Windows, le nom du contrat de l’ensemble d’API est le choix le plus direct.

Voir aussi