Différence entre API et ABI

203

Je suis nouveau dans la programmation système Linux et je suis tombé sur l'API et l'ABI en lisant la programmation système Linux .

Définition de l'API:

Une API définit les interfaces par lesquelles un logiciel communique avec un autre au niveau source.

Définition de l'ABI:

Alors qu'une API définit une interface source, une ABI définit l'interface binaire de bas niveau entre deux ou plusieurs logiciels sur une architecture particulière. Il définit comment une application interagit avec elle-même, comment une application interagit avec le noyau et comment une application interagit avec les bibliothèques.

Comment un programme peut-il communiquer au niveau de la source? Qu'est-ce qu'un niveau source? Est-ce lié au code source de toute façon? Ou la source de la bibliothèque est-elle incluse dans le programme principal?

La seule différence que je connais est que l'API est principalement utilisée par les programmeurs et ABI est principalement utilisée par le compilateur.

Searock
la source
2
par niveau de source, ils signifient quelque chose comme un fichier d'inclusion pour exposer les définitions de fonction
Anycorn
1
Voir également stackoverflow.com/q/2171177/632951
Pacerier

Réponses:

52

L'API est ce que les humains utilisent. Nous écrivons du code source. Lorsque nous écrivons un programme et que nous voulons utiliser une fonction de bibliothèque, nous écrivons du code comme:

 long howManyDecibels = 123L;
 int ok = livenMyHills( howManyDecibels);

et nous avions besoin de savoir qu'il existe une méthode livenMyHills(), qui prend un long paramètre entier. Donc, en tant qu'interface de programmation, tout est exprimé en code source. Le compilateur transforme cela en instructions exécutables conformes à l'implémentation de ce langage sur ce système d'exploitation particulier. Et dans ce cas, il en résulte des opérations de bas niveau sur une unité audio. Ainsi, des bits et des octets particuliers sont éjectés sur certains matériels. Donc, au moment de l'exécution, il y a beaucoup d'actions au niveau binaire que nous ne voyons généralement pas.

djna
la source
317

API: interface de programme d'application

Il s'agit de l'ensemble des types / variables / fonctions publics que vous exposez à partir de votre application / bibliothèque.

En C / C ++, c'est ce que vous exposez dans les fichiers d'en-tête que vous livrez avec l'application.

ABI: interface binaire d'application

C'est ainsi que le compilateur crée une application.
Il définit les choses (mais n'est pas limité à):

  • Comment les paramètres sont passés aux fonctions (registres / pile).
  • Qui nettoie les paramètres de la pile (appelant / appelé).
  • Où la valeur de retour est placée pour le retour.
  • Comment les exceptions se propagent.
Martin York
la source
18
C'est probablement la meilleure explication concise de ce qu'est un ABI, que j'ai jamais vue; gj!
TerryP
3
Vous devez décider si cette réponse est concise ou détaillée. :)
jrok
1
@jrok: Les choses peuvent être concises et détaillées, elles ne s'excluent pas mutuellement.
Martin York
@LokiAstari, L'ABI n'est-il pas en fait une API aussi?
Pacerier
4
@Pacerier: Ce sont deux interfaces. Mais ils sont à différents niveaux d'abstraction. L'API est au niveau du développeur d'applications. ABI est au niveau du compilateur (quelque part un développeur d'application ne va jamais).
Martin York
47

Je rencontre principalement ces termes dans le sens d'un changement incompatible avec l'API ou d'un changement incompatible avec l'ABI.

Un changement d'API est essentiellement l'endroit où le code qui aurait été compilé avec la version précédente ne fonctionnera plus. Cela peut se produire parce que vous avez ajouté un argument à une fonction ou changé le nom d'un élément accessible en dehors de votre code local. Chaque fois que vous modifiez un en-tête et que cela vous oblige à modifier quelque chose dans un fichier .c / .cpp, vous avez effectué une modification d'API.

Un changement ABI est l'endroit où le code qui a déjà été compilé avec la version 1 ne fonctionnera plus avec la version 2 d'une base de code (généralement une bibliothèque). C'est généralement plus difficile à suivre que les changements incompatibles avec l'API car quelque chose d'aussi simple que l'ajout d'une méthode virtuelle à une classe peut être incompatible avec l'ABI.

J'ai trouvé deux ressources extrêmement utiles pour déterminer ce qu'est la compatibilité ABI et comment la préserver:

jkerian
la source
4
+1 pour avoir souligné leur exclusivité mutuelle. Par exemple, l'introduction par Java du mot-clé assert est une modification incompatible avec l'API mais compatible avec ABI docs.oracle.com/javase/7/docs/technotes/guides/language/… .
Pacerier
Vous pouvez ajouter à vos ressources la section "3.6. Bibliothèques incompatibles" de tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html , qui répertorie ce qui peut provoquer un changement d'ABI.
Demi-Lune
21

Voici mes explications profanes:

  • API - pensez aux includefichiers. Ils fournissent des interfaces de programmation.
  • ABI - pensez au module du noyau. Lorsque vous l'exécutez sur un noyau, il doit se mettre d'accord sur la façon de communiquer sans inclure de fichiers, c'est-à-dire comme interface binaire de bas niveau.
Anycorn
la source
15

Exemple d'API exécutable minimale de bibliothèque partagée Linux vs ABI

Cette réponse a été extraite de mon autre réponse: Qu'est-ce qu'une interface binaire d'application (ABI)? mais j'ai senti que cela répond directement à celle-ci aussi, et que les questions ne sont pas des doublons.

Dans le contexte des bibliothèques partagées, l'implication la plus importante d '"avoir un ABI stable" est que vous n'avez pas besoin de recompiler vos programmes après les changements de bibliothèque.

Comme nous le verrons dans l'exemple ci-dessous, il est possible de modifier l'ABI, interrompant les programmes, même si l'API est inchangée.

principal c

#include <assert.h>
#include <stdlib.h>

#include "mylib.h"

int main(void) {
    mylib_mystrict *myobject = mylib_init(1);
    assert(myobject->old_field == 1);
    free(myobject);
    return EXIT_SUCCESS;
}

mylib.c

#include <stdlib.h>

#include "mylib.h"

mylib_mystruct* mylib_init(int old_field) {
    mylib_mystruct *myobject;
    myobject = malloc(sizeof(mylib_mystruct));
    myobject->old_field = old_field;
    return myobject;
}

mylib.h

#ifndef MYLIB_H
#define MYLIB_H

typedef struct {
    int old_field;
} mylib_mystruct;

mylib_mystruct* mylib_init(int old_field);

#endif

Compile et fonctionne correctement avec:

cc='gcc -pedantic-errors -std=c89 -Wall -Wextra'
$cc -fPIC -c -o mylib.o mylib.c
$cc -L . -shared -o libmylib.so mylib.o
$cc -L . -o main.out main.c -lmylib
LD_LIBRARY_PATH=. ./main.out

Maintenant, supposons que pour la v2 de la bibliothèque, nous souhaitons ajouter un nouveau champ à mylib_mystrictappelé new_field.

Si nous avons ajouté le champ avant old_fieldcomme dans:

typedef struct {
    int new_field;
    int old_field;
} mylib_mystruct;

et reconstruit la bibliothèque mais pas main.out, alors l'assertion échoue!

C'est parce que la ligne:

myobject->old_field == 1

avait généré l'assembly qui tente d'accéder au tout premier intde la structure, qui est maintenant new_fieldau lieu de l'attendu old_field.

Par conséquent, ce changement a cassé l'ABI.

Si, cependant, nous ajoutons new_fieldaprès old_field:

typedef struct {
    int old_field;
    int new_field;
} mylib_mystruct;

alors l'ancien assembly généré accède toujours au premier intde la structure, et le programme fonctionne toujours, car nous avons maintenu l'ABI stable.

Voici une version entièrement automatisée de cet exemple sur GitHub .

Une autre façon de maintenir cette ABI stable aurait été de la traiter mylib_mystructcomme une structure opaque et d'accéder uniquement à ses champs via des assistants de méthode. Cela facilite le maintien de la stabilité de l'ABI, mais entraînerait une surcharge de performance car nous ferions plus d'appels de fonctions.

API vs ABI

Dans l'exemple précédent, il est intéressant de noter que l'ajout de l' new_fieldavant old_field, n'a cassé que l'ABI, mais pas l'API.

Ce que cela signifie, c'est que si nous avions recompilé notre main.cprogramme contre la bibliothèque, cela aurait fonctionné de toute façon.

Nous aurions également cassé l'API si nous avions changé par exemple la signature de la fonction:

mylib_mystruct* mylib_init(int old_field, int new_field);

car dans ce cas, main.carrêterait complètement la compilation.

API sémantique vs API de programmation vs ABI

Nous pouvons également classer les changements d'API dans un troisième type: les changements sémantiques.

Par exemple, si nous avions modifié

myobject->old_field = old_field;

à:

myobject->old_field = old_field + 1;

alors cela n'aurait cassé ni l'API ni l'ABI, mais main.cle casserait quand même!

Ceci est dû au fait que nous avons changé la "description humaine" de ce que la fonction est censée faire plutôt qu'un aspect perceptible par programme.

J'ai juste eu la perspicacité philosophique que la vérification formelle du logiciel dans un sens déplace plus de «l'API sémantique» vers une «API plus vérifiable par programme».

API sémantique vs API de programmation

Nous pouvons également classer les changements d'API dans un troisième type: les changements sémantiques.

L'API sémantique est généralement une description en langage naturel de ce que l'API est censée faire, généralement incluse dans la documentation de l'API.

Il est donc possible de casser l'API sémantique sans casser la construction du programme lui-même.

Par exemple, si nous avions modifié

myobject->old_field = old_field;

à:

myobject->old_field = old_field + 1;

alors cela n'aurait cassé ni l'API de programmation, ni l'ABI, mais main.cl'API sémantique serait cassée .

Il existe deux façons de vérifier par programme l'API du contrat:

  • tester un tas de boîtiers d'angle. Facile à faire, mais vous pourriez toujours en manquer un.
  • vérification formelle . Plus difficile à faire, mais produit une preuve mathématique de l'exactitude, unifiant essentiellement la documentation et les tests d'une manière vérifiable «humaine» / machine! Tant qu'il n'y a pas de bogue dans votre description formelle bien sûr ;-)

Testé dans Ubuntu 18.10, GCC 8.2.0.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
2
Votre réponse était suffisamment détaillée pour m'aider à comprendre la différence entre API et ABI. Merci!
Rakshith Ravi
9

( A pplication B inaire I nterface) Une spécification pour une plate - forme matérielle spécifique combiné avec le système d'exploitation. Il est une étape au - delà de l'API ( A pplication P rogramme I nterface), qui définit les appels provenant de l'application au système d'exploitation. L'ABI définit l'API ainsi que le langage machine pour une famille de CPU particulière. Une API n'assure pas la compatibilité d'exécution, mais une ABI le fait, car elle définit le format du langage machine, ou du format d'exécution.

entrez la description de l'image ici

Courtoisie

Premraj
la source
9

Permettez-moi de donner un exemple spécifique de la différence entre ABI et API en Java.

Un changement incompatible ABI est si je change une méthode A # m () de prendre a Stringcomme argument en String...argument. Ce n'est pas compatible ABI car vous devez recompiler le code qui appelle cela, mais il est compatible avec l'API car vous pouvez le résoudre en recompilant sans aucune modification de code dans l'appelant.

Voici l'exemple énoncé. J'ai ma bibliothèque Java avec la classe A

// Version 1.0.0
public class A {
    public void m(String string) {
        System.out.println(string);
    }
}

Et j'ai une classe qui utilise cette bibliothèque

public class Main {
    public static void main(String[] args) {
        (new A()).m("string");
    }
}

Maintenant, l'auteur de la bibliothèque a compilé sa classe A, j'ai compilé ma classe Main et tout fonctionne bien. Imaginez qu'une nouvelle version de A arrive

// Version 2.0.0
public class A {
    public void m(String... string) {
        System.out.println(string[0]);
    }
}

Si je prends simplement la nouvelle classe compilée A et la dépose avec la classe Main précédemment compilée, j'obtiens une exception en tentant d'appeler la méthode

Exception in thread "main" java.lang.NoSuchMethodError: A.m(Ljava/lang/String;)V
        at Main.main(Main.java:5)

Si je recompile Main, cela est corrigé et tout fonctionne à nouveau.

user7610
la source
7

Votre programme (code source) peut être compilé avec des modules qui fournissent une API appropriée .

Votre programme (binaire) peut fonctionner sur des plates-formes qui fournissent un ABI approprié .

L'API restreint les définitions de type, les définitions de fonction, les macros, parfois les variables globales qu'une bibliothèque doit exposer.

ABI restreint ce qu'une «plate-forme» doit fournir pour que votre programme s'exécute. J'aime le considérer en 3 niveaux:

  • niveau du processeur - le jeu d'instructions, la convention d'appel

  • niveau du noyau - la convention d'appel système, la convention spéciale de chemin de fichier (par exemple les fichiers /procet /syssous Linux), etc.

  • Niveau du système d'exploitation - le format de l'objet, les bibliothèques d'exécution, etc.

Considérez un compilateur croisé nommé arm-linux-gnueabi-gcc. "arm" indique l'architecture du processeur, "linux" indique le noyau, "gnu" indique que ses programmes cibles utilisent la libc de GNU comme bibliothèque d'exécution, différente de arm-linux-androideabi-gcccelle utilisée par l'implémentation libc d'Android.

ZhouZhuo
la source
1
c'est une explication très succincte de la différence entre eux, et dans une perspective très unique.
Sajuuk
1

API- Application Programming Interfaceest une interface de compilation qui peut être utilisée par le développeur pour utiliser des fonctionnalités non liées au projet telles que la bibliothèque, le système d'exploitation, les appels de base dans le code source

ABI[About] -Application Binary Interfaceest uneinterface d' exécution qui est utilisée par un programme pendant l'exécution pour la communication entre les composants dans le code machine

yoAlex5
la source