V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
kaiduo
V2EX  ›  程序员

API 与 ABI 的区别

  •  
  •   kaiduo · 2021-12-20 01:01:37 +08:00 · 3470 次点击
    这是一个创建于 1061 天前的主题,其中的信息可能已经有所发展或是发生改变。

    image

    原文链接: https://github.com/xiaoxiaojx/blog/issues/22

    背景

    Node-API 的基本概念里面提到了 ABI, 前端开发的同学对这个词语可能就比较陌生,和平时经常提到的 API 有什么区别?

    Node-API (以前称为 N-API )是用于构建原生插件的 API 。它独立于底层 JavaScript 运行时(例如,V8 )并作为 Node.js 本身的一部分进行维护。此 API 将在 Node.js 的各个版本中保持稳定的应用程序二进制接口 (ABI)。它旨在将插件与底层 JavaScript 引擎中的更改隔离开来,并允许为一个主要版本编译的模块无需重新编译即可在 Node.js 的后续主要版本上运行

    API 与 ABI

    API 应用程序接口

    这是从应用程序 /库公开的一组公共类型 /变量 /函数。 在 C/C++ 中,这是应用程序附带的头文件中公开的内容。

    ABI 二进制接口

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

    • 如何将参数传递给函数(寄存器 /堆栈)
    • 谁从堆栈中清除参数(调用者 /被调用者)
    • 返回值放置的位置以供返回
    • 异常如何传播

    举个例子

    下面的 main.c 程序依赖了 mylib 这个库, mylib 这个库对外暴露了 mylib_init 这个接口, 该接口的出参与入参可以看 mylib.h 中的类型定义。

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

    现在 mylib 这个库进行了 v2 版本的升级。v2 版本修改了 mylib_mystruct 的定义, 新增加了 new_field 字段,新的定义如下

    // mylib.h
    
    typedef struct {
        int new_field;
        int old_field;
    } mylib_mystruct;
    

    此时我们只重新编译 mylib, 不重新编译 main.c 主程序。然后运行 main.out, 发现 main 函数里面的 assert 错误了...

    // main.c
    
    assert(myobject->old_field == 1);
    

    因为 myobject 还是访问的第一个字段, 但是现在第一个字段为 new_field 了,程序中并没有为它赋值。此时对于用户来说 API 没有造成 break change, 可以不用修改代码来适配。但是由于 ABI 的 break change 导致需要重新编译主程序,所以 ABI 的稳定性的维持是高于 API 的

    如果把新增 new_field 放在 old_field 之后了,发现程序运行是没有问题的。mylib 通过后者的方式去升级 v2 版本,即使新增了字段,ABI 依然是稳定的。

    // mylib.h
    
    typedef struct {
        int old_field;
        int new_field;
    } mylib_mystruct;
    

    扩展阅读

    下面所示的使用 Node-API 开发的 c++ 插件的代码例子, 对于我来说就比较好奇 napi_value 的定义

    // demo
    
    napi_status status;
    napi_value object, string;
    status = napi_create_object(env, &object);
    if (status != napi_ok) {
      napi_throw_error(env, ...);
      return;
    }
    
    status = napi_create_string_utf8(env, "bar", NAPI_AUTO_LENGTH, &string);
    if (status != napi_ok) {
      napi_throw_error(env, ...);
      return;
    }
    
    status = napi_set_named_property(env, object, "foo", string);
    if (status != napi_ok) {
      napi_throw_error(env, ...);
      return;
    }
    

    最后我们在 js_native_api_types.h 文件找到了 napi_value 的定义。napi_value 是 struct napi_value__ 类型的指针,其实 napi_value__ 是未定义的。从源码中的注释可知, 编译时 undefined structs 会比 void* 更加安全。

    // src/js_native_api_types.h
    
    // JSVM API types are all opaque pointers for ABI stability
    // typedef undefined structs instead of void* for compile time type safety
    typedef struct napi_value__* napi_value;
    

    实测上面的 napi_value__ 是 undefined 编译是会通过的,实际使用的时候强制类型转换为目标类型即可。

    参考

    13 条回复    2021-12-21 02:20:02 +08:00
    12101111
        1
    12101111  
       2021-12-20 01:11:47 +08:00
    实践上并不存在真正稳定的 ABI ,对代码的任意修改,对编译器或编译器参数的修改都有可能导致代码无法执行
    只有细致的对比两个编译后的二进制才能得知是否发生了 ABI 变动
    https://cor3ntin.github.io/posts/abi/
    xupefei
        2
    xupefei  
       2021-12-20 02:01:42 +08:00 via iPhone
    谈 ABI 不能靠代码,得把反汇编放出来。讲一下导出表和 call 指令一下就懂了,不用费这么多口舌。
    jones2000
        3
    jones2000  
       2021-12-20 08:01:07 +08:00
    直接用 json 字符串作为参数不就可以了。
    whi147
        4
    whi147  
       2021-12-20 08:58:21 +08:00 via iPhone
    符号文件链接不上
    2i2Re2PLMaDnghL
        5
    2i2Re2PLMaDnghL  
       2021-12-20 09:20:31 +08:00
    @jones2000 这个就叫『序列化』,正是一种通用的解决各种不兼容的方法。甚至 API 不兼容也没问题,golang 写的服务器你也可以拿 Python 去请求。序列化也不仅仅是 json ,也有 xml ,x-www-form-urlencoded ,msgpack ,甚至古早的 s expr 。
    不过,具体地说,它也依赖于一致的『字符序列』接口。大部分与 CJK 打交道的程序员应该都遇到过编码不一致的问题(手持两把锟斤铐,嘴上直呼烫烫烫)。也有字符串长度如何表示(臭名昭著的所谓「粘包」)、编码大小端等问题。反序列化过程中造成的漏洞也不少。
    KaynW
        6
    KaynW  
       2021-12-20 13:07:44 +08:00
    放个 arcane 的图干嘛
    jones2000
        7
    jones2000  
       2021-12-20 13:20:18 +08:00
    @2i2Re2PLMaDnghL 编码或粘包问题, 只要是用到了字符串作为变量都会有, 就算你是用结构体里面放一个字符串指针也一样。漏洞的问题,是个程序都会有漏洞,跟用什么接口方式无关。
    如果是方便开发就用 json ,特别是跨语言,跨平台,空间和运算成本大一点,毕竟是要序列化的。
    kaiduo
        8
    kaiduo  
    OP
       2021-12-20 23:42:59 +08:00
    @KaynW 文章的精髓所在 ~
    kaiduo
        9
    kaiduo  
    OP
       2021-12-20 23:43:58 +08:00
    @12101111 Node-api 说是会维持稳定的 ABI ,还是实践比较少
    kaiduo
        10
    kaiduo  
    OP
       2021-12-20 23:46:52 +08:00
    @jones2000 所以说 Js 开发的同学就比较陌生 ABI 这个概念,清一色的 API
    kaiduo
        11
    kaiduo  
    OP
       2021-12-20 23:47:32 +08:00
    @jones2000 所以说 Js 开发的同学就比较陌生 ABI 这个概念,遇见的都是 API
    kaiduo
        12
    kaiduo  
    OP
       2021-12-20 23:50:54 +08:00
    @xupefei 学习了,看来我这里只是很浅显的理解
    ungrown
        13
    ungrown  
       2021-12-21 02:20:02 +08:00
    @2i2Re2PLMaDnghL #5
    UTF-8 默秒全
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5737 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 03:05 · PVG 11:05 · LAX 19:05 · JFK 22:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.