API的定义:
API定义了接口 一个软件进行通信
ABI的定义:
而API定义了一个源 接口时,ABI定义了 两个之间的低级二进制接口 或者更多的软件 特定的体系结构。它定义了 应用程序如何与 本身,应用程序如何交互 与内核,以及如何一个 应用程序与库交互
程序如何在源级进行通信?什么是源级别?它是否与源代码有关?或者库的源代码包含在主程序中?
我所知道的唯一区别是API主要由程序员使用,而ABI主要由编译器使用。
这是我的外行解释:
include
API是人类使用的。我们编写源代码。当我们编写程序并想要使用一些库函数时,我们编写如下代码:
long howManyDecibels = 123L; int ok = livenMyHills(howManyDecibels);
我们需要知道有一个方法livenMyHills(),它接受一个长整型参数。所以作为一个编程接口,它都是用源代码表示的。编译器将其转换为可执行指令,这些指令符合该语言在特定操作系统上的实现。在这种情况下,会导致音频单元上的一些低级操作。所以特定的比特和字节被喷到一些硬件上。所以在运行时有很多二进制级别的动作,我们通常看不到。
livenMyHills()
在二进制级别,必须对在二进制级别传递的字节有一个精确的定义,例如4字节整数中的字节顺序,或者复杂数据结构的布局——是否有填充字节来对齐一些值。这个定义就是ABI。
这是您从应用程序/库中公开的公共类型/变量/函数集。
在C/ c++中,这是在应用程序附带的头文件中公开的内容。
这是编译器构建应用程序的方式 它定义的东西(但不限于):
我经常在api不兼容的更改或abi不兼容的更改的意义上遇到这些术语。
API更改本质上是使用以前版本编译的代码将不再工作。这可能是因为您向函数添加了参数,或者更改了在本地代码之外可访问的内容的名称。任何时候,当您更改一个头文件时,它就会迫使您更改.c/.cpp文件中的某些内容,您就做了一个api更改。
ABI更改是指已经根据版本1编译的代码将不再使用版本2的代码库(通常是库)。这通常比与api不兼容的更改更难跟踪,因为像向类添加虚拟方法这样简单的事情可能与ABI不兼容。
我发现了两种非常有用的资源,用于了解什么是ABI兼容性以及如何保存它:
(__abc0application Binary 我nterface)结合操作系统的特定硬件平台的规范。它是API (__abc0application __abc4program 我nterface)的一步,API定义了从应用程序到操作系统的调用。ABI为特定的CPU系列定义API和机器语言。API不能确保运行时兼容性,但ABI可以,因为它定义了机器语言或运行时格式。
礼貌
让我举一个具体的例子,说明在Java中ABI和API是如何不同的。
ABI不兼容的更改是如果我将方法a# m()从String作为参数更改为String...参数。这是不兼容ABI,因为你必须重新编译调用它的代码,但它是API兼容,因为你可以通过重新编译来解决它,而不需要在调用者中修改任何代码。
String
String...
这里有一个例子。我的Java库有类A
// 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,这是固定的,所有工作再次。
你的程序(源代码)可以用提供适当API的模块来编译。
你的程序(二进制)可以在提供适当ABI的平台上运行。
API限制了类型定义、函数定义、宏,有时还有库应该公开的全局变量。
ABI限制了一个“平台”应该为您的程序运行提供什么。我喜欢从三个层面来考虑:
处理器级——指令集,调用约定
内核级——系统调用约定,特殊的文件路径约定(例如Linux中的/proc和/sys文件),等等。
/proc
/sys
操作系统级别——对象格式、运行时库等。
考虑一个名为arm-linux-gnueabi-gcc的交叉编译器。“arm”表示处理器架构,“linux”表示内核,“gnu”表示其目标程序使用gnu的libc作为运行时库,不同于arm-linux-androideabi-gcc使用Android的libc实现。
arm-linux-gnueabi-gcc
arm-linux-androideabi-gcc
Linux共享库最小可运行API vs ABI示例
这个答案是从我的另一个答案中提取出来的:什么是应用程序二进制接口(ABI)?,但我觉得它也直接回答了这个问题,而且这些问题不是重复的。
在共享库的背景下,“拥有稳定的abi”最重要的含义是:你不需要在库改变后重新编译你的程序。
正如我们将在下面的示例中看到的,即使API没有改变,也可以修改ABI,从而破坏程序。
c
#include <assert.h> #include <stdlib.h> #include "mylib.h" int main(void) { mylib_mystruct *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_mystruct中添加一个名为new_field的新字段。
mylib_mystruct
new_field
如果我们在old_field之前添加字段,如下所示:
old_field
typedef struct { int new_field; int old_field; } mylib_mystruct;
并且重新构建了库而不是main.out,那么断言失败!
main.out
这是因为这一行:
myobject->old_field == 1
生成的程序集试图访问结构的第一个int,现在是new_field,而不是预期的old_field。
int
因此,这个更改破坏了ABI。
然而,如果我们在old_field之后添加new_field:
typedef struct { int old_field; int new_field; } mylib_mystruct;
那么旧生成的程序集仍然访问结构的第一个int,程序仍然工作,因为我们保持了ABI的稳定。
这是一个这个例子在GitHub上的全自动版本。
另一种保持ABI稳定的方法是将mylib_mystruct视为不透明的结构,并且只通过方法帮助器访问它的字段。这样可以更容易地保持ABI的稳定,但是由于我们要进行更多的函数调用,因此会产生性能开销。
API vs ABI
在前面的例子中,有趣的是,在old_field之前添加new_field只破坏了ABI,而没有破坏API。
这意味着,如果我们根据标准库重新编译了main.c程序,无论如何它都会工作。
main.c
然而,如果我们改变了例如函数签名,我们也会破坏API:
mylib_mystruct* mylib_init(int old_field, int new_field);
因为在这种情况下,main.c将完全停止编译。
语义API vs编程API
我们还可以将API更改分为第三种类型:语义更改。
语义API通常是API应该做什么的自然语言描述,通常包含在API文档中。
因此,可以在不破坏程序构建本身的情况下破坏语义API。
例如,如果我们修改了
myobject->old_field = old_field;
:
myobject->old_field = old_field + 1;
那么这既不会破坏编程API,也不会破坏ABI,但是main.c语义API会破坏。
有两种方法可以通过编程方式检查合约API:
在Ubuntu 18.10, GCC 8.2.0中测试。
API - Application Programming Interface是一个编译时间接口,可以被开发人员用来使用源代码中的库、操作系统、核心调用等非项目功能
API
Application Programming Interface
ABI<一口>[对]< /一口> - Application Binary Interface是一个运行时接口,程序在执行过程中使用它在机器代码中的组件之间进行通信
ABI
Application Binary Interface
ABI指的是目标文件/库和最终二进制文件的布局,从成功链接、加载和执行某些二进制文件的角度出发,而不会因为二进制文件不兼容而出现链接错误或逻辑错误。
. 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包括:
我先回答你们的具体问题。
1.什么是源级别?它是否与源代码有关?
是的,源代码级别这个术语指的是源代码的级别。术语级别指的是计算需求的语义级别,因为它们从应用程序域级别转换到源代码级别,又从源代码级别转换到机器代码级别(二进制代码)。应用程序域级别指的是软件的最终用户想要什么,并指定为他们的计算需求。源代码级是指程序员对应用程序级需求的理解,然后用某种语言指定为程序。
语言API专门指一种语言需要(指定)(即接口)用该语言编写可重用的模块。可重用程序符合这些接口(API)要求,以便在相同语言的其他程序中重用。每次重用都需要符合相同的API需求。所以,“交流”这个词;指重用。
是的,源代码(可重用模块的源代码;在C/ c++的情况下,.h文件)被包含(在预处理阶段复制)是重用在 C/ c++的常用方式,因此是c++ API的一部分。即使你只是在c++程序的全局空间中编写了一个简单的函数foo(),然后调用该函数foo();,也可以根据c++语言API重用多次。Java包中的Java类是Java中可重用的模块。Java bean规范也是一个Java API,在运行时/容器(符合该规范)的帮助下,允许其他模块(可能是另一个bean)重用可重用程序(bean)。
foo();
关于语言API和ABI之间的区别,以及面向服务的API如何与语言API进行比较,我在SO上的回答应该会有帮助。