开头先叠甲,如果不是真的很想整活建议是用 Qt 来完成编译原理实验
我们知道 SCNU 浴帘🤴的编译原理实验 / 项目的基本要求是:
- 核心使用 C/C++ 实现
- GUI
作为热爱整活的 Frontend Developer,自然想在完成作业的同时用上一些不一样的东西,比如:
- Electron
- Flutter
- SwiftUI(macOS)
...
说干就干之后,随之而来的是探索过程中的巨多坑。所以假设你也很爱整活,那么本文也许可以帮你少踩一点在各种奇怪技术栈中硬塞 C++ 的坑。
Electron 和 Flutter 有机会再分享,本文聚焦“在 SwiftUI 中调用 C++ 函数”来讲。
MVP
在一开始,我快速捋一遍流程以及常见的坑:
- 使用 Object-C 桥接,可以使用 C++11 标准、模版语法等,无需 extern "C" 标识
- 前提:Xcode 创建一个 SwiftUI App,写一些 C++ Functions,拖进项目目录中并且通过 Add Files 添加了引用(就是在 Xcode 菜单栏中可以看见)
- 整体流程:
- 正常写一堆 C++ Functions,头文件后缀
.hpp
,源文件后缀 .cpp
;
- 新建 Object-C 包装头文件
.h
;
- 新建 Object-C 包装源文件 .mm,实现调用;
- 新建桥文件,引入 Object-C 包装头文件。
下面跟着小标题详细说明。
Object-C 包装头文件
在这里引入<Foundation/Foundation.h>
#ifndef CppWrapper_h
#define CppWrapper_h
#import <Foundation/Foundation.h>
@interface CppWrapper: NSObject
(const char *) toPostfixWrapped: (NSString
)str;
@end
*#endif /* CppWrapper_h */
Object-C 包装源文件
在这里引入C++头文件,比如这里的 CppSolver.h
#import "CppWrapper.h"
#import "CppSolver.h"
@implementation CppWrapper
(const char *)toPostfixWrapped:(NSString *)str {
return toPostfix([str UTF8String]);
}
@end
在这里我把 C++ Functions 都通过 CppSolver.h 导出了。
#ifndef CppSolver_h
#define CppSolver_h
#include "nfa.hpp"
#include "utils.hpp"
#endif /* CppSolver_h */
这是我 toPostfix() 方法的函数签名:
const char* toPostfix(string);
可以看到,入参使用 string,返回值其实也是 string,但是返回 const char *
更方便 Swift 转换,所以在 C++ 代码中只需使用 string 的 c_str()
方法获取 string 的 const char *
即可返回。
入参在 Object-C 包装源文件.mm中,是需要做些 transfer 的:把 NSString 转换成 UTF8String,上面也已经写了,这里再贴出来:
(const char *)toPostfixWrapped:(NSString *)str {
return toPostfix([str UTF8String]);
}
桥接头文件
命名规范一般是"项目名称-Bridging-Header.h",创建头文件后,设置为桥接文件,步骤如下图:
桥接文件中的内容很简单,就是 Object-C 的包装头文件(桥接文件本身也是 Object-C)
#ifndef XLEX_Bridging_Header_h
#define XLEX_Bridging_Header_h
#import "CppWrapper.h"
#endif /* XLEX_Bridging_Header_h */
在SwiftUI中调用
比如下面这个 Demo,在一个 View 中调用,由 Button 触发:
import SwiftUI
struct ContentView: View {
var body: some View {
Button("测试") {
test()
}
.frame(minWidth: 480, minHeight: 360)
.padding()
}
func test() {
let cppWrapper = CppWrapper()
guard let postfix = cppWrapper.toPostfixWrapped("(A|B)*C") else { fatalError("Uncaught Error: Fail to call C++ Function toPostfix().")
}
guard let result = String(cString: postfix, encoding: .utf8) else {
fatalError("Uncaught Error: Fail to transfer C++ string.")
}
print(result)
}
}
其中需要对C++函数返回的 const char*
进行转换,转换的方法就是通过 String 的 Constructor,传入 cString属性(看上面的第 16 行)
String(cString: postfix, encoding: .utf8)
预览视图,点击按钮,可以看到Console里有了预期的Output。
更复杂的数据结构?
- 可以使用 JSON 来序列化数据再传递,在 Swift 中反序列化数据。
- C++ 的 JSON 库可以使用 Tencent 的 RapidJSON,只需下载 Release,解压后将 include 目录中的东西拖进项目目录然后 Add Files 即可:https://github.com/Tencent/rapidjson/
- Swift 可以使用自带的 JSONEncoder