DartNative Memory Management: C++ Non-Object

栏目: IT技术 · 发布时间: 4年前

内容简介:如果你还不了解PS:dart_objc 已经更名为 dart_native。

dart_native 基于 Dart FFI,通过 C++ 调用 Native 的 API。这种跨多语言的 bridge 就需要考虑到内存管理的问题。上一篇文章 介绍了 Objective-C 对象类型的管理,本篇算是它的续篇,讲下对 structchar * 内存的管理。

如果你还不了解 dart_native 是什么,建议先看下我之前的两篇文章:

PS:dart_objc 已经更名为 dart_native。

问题分析

Cocoa(Touch) 中的好多 API 都用到了系统内建的 structUTF8String ( char * ) 类型,它们不像 Objective-C 对象那样只存在于堆上(Block 除外),既可以存在堆上也可以在栈上。 如果能将 structchar * 用对象的形式包一层 ,那么就可以 将堆上非对象类型的生命周期转换为对象类型,交由 ARC 来管理 。由此继续借助上一篇文章的经验和流程,自动释放存储在堆上的 structchar * 类型。

何时销毁非对象类型

首先要确定非对象类型传递的方式。这里的解决方案是全都存储于堆上,并用一个 Wrapper 对象包一层来传递。下面说说为何这么做。

非对象类型如果存储在栈上,那么当调用结束返回后就会被销毁。在跨语言异步调用时,栈上的内存也会被回收,Dart 侧无法长期持有并访问这些数据。Objective-C 大多使用 Block 的方式来实现异步调用逻辑,由于 Block 会去捕获外部变量,所以可以正常运行。

这个 PointerWrapper 类也很简单,它包了个 void *pointer 属性,在析构的时候会释放 pointer 指向的内存:

- (void)dealloc {
    free(_pointer);
}

这样就可以把一个非对象类型先 copy 到堆上,然后封装成对象类型来传递了。确保其不会过早被释放,且在同步或异步调用完成后由 ARC 自动释放。

Dart 从 C++ 获取非对象类型

这里分两种情况:

  1. Dart 创建新的 structchar *Pointer<Utf8> )。会通过 Dart FFI 的 allocate 在堆上开辟新的内存, 需要释放
  2. Dart 调用 C++ 函数或 Objective-C Block 时获取的返回值。 struct 会被拷贝到新创建的堆内存上, 需要释放char * 会自动转换成 String不需要释放
  3. C++ 调用 Dart callback 时传入的参数。 struct 会被拷贝到新创建的堆内存上, 需要释放char * 会自动转换成 String不需要释放

至于如何在 Dart 侧创建诸如 CGRect 之类的 struct ,可能又能单开一篇文章来讲了,这里不细说了。Dart 侧并不会直接从 Objective-C/C++ 侧拿到 struct 类型,而是拿到一份 malloc 并拷贝后的指针。

上面这些需要释放的 struct 均可以通过 PointerWrapper 来自动释放,也就是默认创建的是临时变量,用完会自动销毁。Dart 的 struct 都以 NativeStruct 作为基类。其中 addressOf 为指向 struct 的指针, wrapper 接管了 struct 的生命周期。

abstract class NativeStruct {
  Pointer get addressOf;

  PointerWrapper _wrapper;
  PointerWrapper get wrapper {
    if (_wrapper == null) {
      _wrapper = PointerWrapper();
    }
    Pointer<Void> result = addressOf.cast<Void>();
    _wrapper.value = result;
    return _wrapper;
  }

  NativeStruct retain() {
    wrapper.retain();
    return this;
  }

  release() => wrapper.release();
}

也就是 Dart 获取到的 struct 是个临时变量,不用就会自动销毁。如果需要长期持有,则需要手动 retainrelease 。而 Dart 获取到的 char * 则会被自动转为 String 类型,无需关心内存管理。

C++ 从 Dart 获取非对象类型

这里分两种情况:

  1. Dart 调用 C++ 函数或 Objective-C Block 时传入的参数。
  2. Objective-C 调用 Dart callback 时获取的返回值。

Dart 侧的 struct 早已由 PointerWrapper 交给 ARC 来接管生命周期, 在调用完成后自动释放 。不过需要注意的一点是,Dart 的 String 自动转换为 C++ 的 char *Pointer<Utf8> )时属于新创建 char * 需要交给 PointerWrapper 自动释放 :

dynamic storeCStringToPointer(dynamic object, Pointer<Pointer<Void>> ptr) {
  Pointer<Utf8> charPtr = Utf8.toUtf8(object);
  PointerWrapper wrapper = PointerWrapper();
  wrapper.value = charPtr.cast<Void>();
  ptr.cast<Pointer<Utf8>>().value = charPtr;
  return wrapper;
}

Dart 向 C++ 传参

  1. 由于字符串比较特殊,即便在函数调用结束后,字符串很多以常量的形式被继续使用。所以传递 char * 的时候,即便已经通过传递 PointerWrapper 来保证调用过程中不被释放,但还需要利用 NSTaggedPointerString 将其生命周期交给 Foundation 管理。
  2. 原本传递结构体现在改成了传递结构体的指针。因为跨语言调用时,使用 Dart FFI 传递单个数据最大为 64bit,可以为整型、浮点型或指针等。所以可能无法容纳下比较大的结构体,需要传递指向结构体的指针。
  3. 其余类型照常传递。
void
_fillArgsToInvocation(NSMethodSignature *signature, void **args, NSInvocation *invocation, NSUInteger offset) {
    for (NSUInteger i = offset; i < signature.numberOfArguments; i++) {
        const char *argType = [signature getArgumentTypeAtIndex:i];
        NSUInteger argsIndex = i - offset;
        if (argType[0] == '*') {
            // Copy CString to NSTaggedPointerString and transfer it's lifecycle to ARC. Orginal pointer will be freed after function returning.
            const char *temp = [NSString stringWithUTF8String:(const char *)args[argsIndex]].UTF8String;
            if (temp) {
                args[argsIndex] = (void *)temp;
            }
        }
        if (argType[0] == '{') {
            // Already put struct in pointer on Dart side.
            [invocation setArgument:args[argsIndex] atIndex:i];
        } else {
            [invocation setArgument:&args[argsIndex] atIndex:i];
        }
    }
}

Objective-C 调用 Dart callback 时获取的返回值

由于 Dart callback 所对应的 C++ Function 由 libffi 动态创建,而基于动态创建的 C++ Function 又动态创建了 Objective-C Block 和方法。所以这一切都是我们创建的,尽在掌控之中。而这个动态创建的过程又有点复杂,可以再单独开一篇文章来讲了。

Objective-C 中方法和 Block 的返回值如果是比较大的 struct ,运行在 x86 架构上时,实际上调用更底层函数时的参数列表会有变化。此时第一个参数是指向返回结构体的指针,其余参数依次后移一位。这在 Objective-C 中缩写为 stret,也就是 struct return 的意思。

如果没有触发 stret 条件,此时的策略是 Dart callback 返回非对象类型锁对应的 PointerWrapper ,然后 Objective-C 侧再从 wrapper 中取出对非对象类型,并塞入到 libffi 提供的 ret 指针里:

if (wrapper.hasStret) {
    // synchronize stret value from first argument.
    [invocation setReturnValue:*(void **)args[0]];
} else if ([wrapper.typeString hasPrefix:@"{"]) {
    DOPointerWrapper *pointerWrapper = *(DOPointerWrapper *__strong *)ret;
    memcpy(ret, pointerWrapper.pointer, invocation.methodSignature.methodReturnLength);
} else if ([wrapper.typeString hasPrefix:@"*"]) {
    DOPointerWrapper *pointerWrapper = *(DOPointerWrapper *__strong *)ret;
    const char *origCString = (const char *)pointerWrapper.pointer;
    const char *temp = [NSString stringWithUTF8String:origCString].UTF8String;
    *(const char **)ret = temp;
}

后记

非对象类型的内存管理要比对象类型复杂得多,光是把 struct 在 Dart 中转换出来就已经有些麻烦了。好在大部分问题都已经克服过去了,最终实现了一套半自动化的内存管理系统,也实现了跨语言的类型自动转换。后续可能还会对 stret 的情况进行优化,甚至对方案进行大改。

哎真是太难了,我还是继续骑着小摩托去找人马吧。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The NSHipster Fake Book (Objective-C)

The NSHipster Fake Book (Objective-C)

Mattt Thompson / NSHipster Portland, Oregon / 2014 / USD 19.00

Fake Books are an indispensable tool for jazz musicians. They contain the melody, rhythm, and chord changes for hundreds of standards, allowing a player to jump into any session cold, and "fake it" th......一起来看看 《The NSHipster Fake Book (Objective-C)》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具