将 std::string 转换为 Swift String

convert an std::string to a Swift String

本文关键字:Swift String string std 转换      更新时间:2023-10-16

简短版本:

如何转换std::string(.cpp由从 .使用桥接的 Swift 文件)到 SwiftString

长版本:

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

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

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

以及一个.cpp文件,该文件可以调用(当然)内部的C++函数,并且可以用外部"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)

放置一个断点来检查函数内部的sgetStringFromLibrary()我可以看到字符串的内容,因此正确调用了库中的函数。

无论如何,.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然后释放其内存的正确方法是什么?

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

桥接接头:

#include "Wrapper.hpp"

Wrapper.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 */

Wrapper.mm:

#include "Wrapper.hpp"
#include <string>
#include "my_library.hpp"
NSString * _Nonnull getStringFromLibrary() {
return [NSString stringWithUTF8String:get_string_from_library().c_str()];
}

迅捷代码:

print(getStringFromLibrary())

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

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);
}
}

现在在 swift 中,你可以做这样的事情:

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

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

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

我定义了一个函数(如果你愿意,它可以是一个静态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()))

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

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

桥接接头:

//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());
}

迅捷代码:

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