API和ABI之间的区别

时间:2010-09-24 05:18:07

标签: api abi

我是linux系统编程的新手,我在阅读时遇到了API和ABI Linux系统编程

API的定义:

  

API定义了接口   一个软件进行通信   与另一个在源级别。

ABI的定义:

  

API定义了一个源   接口,ABI定义了   两者之间的低级二进制接口   或更多的软件   特殊的架构。它定义了   应用程序如何与之交互   本身,应用程序如何交互   与内核,以及如何   应用程序与库交互。

程序如何在源级别进行通信?什么是源级别?它无论如何都与源代码有关?或者库的源代码包含在主程序中?

我所知道的唯一区别是API主要由程序员使用,而ABI主要由编译器使用。

11 个答案:

答案 0 :(得分:273)

API:应用程序接口

这是您从应用程序/库中公开的一组公共类型/变量/函数。

在C / C ++中,这是您在应用程序附带的头文件中公开的内容。

ABI:应用程序二进制接口

这是编译器构建应用程序的方式 它定义了事物(但不仅限于):

  • 如何将参数传递给函数(寄存器/堆栈)。
  • 谁清除堆栈中的参数(来电者/被叫者)。
  • 返回值的位置。
  • 例外如何传播。

答案 1 :(得分:41)

API是人类使用的。我们写源代码。当我们编写程序并想要使用某些库函数时,我们编写如下代码:

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

我们需要知道有一个方法livenMyHills(),它接受​​一个长整数参数。所以作为一个编程接口,它都用源代码表示。编译器将其转换为可执行指令,这些指令符合此特定操作系统上此语言的实现。在这种情况下,会导致音频单元上的某些低级操作。因此,在某些硬件上会喷射特定的位和字节。因此,在运行时,我们通常不会看到很多二进制级别的操作。

答案 2 :(得分:40)

我主要是因为API不兼容的更改或ABI不兼容的更改而遇到这些术语。

API更改本质上是使用以前版本编译的代码将不再起作用的地方。这可能是因为您向函数添加了参数,或者更改了本地代码之外可访问的名称。无论何时更改标题,它都会强制您更改.c / .cpp文件中的内容,您都进行了API更改。

ABI更改是指已针对版本1编译的代码将不再适用于代码库的版本2(通常是库)。与API不兼容的更改相比,这通常更难以跟踪,因为向类添加虚拟方法这样简单的操作可能与ABI不兼容。

我找到了两个非常有用的资源,用于确定ABI的兼容性以及如何保护它:

答案 3 :(得分:19)

这是我的外行解释:

  • api - 认为include个文件。他们提供编程接口
  • abi - 思考内核模块。当你在某个内核上运行它时,他们必须同意如何在没有包含文件的情况下进行通信,即作为低级二进制接口

答案 4 :(得分:7)

A 应用 B inary I nterface)与操作系统结合的特定硬件平台的规范。它超越了API( A 应用 P rogram I 接口),它定义了从应用程序到操作系统的调用。 ABI定义了API以及特定CPU系列的机器语言。 API不能确保运行时兼容性,但ABI确实如此,因为它定义了机器语言或运行时格式。

enter image description here

Courtesy

答案 5 :(得分:7)

让我举一个具体的例子,说明ABI和API在Java中的区别。

ABI不兼容的更改是我将方法A#m()更改为将String作为参数更改为String...参数。这是与ABI不兼容的,因为您必须重新编译调用它的代码,但它是API兼容的,因为您可以通过重新编译来解决它,而无需在调用者中进行任何代码更改。

以下是拼写的示例。我有一个带有A类的Java库

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

我有一个使用这个库的课程

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

现在,图书馆作者编写了他们的A类,我编写了我的类Main,它们都运行良好。想象一下A的新版本

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

如果我只使用新编译的类A并将其与先前编译的类Main一起删除,则在尝试调用方法时会出现异常

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

如果我重新编译Main,这是固定的,一切都在重新编译。

答案 6 :(得分:3)

您的程序(源代码)可以使用提供正确 API 的模块进行编译。

您的程序(二进制)可以在提供正确 ABI 的平台上运行。

API限制类型定义,函数定义,宏,有时是库应该公开的全局变量。

ABI限制“平台”应该为您运行的程序提供什么。我喜欢在3个级别中考虑它:

  • 处理器级别 - 指令集,调用约定

  • 内核级别 - 系统调用约定,特殊文件路径约定(例如Linux中的/proc/sys文件)等。

  • 操作系统级别 - 对象格式,运行时库等

考虑一个名为arm-linux-gnueabi-gcc的交叉编译器。 “arm”表示处理器体系结构,“linux”表示内核,“gnu”表示其目标程序使用GNU的libc作为运行时库,不同于使用Android的libc实现的arm-linux-androideabi-gcc

答案 7 :(得分:3)

Linux共享库最小可运行API与ABI示例

此答案是从我的其他答案中提取的:What is an application binary interface (ABI)?,但我觉得它也直接回答了这个问题,并且这些问题不是重复的。

在共享库的上下文中,“具有稳定的ABI”最重要的含义是,在更改库后,您无需重新编译程序。

如下面的示例所示,即使API不变,也可以修改ABI,破坏程序。

main.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

编译并运行良好:

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

现在,假设对于库的v2,我们想向mylib_mystrict添加一个名为new_field的新字段。

如果我们将字段添加到old_field之前,如下所示:

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

并重建了库,但没有重建main.out,则断言失败!

这是因为该行:

myobject->old_field == 1

生成了试图访问结构的第一个int的程序集,该程序集现在是new_field而不是预期的old_field

因此,此更改破坏了ABI。

但是,如果我们在new_field之后添加old_field

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

然后,旧生成的程序集仍然会访问该结构的第一个int,并且该程序仍然可以运行,因为我们保持了ABI的稳定。

这里是fully automated version of this example on GitHub

保持此ABI稳定的另一种方法是将mylib_mystruct视为opaque struct,并且只能通过方法助手来访问其字段。这样可以更轻松地保持ABI的稳定,但是会增加性能开销,因为我们需要执行更多的函数调用。

API与ABI

在前面的示例中,有趣的是注意到在new_field之前添加old_field只会破坏ABI,而不会破坏API。

这意味着,如果我们针对库重新编译main.c程序,则无论如何它都可以工作。

我们也会破坏API,但是如果我们更改了例如函数签名:

mylib_mystruct* mylib_init(int old_field, int new_field);

因为在这种情况下,main.c将完全停止编译。

语义API与编程API与ABI

我们还可以将API更改分类为第三种类型:语义更改。

例如,如果我们已经修改

myobject->old_field = old_field;

收件人:

myobject->old_field = old_field + 1;

这将不会破坏API或ABI,但是main.c仍然会破坏!

这是因为我们更改了该功能应该执行的“人工描述”,而不是程序上引人注目的方面。

我只是有一种哲学上的见解,即formal verification of software在某种意义上将更多的“语义API”转移到了一个“可程序验证的API”中。

语义API与编程API

我们还可以将API更改分类为第三种类型:语义更改。

语义API通常是API应该执行的自然语言描述,通常包含在API文档中。

因此可以在不破坏程序本身的情况下破坏语义API。

例如,如果我们已经修改

myobject->old_field = old_field;

收件人:

myobject->old_field = old_field + 1;

这将不会破坏编程API或ABI,但是main.c语义API会破坏。

有两种方法可以以编程方式检查合同API:

  • 测试一些极端情况。容易做到,但您可能总是会错过一个。
  • formal verification。难度较大,但会产生正确性的数学证明,从本质上将文档和测试统一为“人工” /机器可验证的方式!只要您对课程的形式描述中没有错误;-)

在Ubuntu 18.10,GCC 8.2.0中进行了测试。

答案 8 :(得分:1)

enter image description here

Application programming interface (API)-由最高抽象级别表示。该API将应用程序连接到库和/或核心OS。

Application Binary Interface (ABI)涵盖了诸如低级数据类型和调用约定之类的事实,并且它还定义了许多程序的格式。主要是在此级别定义系统调用。而且,这种类型的接口可实现跨使用相同ABI的OS的各种应用程序和库的可移植性。

了解更多here

答案 9 :(得分:0)

API-Application Programming Interface编译时间接口,开发人员可以使用它来使用非项目功能,例如库,操作系统,源代码中的核心调用代码

ABI [About]-Application Binary Interface运行时界面,程序在执行过程中会使用该界面在机器代码中的组件之间进行通信

答案 10 :(得分:0)

ABI 从成功链接、加载和执行某些二进制文件的角度来看,是指目标文件/库和最终二进制文件的布局,而不会因二进制文件不兼容而发生链接错误或逻辑错误。

  • 二进制格式规范(PE、COFF、ELF、.obj、.o、.a、.lib(导入库、静态库)、.NET 程序集、.pyc、COM .dll):标头、标头格式,定义节的位置以及导入/导出/异常表的位置以及它们的格式
  • 用于对代码部分中的字节进行编码的指令集,以及具体的机器指令
  • API 中定义的函数和数据的实际签名(以及它们在二进制文件中的表示方式(接下来的 2 点))
  • 代码部分中函数的调用约定,可能会被其他二进制文件调用(与 ABI 兼容性特别相关,即实际导出的函数)
  • 数据部分在数据类型中的表示和对齐方式(与 ABI 兼容性特别相关,即实际导出的数据)
  • 代码中挂钩的系统调用号或中断向量
  • 导出函数和数据的名称修饰
  • 目标文件中的链接器指令
  • API 程序员使用的预处理器/编译器/汇编器/链接器标志和指令,以及如何解释它们以省略、优化、内联或更改库或最终二进制文件中某些符号或代码的链接(该二进制文件是 . dll 或静态链接时的可执行文件)

.NET C# 的字节码格式是 ABI(通用),其中包括 .NET 程序集 .dll 格式。解释字节码的虚拟机有一个基于 C++ 的特定 ABI,当从本机代码和本机调用字节码时,需要在本机代码特定 ABI 使用的本机 C++ 类型和虚拟机 ABI 的装箱类型之间编组类型来自字节码的代码。在这里,我将特定程序的 ABI 称为特定 ABI,而一般的 ABI,例如“MS ABI”或“C ABI”仅指调用约定和组织结构的方式,而不是指ABI 由特定的二进制文件组成,这引入了新级别的 ABI 兼容性问题。

API 是指特定库导出的类型定义集,在特定翻译单元中导入和使用,从翻译单元的编译器的角度来看,成功解析和检查类型引用,以便能够编译一个二进制文件,并且该二进制文件将遵守目标 ABI 的标准,这样,如果实际实现 API 的库也被编译为兼容的 ABI,它将按预期链接和工作。如果 API 更新,应用程序可能仍能编译,但现在会出现二进制不兼容,因此需要使用新的二进制。

API 涉及:

  • 函数、变量、类、对象、常量、它们的名称、类型和定义以语法和语义正确的方式编码的语言呈现
  • 这些函数实际上做了什么以及如何在源语言中使用它们
  • 需要包含的源代码文件/需要链接到才能使用它们的二进制文件,以及它们的 ABI 兼容性