【问题标题】:convert an std::string to a Swift String将 std::string 转换为 Swift 字符串
【发布时间】:2026-01-09 15:50:01
【问题描述】:

短版:

如何将std::string(使用桥接从 .Swift 文件调用的 .cpp 函数返回的对象)转换为 Swift String

加长版:

我有一个用 C++ 编写的库,我必须使用 Swift 调用一些代码。我创建了一个桥,在我的 Xcode 项目中添加了两个文件:

一个桥接头,它允许 Swift 调用 C 函数(afaik Swift 不能直接调用 C++ 函数,所以它需要通过 C 函数)

//file bridgingHeader.h
const char * getLastOpenedFile();

还有一个 .cpp 文件,它可以在内部调用(当然)C++ 函数,并且可以用 extern "C" 定义 C 函数

//file wrapper.cpp    
#include <string>
#include "my_library.hpp"

extern "C" const char * getStringFromLibrary()
{
    const char * s = get_string_from_library().c_str();
    return s;
}

我可以使用 .swift 文件访问返回值

let myString = String(cString: getStringFromLibrary())
Swift.print(myString)

在函数getStringFromLibrary()中放置断点检查s的值我可以看到字符串的内容,所以库中的函数被正确调用了。

无论如何,.swift 文件会打印一些奇怪的符号而不是原始字符串。 将getStringFromLibrary() 更改为以下内容

extern "C" const char * getStringFromLibrary()
{        
    return get_string_from_library().c_str();
}

因此,.swift 代码会打印出真实字符串的前缀。这让我想到了一个内存问题:可能当getStringFromLibrary()退出时,get_string_from_library()返回的std::string对象被销毁,所以.c_str()返回的指针指向的内存不再可靠,原因我从Swift.print() 得到错误的输出。

从 .swift 文件中访问 std::string 然后释放其内存的正确方法是什么?

【问题讨论】:

  • 您的getStringFromLibrary 将挂在一个悬空指针上,因为s 指的是一个由get_string_from_library 函数分配和销毁的const char*。我不熟悉 swift,但您基本上必须堆分配 const char* 并将所有权授予调用者。
  • 也许一个解决方案是在 getStringFromLibrary() 中调用 malloc 为 char * 变量分配内存,将变量的内容复制到 Swift 字符串,然后显式调用 free()
  • @Nisba 是的,这是通常的方法。您可以考虑添加一个freeStringFromLibrary 函数,而不是直接从Swift 调用free,以便隐藏内存分配的底层方法。这会让你在内部使用new[]malloc

标签: c++ swift static-libraries bridge


【解决方案1】:

您可以编写一个 Objective-C++ 包装器来处理 C++ 代码。

桥接头:

#include "Wrapper.hpp"

包装器.hpp:

#ifndef Wrapper_hpp
#define Wrapper_hpp

#import <Foundation/Foundation.h>

#if defined(__cplusplus)
extern "C" {
#endif
    NSString * _Nonnull getStringFromLibrary();
#if defined(__cplusplus)
}
#endif

#endif /* Wrapper_hpp */

包装器.mm:

#include "Wrapper.hpp"

#include <string>
#include "my_library.hpp"

NSString * _Nonnull getStringFromLibrary() {
    return [NSString stringWithUTF8String:get_string_from_library().c_str()];
}

Swift 代码:

print(getStringFromLibrary())

[NSString stringWithUTF8String:] 将缓冲区的内容复制到一些内部存储中,ARC 管理释放它。您无需定义free_something()

【讨论】:

    【解决方案2】:

    std::string 对象拥有通过c_str 返回指针的缓冲区。这意味着getStringFromLibrary 返回一个空指针。

    有几种方法可以避免这种情况:

    1) 将缓冲区的内容复制到某个长期存在的地方,并返回一个指向该缓冲区的指针。这通常意味着通过new[]malloc 分配一些内存:

    extern "C"
    {
    
    const char* getStringFromLibrary()
    {
        std::string str = get_string_from_library();
        char* s = new char[str.size() + 1]{};
        std::copy(str.begin(), str.end(), s);
        return s;
    }
    
    void freeStringFromLibrary(char* s)
    {
        delete[] s;
    }
    
    }
    

    这种方法很简单,但确实会产生额外的副本成本。这可能相关也可能不相关,具体取决于str 的大小以及调用getStringFromLibrary 的频率。它还需要用户处理资源管理,因此不是特别安全。

    2) 将一个不透明的“句柄”返回给 std::string 对象,让 swift 访问其底层缓冲区并通过不同的函数释放它:

    extern "C"
    {
    
    void* getStringFromLibrary()
    {
        std::string* s = new std::string(get_string_from_library());
        return s;
    }
    
    const char* libraryGetCString(void* s)
    {
        return static_cast<std::string*>(s)->c_str();
    }
    
    void freeStringFromLibrary(void* s)
    {
        delete static_cast<std::string*>(s);
    }
    
    }
    

    现在您可以快速执行以下操作:

    let handle: UnsafeMutablePointer<COpaquePointer> = getStringFromLibrary()
    let myString = String(cString: libraryGetCString(handle))
    freeStringFromLibrary(handle)
    

    这种方法消除了额外的副本,但它仍然不是异常安全的,因为调用者必须处理资源管理。

    可能有一种方法可以将 Objective-C++ 混合到解决方案中以避免资源管理问题,但遗憾的是,我对 Swift 或 Objective-C++ 不够熟悉,无法告诉您该解决方案是什么样的。

    【讨论】:

    • 不是std::string* s = new std::string(get_string_from_library());中隐藏的多余副本吗?
    • 不,get_string_from_library 返回的字符串将被移动到新的string 对象中。这只需要一个指针副本,而不是复制整个字符串。
    • 用第一种方法寻找风格点:使用strdup()free()而不是new/copy/delete[]
    【解决方案3】:

    我解决了,我发布了我最终得到的解决方案。

    将来我可能会编写一个类,它可以让我以更安全的方式自动管理删除操作。我会更新答案。

    桥接头:

    //file bridgingHeader.h
    char * getStringFromLibrary();
    void freeString(char * string);
    

    包装器:

    char * std_string_to_c_string(std::string s0) {
        size_t length = s0.length() + 1;
        char * s1 = new char [length];
        std::strcpy(s1, s0.c_str());
        return s1;
    }
    
    extern "C" void freeString(char * string) {    
        delete [] string;
    }
    
    extern "C" char * getStringFromLibrary()
    {
        return std_string_to_c_string(get_string_from_library().c_str());
    }
    

    Swift 代码:

    let c_string: UnsafeMutablePointer<Int8>! = getStringFromLibrary()
    let myString = String(cString: c_string)
    freeString(c_string)
    
    Swift.print(myString)
    

    【讨论】:

      【解决方案4】:

      我定义了一个函数(如果你愿意,它可以是一个静态的String 扩展):

      func getStringAndFree(_ cString: UnsafePointer<Int8>) -> String {
          let res = String(cString: cString)
          cString.deallocate()
          return res
      }
      

      在 C++ 中,

      extern "C" char *getStringFromLibrary()
      {
          return strdup(get_string_from_library().c_str());
      }
      

      现在在 Swift 中,我可以简单地以一种即发即弃的方式使用它:

      print("String from C++ is: " + getStringAndFree(getStringFromLibrary()))
      

      【讨论】: