Swift + JavaScriptCore
JavaScript 核心 (JSC) 是为 Safari Web 浏览器提供支持的 JavaScript 引擎。
¥JavaScriptCore (JSC) is the JavaScript engine powering the Safari web browser.
SheetJS 是一个用于从电子表格读取和写入数据的 JavaScript 库。
¥SheetJS is a JavaScript library for reading and writing data from spreadsheets.
该演示使用 JSC 和 SheetJS 来读取和写入电子表格。我们将探讨如何在 JSC 上下文中加载 SheetJS,并处理来自 C++ 和 Swift 程序的电子表格和结构化数据。
¥This demo uses JSC and SheetJS to read and write spreadsheets. We'll explore how to load SheetJS in a JSC context and process spreadsheets and structured data from C++ and Swift programs.
本 demo 在以下环境下进行了测试:
¥This demo was tested in the following environments:
MacOS 上的 Swift 支持 JavaScriptCore,无需额外依赖。
¥Swift on MacOS supports JavaScriptCore without additional dependencies.
架构 | 迅速 | 日期 |
---|---|---|
darwin-x64 | 6.1 | 2025-04-21 |
darwin-arm | 6.1 | 2025-04-21 |
JavaScriptCore 可以从源代码构建并链接到 C/C++ 程序中。
¥JavaScriptCore can be built from source and linked in C / C++ programs.
架构 | 版本 | 日期 |
---|---|---|
darwin-x64 | 7620.2.4.111.7 | 2025-04-21 |
darwin-arm | 7620.2.4.111.7 | 2025-04-21 |
linux-x64 | 7620.2.4.111.7 | 2025-04-21 |
linux-arm | 7620.2.4.111.7 | 2025-04-21 |
Swift 编译器可以链接到从 JavaScriptCore 源构建的库。
¥Swift compiler can link against libraries built from the JavaScriptCore source.
架构 | 版本 | 日期 |
---|---|---|
linux-x64 | 7620.2.4.111.7 | 2025-04-21 |
linux-arm | 7620.2.4.111.7 | 2025-04-21 |
集成详情
¥Integration Details
SheetJS 独立脚本 可以在 JSC 上下文中进行解析和评估。
¥The SheetJS Standalone scripts can be parsed and evaluated in a JSC context.
- Swift
- C++
可以使用 String.Encoding.isoLatin1
来回传递二进制字符串。
¥Binary strings can be passed back and forth using String.Encoding.isoLatin1
.
SheetJS read
方法 [^1],具有 "binary"
类型,可以解析二进制字符串。
¥The SheetJS read
method[^1], with the "binary"
type, can parse binary strings.
write
方法 [^2],具有 "binary"
类型,可以创建二进制字符串。
¥The write
method[^2], with the "binary"
type, can create binary strings.
JSC 提供了一些处理 Uint8Array
对象的特殊方法:
¥JSC provides a few special methods for working with Uint8Array
objects:
-
JSObjectMakeTypedArrayWithBytesNoCopy
[^3] 根据指针和大小创建类型化数组。它直接使用内存地址(不复制)。¥
JSObjectMakeTypedArrayWithBytesNoCopy
[^3] creates a typed array from a pointer and size. It uses the memory address directly (no copy). -
JSObjectGetTypedArrayLength
[^4] 和JSObjectGetTypedArrayBytesPtr
[^5] 可以从 JSC 引擎中的Uint8Array
返回指针和大小对。¥
JSObjectGetTypedArrayLength
[^4] andJSObjectGetTypedArrayBytesPtr
[^5] can return a pointer and size pair from aUint8Array
in the JSC engine.
SheetJS read
方法 [^6] 可以处理 Uint8Array
个对象。
¥The SheetJS read
method[^6] can process Uint8Array
objects.
write
方法 [^7](具有 "buffer"
类型)创建 Uint8Array
数据。
¥The write
method[^7], with the "buffer"
type, creates Uint8Array
data.
初始化 JSC
¥Initialize JSC
- Swift
- C++
可以使用 JSContext
函数创建 JSC 上下文:
¥A JSC context can be created with the JSContext
function:
var context: JSContext!
do {
context = JSContext();
context.exceptionHandler = { _, X in if let e = X { print(e.toString()!); }; };
} catch { print(error.localizedDescription); }
可以使用 JSGlobalContextCreate
函数创建 JSC 上下文:
¥A JSC context can be created with the JSGlobalContextCreate
function:
JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
JSC 不提供 global
变量。它可以在一行中创建:
¥JSC does not provide a global
variable. It can be created in one line:
- Swift
- C++
do {
context.evaluateScript("var global = (function(){ return this; }).call(null);");
} catch { print(error.localizedDescription); }
#define DOIT(cmd) \
JSStringRef script = JSStringCreateWithUTF8CString(cmd); \
JSValueRef result = JSEvaluateScript(ctx, script, NULL, NULL, 0, NULL); \
JSStringRelease(script);
{ DOIT("var global = (function(){ return this; }).call(null);") }
加载 SheetJS 脚本
¥Load SheetJS Scripts
可以通过从文件系统读取脚本并在 JSC 上下文中进行评估来加载主库:
¥The main library can be loaded by reading the scripts from the file system and evaluating in the JSC context:
- Swift
- C++
let src = try String(contentsOfFile: "xlsx.full.min.js");
context.evaluateScript(src);
/* load library */
{
size_t sz = 0; char *file = read_file("xlsx.full.min.js", &sz);
DOIT(file);
}
要确认库已加载,可以检查 XLSX.version
:
¥To confirm the library is loaded, XLSX.version
can be inspected:
- Swift
- C++
let XLSX: JSValue! = context.objectForKeyedSubscript("XLSX");
if let ver = XLSX.objectForKeyedSubscript("version") { print(ver.toString()); }
#define JS_STR_TO_C \
JSStringRef str = JSValueToStringCopy(ctx, result, NULL); \
size_t sz = JSStringGetMaximumUTF8CStringSize(str); \
char *buf = (char *)malloc(sz); \
JSStringGetUTF8CString(str, buf, sz); \
/* get version string */
{
DOIT("XLSX.version")
JS_STR_TO_C
printf("SheetJS library version %s\n", buf);
}
读取文件
¥Reading Files
- Swift
- C++
String(contentsOf:encoding:)
从路径读取并返回编码字符串:
¥String(contentsOf:encoding:)
reads from a path and returns an encoded string:
/* read sheetjs.xls as Base64 string */
let file_path = shared_dir.appendingPathComponent("sheetjs.xls");
let data: String! = try String(contentsOf: file_path, encoding: String.Encoding.isoLatin1);
该字符串可以加载到 JS 引擎中并进行处理:
¥This string can be loaded into the JS engine and processed:
/* load data in JSC */
context.setObject(data, forKeyedSubscript: "payload" as (NSCopying & NSObjectProtocol));
/* `payload` (the "forKeyedSubscript" parameter) is a binary string */
context.evaluateScript("var wb = XLSX.read(payload, {type:'binary'});");
Direct Read (click to show)
Uint8Array
data can be passed directly, skipping string encoding and decoding:
let url = URL(fileURLWithPath: file)
var data: Data! = try Data(contentsOf: url);
let count = data.count;
/* Note: the operations must be performed in the closure! */
let wb: JSValue! = data.withUnsafeMutableBytes { (dataPtr: UnsafeMutableRawBufferPointer) in
let ab: JSValue! = JSValue(jsValueRef: JSObjectMakeTypedArrayWithBytesNoCopy(context.jsGlobalContextRef, kJSTypedArrayTypeUint8Array, dataPtr.baseAddress, count, nil, nil, nil), in: context)
/* prepare options argument */
context.evaluateScript(String(format: "var readopts = {type:'array', dense:true}"));
let readopts: JSValue = context.objectForKeyedSubscript("readopts");
/* call XLSX.read */
let XLSX: JSValue! = context.objectForKeyedSubscript("XLSX");
let readfunc: JSValue = XLSX.objectForKeyedSubscript("read");
return readfunc.call(withArguments: [ab, readopts]);
}
For broad compatibility with Swift versions, the demo uses the String method.
将数据加载到 JSC 引擎有几个步骤:
¥There are a few steps for loading data into the JSC engine:
A) 必须将文件读入 char*
缓冲区(使用标准 C 方法)
¥A) The file must be read into a char*
buffer (using standard C methods)
size_t sz; char *file = read_file(argv[1], &sz);
B) 类型化数组必须使用 JSObjectMakeTypedArrayWithBytesNoCopy
创建
¥B) The typed array must be created with JSObjectMakeTypedArrayWithBytesNoCopy
JSValueRef u8 = JSObjectMakeTypedArrayWithBytesNoCopy(ctx, kJSTypedArrayTypeUint8Array, file, sz, NULL, NULL, NULL);
C) 类型化数组必须绑定到全局范围内的变量:
¥C) The typed array must be bound to a variable in the global scope:
/* assign to `global.buf` */
JSObjectRef global = JSContextGetGlobalObject(ctx);
JSStringRef key = JSStringCreateWithUTF8CString("buf");
JSObjectSetProperty(ctx, global, key, u8, 0, NULL);
JSStringRelease(key);
写入文件
¥Writing Files
- Swift
- C++
在 JavaScriptCore 中写入二进制字符串时,结果应存储在变量中并在 Swift 中转换为字符串:
¥When writing to binary string in JavaScriptCore, the result should be stored in a variable and converted to string in Swift:
/* write to binary string */
context.evaluateScript("var out = XLSX.write(wb, {type:'binary', bookType:'xlsx'})");
/* `out` from the script is a binary string that can be stringified in Swift */
let outvalue: JSValue! = context.objectForKeyedSubscript("out");
var out: String! = outvalue.toString();
String#write(to:atomically:encoding)
将字符串写入指定路径:
¥String#write(to:atomically:encoding)
writes the string to the specified path:
/* write to sheetjsw.xlsx */
let out_path = shared_dir.appendingPathComponent("sheetjsw.xlsx");
try? out.write(to: out_path, atomically: false, encoding: String.Encoding.isoLatin1);
类型为 "buffer"
的 SheetJS write
方法将返回一个 Uint8Array
对象:
¥The SheetJS write
method with type "buffer"
will return a Uint8Array
object:
DOIT("XLSX.write(wb, {type:'buffer', bookType:'xlsb'});")
JSObjectRef u8 = JSValueToObject(ctx, result, NULL);
给定结果对象,JSObjectGetTypedArrayLength
将长度拉入 C:
¥Given the result object, JSObjectGetTypedArrayLength
pulls the length into C:
size_t sz = JSObjectGetTypedArrayLength(ctx, u8, NULL);
JSObjectGetTypedArrayBytesPtr
返回指向结果缓冲区的指针:
¥JSObjectGetTypedArrayBytesPtr
returns a pointer to the result buffer:
char *buf = (char *)JSObjectGetTypedArrayBytesPtr(ctx, u8, NULL);
可以使用标准 C 方法将数据写入文件:
¥The data can be written to file using standard C methods:
FILE *f = fopen("sheetjsw.xlsb", "wb"); fwrite(buf, 1, sz, f); fclose(f);
完整示例
¥Complete Example
迅速
¥Swift
该演示包含一个示例 SheetJSCore
Wrapper 类来简化操作。
¥The demo includes a sample SheetJSCore
Wrapper class to simplify operations.
-
通过在终端中运行以下命令确保已安装 Swift:
¥Ensure Swift is installed by running the following command in the terminal:
swiftc --version
如果未找到该命令,请安装 Xcode。
¥If the command is not found, install Xcode.
-
为项目创建一个文件夹:
¥Create a folder for the project:
mkdir sheetjswift
cd sheetjswift
-
下载 SheetJS Standalone 脚本和测试文件。将这两个文件保存在项目目录中:
¥Download the SheetJS Standalone script and the test file. Save both files in the project directory:
curl -LO https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js
curl -LO https://xlsx.nodejs.cn/pres.numbers
-
下载演示的 Swift 脚本
¥Download the Swift scripts for the demo
-
¥
SheetJSCore.swift
Wrapper library -
main.swift
命令行脚本¥
main.swift
Command-line script
curl -LO https://xlsx.nodejs.cn/swift/SheetJSCore.swift
curl -LO https://xlsx.nodejs.cn/swift/main.swift
-
构建
SheetJSwift
程序:¥Build the
SheetJSwift
program:
swiftc SheetJSCore.swift main.swift -o SheetJSwift
-
测试程序:
¥Test the program:
./SheetJSwift pres.numbers
如果成功,CSV 将打印到控制台。该脚本还尝试写入 SheetJSwift.xlsx
。该文件可以通过在 Excel/Numbers 中打开来验证。
¥If successful, a CSV will be printed to console. The script also tries to write
to SheetJSwift.xlsx
. That file can be verified by opening in Excel / Numbers.
C++
此演示的旧版本建议下载 WebKit 发行存档。
¥Older versions of this demo recommended downloading the WebKit release archives.
Microsoft 已禁用所有 WebKit 存档下载!
¥Microsoft disabled all WebKit archive downloads!
上次测试演示时,https://codeload.github.com/WebKit/WebKit/zip/refs/tags/WebKit-7620.2.4.111.7 返回 HTTP 422 Archive creation is blocked
。
¥https://codeload.github.com/WebKit/WebKit/zip/refs/tags/WebKit-7620.2.4.111.7 ,
when the demo was last tested, returned HTTP 422 Archive creation is blocked
.
更新后的说明现在克隆了代码库。获取和存储代码需要额外的 20GB 存储空间和 11GB 带宽。
¥The updated instructions now clone the repository. An additional 20GB of storage space and 11GB of bandwidth is required to fetch and store the code.
-
安装依赖
¥Install dependencies
Installation Notes (click to show)
The build requires CMake and Ruby.
On macOS, dependencies should be installed with brew
:
brew install cmake ruby
On the Steam Deck, dependencies should be installed with pacman
:
sudo pacman -Syu base-devel cmake ruby icu glibc linux-api-headers
On Debian and Ubuntu, dependencies should be installed with apt
:
sudo apt-get install build-essential cmake ruby
-
创建项目文件夹:
¥Create a project folder:
mkdir sheetjs-jsc
cd sheetjs-jsc
-
克隆 WebKit 代码库并切换到
WebKit-7620.2.4.111.7
标签:¥Clone the WebKit repository and switch to the
WebKit-7620.2.4.111.7
tag:
git clone https://github.com/WebKit/WebKit.git WebKit
cd WebKit
git checkout WebKit-7620.2.4.111.7
cd ..
-
构建 JavaScriptCore:
¥Build JavaScriptCore:
- MacOS
- Linux
cd WebKit
env CFLAGS="-Wno-error=all -Wno-deprecated-declarations" CXXFLAGS="-Wno-error=all -Wno-deprecated-declarations" LDFLAGS="-framework Foundation" Tools/Scripts/build-webkit --jsc-only --cmakeargs="-Wno-error=all -DENABLE_STATIC_JSC=ON -DCMAKE_C_FLAGS=\"-Wno-error=all -Wno-deprecated-declarations\" -DCMAKE_CXX_FLAGS=\"-Wno-error=all -Wno-deprecated-declarations\"" --make-args="-Wno-error=all -Wno-deprecated-declarations"
cd ..
在 ARM64 macOS 上的某些测试运行中,JIT 引发运行时错误,而 WebAssembly 引发编译时错误。应禁用 WebAssembly 和 JIT:
¥In some test runs on ARM64 macOS, JIT elicited runtime errors and WebAssembly elicited compile-time errors. WebAssembly and JIT should be disabled:
cd WebKit
env CFLAGS="-Wno-error=all -Wno-deprecated-declarations" CXXFLAGS="-Wno-error=all -Wno-deprecated-declarations" LDFLAGS="-framework Foundation" Tools/Scripts/build-webkit --jsc-only --cmakeargs="-Wno-error=all -DENABLE_STATIC_JSC=ON -DCMAKE_C_FLAGS=\"-Wno-error=all -Wno-deprecated-declarations\" -DCMAKE_CXX_FLAGS=\"-Wno-error=all -Wno-deprecated-declarations\"" --make-args="-Wno-error=all -Wno-deprecated-declarations" --no-jit --no-webassembly
cd ..
在一些测试运行中,构建失败并显示错误消息:
¥In some test runs, the build failed with the error message
Source/WTF/wtf/text/ASCIILiteral.h:65:34: error: use of undeclared identifier 'NSString'
WTF_EXPORT_PRIVATE RetainPtr<NSString> createNSString() const;
^
1 error generated.
必须修补引用的头文件以声明 NSString
:
¥The referenced header file must be patched to declare NSString
:
#include <wtf/text/SuperFastHash.h>
#ifdef __OBJC__
@class NSString;
#endif
namespace WTF {
在一些测试运行中,构建失败并显示错误消息:
¥In some test runs, the build failed with the error message
Source/JavaScriptCore/runtime/JSCBytecodeCacheVersion.cpp:37:10: fatal error: 'wtf/spi/darwin/dyldSPI.h' file not found
#include <wtf/spi/darwin/dyldSPI.h>
^~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
#include
应更改为相对指令:
¥The #include
should be changed to a relative directive:
#include <wtf/NeverDestroyed.h>
#include "../../WTF/wtf/spi/darwin/dyldSPI.h"
#endif
cd WebKit
env CFLAGS="-Wno-error=all -Wno-error=volatile-register-var -Wno-dangling-reference" CXXFLAGS="-Wno-error=all -Wno-error=volatile-register-var -Wno-dangling-reference" Tools/Scripts/build-webkit --jsc-only --cmakeargs="-Wno-error=all -Wno-error=volatile-register-var -DENABLE_STATIC_JSC=ON -DUSE_THIN_ARCHIVES=OFF -DCMAKE_C_FLAGS=\"-Wno-error=all -Wno-error=volatile-register-var -Wno-dangling-reference\" -DCMAKE_CXX_FLAGS=\"-Wno-error=all -Wno-error=volatile-register-var \"" --make-args="-j1 -Wno-error=all -Wno-error=volatile-register-var " -j1
cd ..
上次在 Steam Deck 上测试时,构建运行了 24 分钟!
¥When this was last tested on the Steam Deck, the build ran for 24 minutes!
在 AArch64 Linux 上的某些测试运行中,出现悬空指针错误:
¥In some test runs on AArch64 Linux, there was a dangling pointer error:
WebKitBuild/JSCOnly/Release/WTF/Headers/wtf/SentinelLinkedList.h:61:55: error: storing the address of local variable ‘toBeRemoved’ in ‘*MEM[(struct BasicRawSentinelNode * const &)this_4(D) + 96].WTF::BasicRawSentinelNode<JSC::CallLinkInfoBase>::m_next’ [-Werror=dangling-pointer=] 61 | void setNext(BasicRawSentinelNode* next) { m_next = next; } | ~~~~~~~^~~~~~
可以使用定义周围的预处理器指令来抑制错误:
¥The error can be suppressed with preprocessor directives around the definition:
BasicRawSentinelNode() = default;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-pointer"
void setPrev(BasicRawSentinelNode* prev) { m_prev = prev; }
void setNext(BasicRawSentinelNode* next) { m_next = next; }
#pragma GCC diagnostic pop
T* prev() const { return static_cast<T*>(PtrTraits::unwrap(m_prev)); }
修补标题后,必须在没有 WebAssembly 或 JIT 支持的情况下构建 JSC:
¥After patching the header, JSC must be built without WebAssembly or JIT support:
cd WebKit
env CFLAGS="-Wno-error=all -Wno-error=volatile-register-var -Wno-dangling-reference" CXXFLAGS="-Wno-error=all -Wno-error=volatile-register-var -Wno-dangling-reference" Tools/Scripts/build-webkit --jsc-only --cmakeargs="-Wno-error=all -Wno-error=volatile-register-var -DENABLE_STATIC_JSC=ON -DUSE_THIN_ARCHIVES=OFF -DCMAKE_C_FLAGS=\"-Wno-error=all -Wno-error=volatile-register-var -Wno-dangling-reference\" -DCMAKE_CXX_FLAGS=\"-Wno-error=all -Wno-error=volatile-register-var \"" --make-args="-j1 -Wno-error=all -Wno-error=volatile-register-var " -j1 --no-jit --no-webassembly
cd ..
在某些测试运行中,出现注册错误:
¥In some test runs, there was a register error:
WebKit/Source/JavaScriptCore/heap/MarkedBlock.cpp: In member function ‘void JSC::MarkedBlock::dumpInfoAndCrashForInvalidHandle(WTF::AbstractLocker&, JSC::HeapCell*)’:
WebKit/Source/JavaScriptCore/heap/MarkedBlock.cpp:589:32: error: address of explicit register variable ‘savedActualVM’ requested
589 | VMInspector::forEachVM([&](VM& vm) {
| ^~~~~~~~~~~~~
590 | if (blockVM == &vm) {
| ~~~~~~~~~~~~~~~~~~~~~
591 | isValidBlockVM = true;
| ~~~~~~~~~~~~~~~~~~~~~~
592 | SAVE_TO_REG(savedActualVM, &vm);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
593 | SAVE_TO_REG(savedBitfield, 8);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
594 | LOG_INVALID_HANDLE_DETAILS("block VM %p is valid\n", &vm);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
595 | return IterationStatus::Done;
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
596 | }
| ~
597 | return IterationStatus::Continue;
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
598 | });
| ~
在上游修复程序正式发布之前,解决方法是在 MarkedBlock.cpp
中明确禁用 SAVE_TO_REG
宏:
¥Until there is a proper upstream fix, the workaround is to explicitly no-op the
SAVE_TO_REG
macro in MarkedBlock.cpp
:
#endif
#define SAVE_TO_REG(name, value) do { \
- name = WTF::opaque(value); \
- WTF::compilerFence(); \
} while (false)
NO_RETURN_DUE_TO_CRASH NEVER_INLINE void MarkedBlock::dumpInfoAndCrashForInvalidHandle(AbstractLocker&, HeapCell* heapCell)
-
创建指向源树中
Release
文件夹的符号链接:¥Create a symbolic link to the
Release
folder in the source tree:
ln -s WebKit/WebKitBuild/JSCOnly/Release .
-
下载
sheetjs-jsc.c
:¥Download
sheetjs-jsc.c
:
curl -LO https://xlsx.nodejs.cn/jsc/sheetjs-jsc.c
-
编译程序 x:
¥Compile the program:
- MacOS
- Linux
g++ -o sheetjs-jsc sheetjs-jsc.c -IRelease/JavaScriptCore/Headers -LRelease/lib -lbmalloc -licucore -lWTF -lJavaScriptCore -IRelease/JavaScriptCore/Headers -framework Foundation
在某些测试运行中,出现有关 macOS
版本的 ld
警告:
¥In some test runs, there were ld
warnings about macOS
versions:
ld: warning: object file (Release/lib/libWTF.a[2](ASCIICType.cpp.o)) was built for newer 'macOS' version (14.5) than being linked (14.0)
这些警告可以忽略。
¥These warnings can be ignored.
g++ -o sheetjs-jsc sheetjs-jsc.c -IRelease/JavaScriptCore/Headers -LRelease/lib -lJavaScriptCore -lWTF -lbmalloc -licui18n -licuuc -latomic -IRelease/JavaScriptCore/Headers
-
下载 SheetJS Standalone 脚本和测试文件。将这两个文件保存在项目目录中:
¥Download the SheetJS Standalone script and the test file. Save both files in the project directory:
curl -LO https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js
curl -LO https://xlsx.nodejs.cn/pres.numbers
-
运行程序:
¥Run the program:
./sheetjs-jsc pres.numbers
如果成功,CSV 将打印到控制台。该脚本还尝试写入 sheetjsw.xlsb
,该文件可以在电子表格编辑器中打开。
¥If successful, a CSV will be printed to console. The script also tries to write
to sheetjsw.xlsb
, which can be opened in a spreadsheet editor.
Swift C
对于 macOS 和 iOS 部署,强烈建议使用官方 JavaScriptCore
绑定。此演示适用于 Linux Swift 应用。
¥For macOS and iOS deployments, it is strongly encouraged to use the official
JavaScriptCore
bindings. This demo is suited for Linux Swift applications.
-
安装 Swift 工具链.[^8]
¥Install the Swift toolchain.[^8]
Installation Notes (click to show)
The linux-x64
test was run on Ubuntu 22.04 using Swift 6.1
The linux-arm
test was run on Debian 12 "bookworm" using Swift 6.1
-
遵循整个 "C" 演示。共享库将在 Swift 中使用。
¥Follow the entire "C" demo. The shared library will be used in Swift.
-
输入上一步中的
sheetjs-jsc
文件夹。¥Enter the
sheetjs-jsc
folder from the previous step. -
创建文件夹
sheetjswift
。它应该在sheetjs-jsc
文件夹中:¥Create a folder
sheetjswift
. It should be in thesheetjs-jsc
folder:
mkdir sheetjswift
cd sheetjswift
-
下载 SheetJS Standalone 脚本和测试文件。将这两个文件保存在项目目录中:
¥Download the SheetJS Standalone script and the test file. Save both files in the project directory:
curl -LO https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js
curl -LO https://xlsx.nodejs.cn/pres.numbers
-
将所有生成的标题复制到当前目录:
¥Copy all generated headers to the current directory:
find ../WebKit/WebKitBuild/JSCOnly/Release/JavaScriptCore/Headers/ -name \*.h | xargs -I '%' cp '%' .
-
编辑每个头文件并将
<JavaScriptCore/
的所有实例替换为<
。例如,JavaScript.h
包括<JavaScriptCore/JSBase.h>
:¥Edit each header file and replace all instances of
<JavaScriptCore/
with<
. For example,JavaScript.h
includes<JavaScriptCore/JSBase.h>
:
#include <JavaScriptCore/JSBase.h>
必须将其更改为 <JSBase.h>
:
¥This must be changed to <JSBase.h>
:
#include <JSBase.h>
-
打印当前工作目录。它将是
sheetjswift
的路径:¥Print the current working directory. It will be the path to
sheetjswift
:
pwd
-
创建一个名为
JavaScriptCore-Bridging-Header.h
的新标题:¥Create a new header named
JavaScriptCore-Bridging-Header.h
:
#import "/tmp/sheetjs-jsc/sheetjswift/JavaScript.h"
替换步骤 7 中工作目录的导入路径。例如,如果路径是 /home/sheetjs/sheetjs-jsc/sheetjswift/
,则导入应该是
¥Replace the import path to the working directory from step 7. For example, if
the path was /home/sheetjs/sheetjs-jsc/sheetjswift/
, the import should be
#import "/home/sheetjs/sheetjs-jsc/JavaScript.h"
-
创建默认模块映射
module.modulemap
:¥Create the default module map
module.modulemap
:
module JavaScriptCore {
header "./JavaScript.h"
link "JavaScriptCore"
}
-
¥Download
SheetJSCRaw.swift
:
curl -LO https://xlsx.nodejs.cn/swift/SheetJSCRaw.swift
-
构建
SheetJSwift
:¥Build
SheetJSwift
:
swiftc -Xcc -I$(pwd) -Xlinker -L../WebKit/WebKitBuild/JSCOnly/Release/lib/ -Xlinker -lJavaScriptCore -Xlinker -lWTF -Xlinker -lbmalloc -Xlinker -lstdc++ -Xlinker -latomic -Xlinker -licuuc -Xlinker -licui18n -import-objc-header JavaScriptCore-Bridging-Header.h SheetJSCRaw.swift -o SheetJSwift
-
运行命令:
¥Run the command:
./SheetJSwift pres.numbers
如果成功,CSV 将打印到控制台。程序还尝试写入 SheetJSwift.xlsx
,可在电子表格编辑器中打开。
¥If successful, a CSV will be printed to console. The program also tries to write
to SheetJSwift.xlsx
, which can be opened in a spreadsheet editor.
绑定
¥Bindings
在其他编程语言构建的程序中,可以直接加载平台原生 (macOS) 或已编译的库。
¥It is straightforward to load the platform-native (macOS) or compiled libraries in programs built in other programming languages.
JavaScriptCore C 接口不使用 "blingos"(类似函数的宏),因此可以在外部绑定中引用每个方法。
¥The JavaScriptCore C interface does not use "blingos" (function-like macros), so it is possible to reference each method in an external binding.
Rust
编写绑定相当机械化。此过程分为 4 个部分:
¥Writing bindings is fairly mechanical. There are 4 parts to the process:
-
链接到外部库。
¥Link to the external library.
-
生成原始 C 数据类型的 Rust 表示。
¥Generate Rust representations of the original C data types.
-
翻译函数声明。
¥Translate the function declaration.
-
编写一个封装器来在 Rust 概念和 C 概念之间进行转换。
¥Write a wrapper to convert between Rust concepts and C concepts.
例如,以下 C 代码在引擎中根据 UTF8 字符串创建一个字符串:
¥For example, the following C code creates a string within the engine from a UTF8 string:
const char *code = "'Sheet' + 'JS'";
JSStringRef script = JSStringCreateWithUTF8CString(code);
一个符合人机工程学的封装函数将接受 Rust 字符串字面量并处理不安全的数据操作:
¥An ergonomic wrapper function would take a Rust string literal and handle unsafe data operations:
let code: &str = "'Sheet' + 'JS'";
let script: JSStringRef = JSC::JSStringCreateWithUTF8CString(code);
Rust Linkage
自定义指令通常会添加到 build.rs
。cargo::rustc-link-lib
指令指示 Rust 编译器链接到外部库。
¥Custom directives are typically added to build.rs
. The cargo::rustc-link-lib
directive instructs the Rust compiler to link against an external library.
以下代码片段将指示工具链链接到 macOS 上的系统 JavaScriptCore.framework
框架:
¥The following snippet will instruct the toolchain to link against the system
JavaScriptCore.framework
framework on macOS:
#[cfg(target_os = "macos")]
fn main() {
println!("cargo::rustc-link-lib=framework=JavaScriptCore");
}
Rust 类型
¥Rust Types
JSStringRef
是一个指向不透明数据类型的指针。根据 Rustonomicon,其精神等价物是一个指向不透明结构体 [^9] 的指针:
¥JSStringRef
is a pointer to an opaque data type. The spiritual equivalent
according to the Rustonomicon is a pointer to an opaque struct[^9]:
#[repr(C)]
pub struct JSString {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
type JSStringRef = *mut JSContext;
函数声明
¥Function Declaration
JSStringCreateWithUTF8CString
函数在 C 语言中声明如下:
¥The JSStringCreateWithUTF8CString
function is declared in C as follows:
JSStringRef JSStringCreateWithUTF8CString(const char * string);
必须在 extern "C"
块中定义等效的 Rust 声明:
¥The equivalent Rust declaration must be defined in an extern "C"
block:
unsafe extern "C" {
// JSStringRef JSStringCreateWithUTF8CString(const char * string);
pub unsafe fn JSStringCreateWithUTF8CString(string: *const u8) -> JSStringRef;
}
Rust Interchange
std::ffi
模块包含许多用于在 Rust 代码和 C 库之间传递数据的辅助函数。
¥The std::ffi
module includes a number of helpers for passing data between Rust
code and C libraries.
Rust 字符串字面量通常表示为 &str
类型:
¥Rust string literals are commonly represented as &str
types:
let script: &str = "'Sheet' + 'JS'"; // 'Sheet' + 'JS'
以下不安全的华尔兹语句创建了一个兼容指针:
¥The following unsafe waltz creates a compatible pointer:
A) 将 &str
转换为字节切片
¥A) Convert the &str
to a byte slice
B) 从字节创建 std::ffi::CString
实例
¥B) Create a std::ffi::CString
instance from the bytes
C) 使用 as_ptr
方法生成指针
¥C) Use the as_ptr
method to generate a pointer
/* start with a string literal */
let script: &str = "'Sheet' + 'JS'";
/* generate CString */
let cstr: std::ffi::CString = std::ffi::CString::new(script.as_bytes()).unwrap();
/* call JSStringCreateWithUTF8CString */
let ref: JSStringRef = JSStringCreateWithUTF8CString(cstr.as_ptr() as *const u8);
该演示程序创建了一个安全的封装器,只需一行代码即可完成不安全的华尔兹:
¥The demo makes a safe wrapper to perform the unsafe waltz in one line:
pub struct JSC;
impl JSC {
pub fn JSStringCreateWithUTF8CString(str: &str) -> JSStringRef { unsafe {
JSStringCreateWithUTF8CString(std::ffi::CString::new(str.as_bytes()).unwrap().as_ptr() as *const u8)
} }
}
Rust 示例
¥Rust Example
该演示最后在以下部署中进行了测试:
¥This demo was last tested in the following deployments:
架构 | 日期 |
---|---|
darwin-x64 | 2025-03-31 |
darwin-arm | 2025-03-30 |
-
创建一个新项目:
¥Create a new project:
cargo new sheetjs-jsc
cd sheetjs-jsc
cargo run
-
将 SheetJS 独立脚本下载到项目中的
src
文件夹:¥Download the SheetJS Standalone script to the
src
folder in the project:
curl -L -o src/xlsx.full.min.js https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js
-
将测试文件下载到项目目录:
¥Download the test file to the project directory:
curl -LO https://xlsx.nodejs.cn/pres.numbers
curl -L -o src/main.rs https://xlsx.nodejs.cn/jsc/main.rs
curl -LO https://xlsx.nodejs.cn/jsc/build.rs
-
构建并运行应用:
¥Build and run the app:
cargo run pres.numbers
如果程序成功,CSV 内容将打印到控制台并创建文件 sheetjsw.xlsb
。可以使用支持 XLSB 电子表格的电子表格编辑器打开该文件。
¥If the program succeeded, the CSV contents will be printed to console and the
file sheetjsw.xlsb
will be created. That file can be opened with a spreadsheet
editor that supports XLSB spreadsheets.
[^1]: 见 read
于 "读取文件"
[^2]: 见 writeFile
于 "写入文件"
¥See writeFile
in "Writing Files"
[^3]: 请参阅 JavaScriptCore 文档中的 JSObjectMakeTypedArrayWithBytesNoCopy
。
¥See JSObjectMakeTypedArrayWithBytesNoCopy
in the JavaScriptCore documentation.
[^4]: 请参阅 JavaScriptCore 文档中的 JSObjectGetTypedArrayLength
。
¥See JSObjectGetTypedArrayLength
in the JavaScriptCore documentation.
[^5]: 请参阅 JavaScriptCore 文档中的 JSObjectGetTypedArrayBytesPtr
。
¥See JSObjectGetTypedArrayBytesPtr
in the JavaScriptCore documentation.
[^6]: 见 read
于 "读取文件"
[^7]: 见 writeFile
于 "写入文件"
¥See writeFile
in "Writing Files"
[^8]: 有关 Swift 网站中的 "安装 Swift"。
¥See "Install Swift" in the Swift website.
[^9]: 请参阅 Rustonomicon 中的 "表示不透明结构体"
¥See "Representing opaque structs" in the Rustonomicon