【问题标题】:Dart ffi - Reading emojis from file with CDart ffi - 使用 C 从文件中读取表情符号
【发布时间】:2023-03-15 06:55:02
【问题描述】:

我正在使用 UTF-8 编码保存一个文件,其中包含一些信息,包括来自 Dart 端的按钮的名称以及以下代码:

file.writeAsString([
          name.length.toString(),
          name + Constants.nativeFileDelimeter,
          ids.length.toString(),
          ids.join(" "),
        ].join(" "));

// Constants.nativeFileDelimeter is "|", it is used so that user can enter a name with whitespaces

我用 C 读取同一个文件,并使用 FFI 在 C 和 Dart 之间传递数据。

        FILE *file;

        file = fopen(filePath, "r");

        if (!file) {
            LOGE("Could not open %s!", filePath);
            *operationState = MediaLoadState::FAILED_TO_LOAD;
            goto cleanup;
        }

        int32_t size;

        if(fscanf(file, "%d ", &size) != 1){
            LOGE("fscanf can not assign variables %s!", filePath);
            *operationState = MediaLoadState::FAILED_TO_LOAD;
            goto cleanup;
        }

        // +1 because C strings ends with /0
        *namePtr = new char[size + 1];

        if (size != 0){
            if(fscanf(file, "%[^|]|", *namePtr) != 1){
                LOGE("fscanf can not assign variables %s!", filePath);
                *operationState = MediaLoadState::FAILED_TO_LOAD;
                goto cleanup;
            }
        }

读取 C 保存的指针的 Dart 代码:

  Pointer<Pointer<Utf8>> _namePtrPtr;
  String get name => Utf8.fromUtf8(_namePtrPtr.value);

我的问题是这段代码可以处理 0 个错误,它甚至可以处理日文、俄文字符,但是当引入表情符号时,事情变得很奇怪。当我保存一个包含表情符号的文件并尝试使用 C 和 Dart ffi 读取它时,我得到了 Utf8.fromUtf8 抛出的奇怪错误。例如:

Unfinished UTF-8 octet sequence (at offset 48)

有时它可以工作并呈现表情符号的代码相同,但后来应用程序随机崩溃。每次读取文件时抛出的异常似乎都不同,有时我没有异常,但后来崩溃了!这是不一致的。我不知道我做错了什么,我希望它可以与表情符号一起使用。谁能帮我解决这个问题?

【问题讨论】:

  • *operationState = MediaLoadState::FAILED_TO_LOAD; C 中没有 :: 运算符
  • 主要代码是c++,但我使用C apis来读取文件。这就是枚举的原因

标签: c dart dart-ffi


【解决方案1】:

在 Dart 中,String.length 返回 UTF-16 代码单元的数量。要在 C 中读取 UTF-8,您需要知道 UTF-8 字节数。因此,在 Dart 代码中输出utf8.encode(name).length 而不是name.length(并导入dart:convert)。异常和崩溃可能是由于尺寸太短引发的未定义行为。

以文本格式单独存储数据大小很容易出错。更好地使用这种方法:

看来您正在使用 C++。在那里,您可以将文件打开为std::ifstream,创建一个std::string name; 并使用std::getline(file, name, '|'); 读取具有动态大小的名称。您可以使用 *namePtr = strdup(name.c_str())std::string 创建一个纯 C 字符串。

【讨论】:

  • 感谢您的精彩回答。我使用了 C 文件操作,因为正如您在答案中看到的那样,您的代码创建了一个额外的名称副本以将其存储在指针中,我不想复制字符串来加快速度
  • 刚刚用utf8.encode(name).length测试过,目前没有问题,看起来很稳定,所有的老问题都没有了,非常感谢,这困扰了我两天。由于文件读取默认为 UTF8 我虽然长度是 utf8 长度。傻我:)
  • 一般来说,如果可能的话,人们应该更喜欢稳健性或至少碰撞安全性而不是速度。如果您需要速度,通常需要进行基准测试。额外的 UTF-8 编码步骤,Dart 中的 int 到字符串转换(长度),以及 C 中的字符串到 int 转换可能比 C 中的额外字符串副本更昂贵。此外,还有其他加快速度的方法,例如,您可以只传递 c_str 指针而不传递 strdup,如果您只将 std::string 保留在 C++ 中,直到对 Dart 的 Utf8.fromUtf8 调用完成。
  • 明智的话,但我认为这个约定是安全的,我错了吗?我的意思是在这种情况下它肯定会崩溃,但我的长度是错误的。这种读取文件的方式可能会出现什么问题,即。首先大小,然后是字符。我真的看不出有什么问题。你能帮我看看吗?只要正确写入输入文件,我看不出 C 文件 io 失败的任何原因。我愿意接受任何建议!
  • 程序通常应该是安全的(没有未定义的行为),即使在文件内容等无效外部输入的情况下也是如此。否则,它可能被视为一个漏洞。正如我所解释的,当前的方法可能并不比仅使用分隔符更快。或者如果您真的想按大小读取(并省略冗余分隔符),请使用 fgets 而不是 fscanf。这也将消除未定义的行为。但目前的方法非常容易出错,甚至不是最快的。
猜你喜欢
  • 1970-01-01
  • 2020-10-02
  • 2020-07-07
  • 2017-11-12
  • 1970-01-01
  • 2015-02-22
  • 2010-10-10
  • 1970-01-01
  • 2020-04-21
相关资源
最近更新 更多