将 std::string 转换为 Swift String

问题描述 投票:0回答:5

简短版本:

如何将

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

长版:

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

一个桥接头,它允许Swift调用C函数(据我所知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)

放置断点来检查函数内部

s
的值
getStringFromLibrary()
我可以看到字符串的内容,因此库中的函数被正确调用。

无论如何,.swift 文件会打印一些奇怪的符号,而不是原始字符串。 将

getStringFromLibrary()
更改为以下

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

我的结果是 .swift 代码打印真实字符串的前缀。这让我想到了内存的问题:可能当

getStringFromLibrary()
退出时,
std::string
返回的
get_string_from_library()
对象被销毁了,所以
.c_str()
返回的指针指向的内存不再可靠了,原因是我从
Swift.print()
得到了错误的输出。

从 .swift 文件访问

std::string
然后释放其内存的正确方法是什么?

c++ swift static-libraries bridge
5个回答
5
投票

您可以编写 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()];
}

快速代码:

print(getStringFromLibrary())

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


4
投票

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++ 不够熟悉,无法告诉您该解决方案是什么样子。


2
投票

我定义了一个函数(如果您愿意,它可以是静态

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()))

1
投票

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

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

桥接头:

//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)

0
投票

std.string
String
的转换是通过
String(describing: YOR_VARIABLE)
完成的。并且
std::string.c_str()
不存在于 Swift CXX 互操作桥中! -- 足够疯狂,典型的苹果

    let res: std.string = steam.getAchievements()
    return String(describing: res)
© www.soinside.com 2019 - 2024. All rights reserved.