简短版:
如何将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)
设置断点以检查函数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
然后释放内存的正确方法是什么?
答案 0 :(得分:4)
您可以编写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()];
}
Swift代码:
print(getStringFromLibrary())
[NSString stringWithUTF8String:]
将缓冲区的内容复制到一些内部存储中,ARC管理释放它。您无需定义free_something()
。
答案 1 :(得分: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;
}
}
这种方法很简单,但确实会产生额外副本的成本。根据{{1}}的大小和str
的调用次数,这可能相关也可能不相关。它还要求用户处理资源管理,因此它不是特别安全的。
2)向getStringFromLibrary
对象返回一个不透明的“句柄”,让swift访问它的底层缓冲区并通过不同的函数释放它:
std::string
现在你可以做一些这样的事情:
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);
}
}
这种方法消除了额外的副本,但它仍然不是异常安全的,因为调用者必须处理资源管理。
可能有一种方法可以将Objective-C ++混合到解决方案中以避免资源管理问题,但遗憾的是我对Swift或Objective-C ++并不熟悉,无法告诉您该解决方案的样子。
答案 2 :(得分: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());
}
Swift代码:
let c_string: UnsafeMutablePointer<Int8>! = getStringFromLibrary()
let myString = String(cString: c_string)
freeString(c_string)
Swift.print(myString)
答案 3 :(得分:1)
我定义了一个函数(如果愿意,可以是静态的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()))