📝 docs(tsl-syntax-reference): document external and remote calls
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# TSL 外部调用与线程
|
||||
|
||||
这一篇吸收函数专题里和外部系统交互有关的部分:`external`、动态库调用、原生函数指针包装、C 回调和线程调用。
|
||||
本页说明动态库声明、原生函数指针、C 回调和线程调用。`MakeInstance`、`DeleteInstance` 是 builtin API;`external`、`name`、`KeepResident`、`cdecl`、`stdcall` 和外部参数声明是语法。
|
||||
|
||||
## 本篇职责
|
||||
|
||||
<!-- section-id: syntax-17-001 -->
|
||||
|
||||
回答“外部 DLL 声明、原生函数指针包装、C 回调和多线程调用有哪些文档明确写法”。本页只覆盖系统交互能力,普通 TSL 函数的定义与调用不在本页收口。
|
||||
回答外部动态库、函数指针、C 回调和线程调用的写法。`LoadLibraryA`、`GetProcAddress`、`dlopen`、`dlsym` 是宿主 API;`TSL_ScriptGo` 等 `TSSVRAPI.h` 名称是 C/C++ SDK API,均不进入 TSL builtin 索引。
|
||||
|
||||
## 核心规则
|
||||
|
||||
@@ -14,14 +14,112 @@
|
||||
|
||||
- 外部函数声明的文档明确形态是 `function Name(...): Type; stdcall|cdecl; external "dll" [name "symbol"];`。
|
||||
- 当 TSL 函数名和 DLL 导出名一致时,`name "symbol"` 可以省略。
|
||||
- Windows 示例默认显式写调用约定;不要把省略调用约定当成跨平台默认规则。
|
||||
- Win32 下调用约定必须与宿主函数一致;Linux 与 Win64 下 `stdcall` 和 `cdecl` 使用相同调用模式。
|
||||
- 无返回值的外部接口可声明为 `procedure Name(...); ... external ...;`。
|
||||
- 外部函数需要写回调用方变量时,在参数名前写 `var`;TSL 参数类型必须与原生 ABI 的参数宽度和含义一致。
|
||||
- `KeepResident` 写在外部库声明末尾,用于保留动态库句柄。
|
||||
- `function(...): ...; external fp;` 可以把原生函数指针重新包装成 TSL 可调用对象。
|
||||
- DLL 名写法包括字面量字符串和类常量字符串;不要把字符串拼接表达式直接当成稳定写法。
|
||||
- `makeInstance(thisFunction(Func), "cdecl", 0)` 可以把 TSL 函数包装成 C 调用约定函数指针。
|
||||
- Windows 线程示例用 `makeInstance(..., "cdecl", 1)` 生成可交给 `CreateThread` 的回调指针。
|
||||
- 外部库名和调用约定要按目标平台选择;不要把 Windows 的 `kernel32.dll` 示例直接复制到 Linux,或把 Linux 的 `.so` 示例直接复制到 Windows。
|
||||
- 本页线程示例是 Windows 专题,用到了 `kernel32.dll`。
|
||||
- 库名可使用字面量字符串或类常量字符串,不能使用字符串拼接表达式。
|
||||
- `MakeInstance(thisFunction(Func), "cdecl", 0)` 或 `MakeInstance(findFunction("Func"), "cdecl", 0)` 可以把有完整参数及返回类型声明的 TSL 函数包装成 C 调用约定函数指针。
|
||||
- `DeleteInstance(ptr)` 用于删除 `MakeInstance` 生成的指针;包装出来的 TSL 函数变量应先解除引用,再删除原始指针。
|
||||
- Windows 线程示例用 `MakeInstance(..., "cdecl", 1)` 生成可交给 `CreateThread` 的回调指针。
|
||||
- 外部库名按目标平台选择:Windows 使用 DLL,Linux 使用对应 `.so`。
|
||||
- 文档列出的基础外部类型包括 `Integer`、`String`、`Double`、`Single`、`Boolean`、`Pointer`、`PChar`、`Int64`、`Short` 和 `Byte`,未列出的类型按 `String` 处理。结构体等复合类型仍须按原生 ABI 组装内存;不能直接导入 C++ 引出类。
|
||||
|
||||
## 外部 ABI 与动态库常驻
|
||||
|
||||
<!-- section-id: syntax-17-012 -->
|
||||
|
||||
<!-- tags: KeepResident 动态库常驻 external ABI 类型映射 -->
|
||||
|
||||
`external` 是 TSL 与宿主 ABI 的边界,不是 builtin 定义。参数类型、宽度、对齐和调用约定必须与原生函数一致;结构体等复合参数须自行封装内存布局。
|
||||
|
||||
`KeepResident` 放在 `external` 库声明末尾,用于要求动态库句柄在调用后保持常驻。例如:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
代码块说明:Linux 示例,依赖 `libc.so.6`。
|
||||
|
||||
```tsl
|
||||
writeLn(GetPidResident() > 0);
|
||||
|
||||
function GetPidResident(): integer; cdecl; external "libc.so.6" name "getpid" KeepResident;
|
||||
```
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
1
|
||||
```
|
||||
|
||||
## 外部出参与 ABI 写回
|
||||
|
||||
<!-- section-id: syntax-17-013 -->
|
||||
|
||||
<!-- tags: 外部出参 external var 出参 写回调用方 glibc libc time ABI -->
|
||||
|
||||
当外部函数需要通过指针写回调用方变量时,在参数名前使用 `var`。这条规则只说明 TSL 声明形态;具体类型、宽度、对齐和调用约定仍必须与原生函数一致。
|
||||
|
||||
下面用 libc `time` 演示 `var` 外部出参:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
代码块说明:Linux x86_64 示例,依赖 `libc.so.6`;`time_t` 按该 ABI 映射为 `int64`。
|
||||
|
||||
```tsl
|
||||
stamp := 0;
|
||||
result := UnixTime(stamp);
|
||||
writeLn(result > 0);
|
||||
writeLn(stamp = result);
|
||||
|
||||
function UnixTime(var stamp: int64): int64; cdecl; external "libc.so.6" name "time";
|
||||
```
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
1
|
||||
1
|
||||
```
|
||||
|
||||
`UnixTime` 是脚本别名,`time` 是 libc 导出符号,二者都不是 TSL builtin。
|
||||
|
||||
## Linux 动态加载与函数指针生命周期
|
||||
|
||||
<!-- section-id: syntax-17-014 -->
|
||||
|
||||
<!-- tags: Linux 动态加载 glibc dlsym dlopen external 原生指针 -->
|
||||
|
||||
Linux 可用 `dlopen` / `dlsym` 取得原生函数指针,再用 `external fp` 包装。Windows 对应使用 `LoadLibraryA` / `GetProcAddress`。这些名称都是宿主 API,不是 TSL builtin。
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
代码块说明:Linux 示例,依赖 `libdl.so.2` 和 `libc.so.6`;`1` 为该环境的 `RTLD_LAZY`。
|
||||
|
||||
```tsl
|
||||
module_handle := dlopen("libc.so.6", 1);
|
||||
func_ptr := dlsym(module_handle, "getpid");
|
||||
wrapped_func := function(): integer; cdecl; external func_ptr;
|
||||
writeLn(module_handle <> nil);
|
||||
writeLn(func_ptr <> nil);
|
||||
writeLn(##wrapped_func() > 0);
|
||||
wrapped_func := nil;
|
||||
writeLn(dlclose(module_handle) = 0);
|
||||
|
||||
function dlopen(filename: string; flags: integer): pointer; cdecl; external "libdl.so.2" name "dlopen";
|
||||
function dlsym(handle: pointer; symbol: string): pointer; cdecl; external "libdl.so.2" name "dlsym";
|
||||
function dlclose(handle: pointer): integer; cdecl; external "libdl.so.2" name "dlclose";
|
||||
```
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
```text
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
```
|
||||
|
||||
释放顺序是先将包装变量设为 `nil`,再调用 `dlclose`。
|
||||
|
||||
`MakeInstance` 生成的指针由 `DeleteInstance` 释放;若已包装为 TSL 函数变量,先将包装变量设为 `nil`。示例见 `syntax-17-008`,API 签名见 `tsl-api-reference`。
|
||||
|
||||
## 可直接照写示例
|
||||
|
||||
@@ -41,11 +139,7 @@ writeLn(Tick64Alias() > 0);
|
||||
function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCount64";
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 输出 `1`
|
||||
- 说明 `stdcall` + `external "dll" name "symbol"` 是本页明确的外部函数声明骨架
|
||||
- 也说明 TSL 里的函数名可以和 DLL 导出名不同,再通过 `name "ExportName"` 绑定
|
||||
结果说明:输出 `1`;`name` 用于绑定不同名的导出符号。
|
||||
|
||||
代码块身份:输出片段
|
||||
|
||||
@@ -53,10 +147,10 @@ function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCou
|
||||
1
|
||||
```
|
||||
|
||||
Linux / POSIX 环境的同类最小骨架:
|
||||
Linux 的同类声明:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
代码块说明:仅类 Unix 环境可执行(依赖 `libc.so.6`);Windows 下不可照抄,本块只演示 `.so` 库名写法。
|
||||
代码块说明:依赖 `libc.so.6`。
|
||||
|
||||
```tsl
|
||||
writeLn(getpid() > 0);
|
||||
@@ -70,8 +164,6 @@ function getpid(): integer; cdecl; external "libc.so.6";
|
||||
1
|
||||
```
|
||||
|
||||
这段说明 Linux / POSIX 目标下可以用 `.so` 库名声明外部函数;生成代码时仍要先判断用户的目标平台。
|
||||
|
||||
当本地函数名和 DLL 导出名一致时,`name` 可以省略:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
@@ -82,12 +174,9 @@ writeLn(GetTickCount64() > 0);
|
||||
function GetTickCount64(): int64; stdcall; external "kernel32.dll";
|
||||
```
|
||||
|
||||
结果说明:
|
||||
结果说明:输出 `1`。
|
||||
|
||||
- 输出 `1`
|
||||
- 说明当本地函数名和导出名一致时,`name "symbol"` 不是强制写法
|
||||
|
||||
Windows 的同一 API 示例里,省略调用约定与显式 `cdecl` 也列入文档边界:
|
||||
也可以省略调用约定或显式写 `cdecl`:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
@@ -102,8 +191,7 @@ function TickCdecl(): int64; cdecl; external "kernel32.dll" name "GetTickCount64
|
||||
结果说明:
|
||||
|
||||
- 依次输出 `1`、`1`
|
||||
- 这只说明 Windows 的同一 API 示例里,这两种写法属于文档边界
|
||||
- 不要把这个结果直接泛化成“所有平台、所有架构下调用约定都等价”
|
||||
- Win32 下仍须按原函数的真实调用约定声明
|
||||
|
||||
### `procedure external`
|
||||
|
||||
@@ -123,10 +211,7 @@ function Tick64(): int64; stdcall; external "kernel32.dll" name "GetTickCount64"
|
||||
procedure SleepMs(ms: integer); stdcall; external "kernel32.dll" name "Sleep";
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 输出 `1`
|
||||
- 说明无返回值的外部过程可以直接声明为 `procedure`
|
||||
结果说明:输出 `1`;无返回值接口可声明为 `procedure`。
|
||||
|
||||
### 原生函数指针包装
|
||||
|
||||
@@ -148,10 +233,7 @@ function LoadLibraryA(lib_name: string): pointer; stdcall; external "kernel32.dl
|
||||
function GetProcAddress(module_handle: pointer; proc_name: string): pointer; stdcall; external "kernel32.dll" name "GetProcAddress";
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 依次输出 `1`、`1`、`1`
|
||||
- 说明 `function(...); ... external fp;` 不只适用于 `makeInstance(...)` 的结果,也适用于 `GetProcAddress(...)` 返回的原生函数指针
|
||||
结果说明:依次输出 `1`、`1`、`1`;`GetProcAddress(...)` 返回的指针可用 `external fp` 包装。
|
||||
|
||||
### DLL 名的文档边界
|
||||
|
||||
@@ -159,8 +241,6 @@ function GetProcAddress(module_handle: pointer; proc_name: string): pointer; std
|
||||
|
||||
<!-- tags: dll 名写在哪, 库名怎么给, 常量放路径 -->
|
||||
|
||||
类常量字符串:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
@@ -178,12 +258,7 @@ public
|
||||
end;
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 输出 `1`
|
||||
- 说明类常量字符串(如 `kKernelDll`)可以用于 `external kKernelDll` 这种 DLL 名位置
|
||||
|
||||
不作为可写事实边界:
|
||||
结果说明:输出 `1`;类常量字符串可以用在 DLL 名位置。
|
||||
|
||||
代码块身份:反例 / 不可照写
|
||||
|
||||
@@ -191,21 +266,23 @@ end;
|
||||
function TickFromExpr(): int64; stdcall; external "kernel32"$"."$"dll" name "GetTickCount64";
|
||||
```
|
||||
|
||||
上面这种 DLL 名字符串拼接表达式不作为可写事实,会报 `dll filename const string not found after external`。本页只把字面量字符串和类常量字符串写成可靠规则。
|
||||
字符串拼接会报 `dll filename const string not found after external`。
|
||||
|
||||
### `makeInstance`
|
||||
### `MakeInstance`
|
||||
|
||||
<!-- section-id: syntax-17-008 -->
|
||||
|
||||
<!-- tags: 回调函数, 把 TSL 函数给 C 用, 生成函数实例 -->
|
||||
<!-- tags: 回调函数, 把 TSL 函数给 C 用, 生成函数实例, 函数指针释放, DeleteInstance, MakeInstance 生命周期 -->
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
func_ptr := makeInstance(thisFunction(Add), "cdecl", 0);
|
||||
func_ptr := MakeInstance(thisFunction(Add), "cdecl", 0);
|
||||
wrapped_func := function(a: integer; b: integer): integer; external func_ptr;
|
||||
writeLn(func_ptr <> nil);
|
||||
writeLn(##wrapped_func(3, 4));
|
||||
wrapped_func := nil;
|
||||
writeLn(DeleteInstance(func_ptr));
|
||||
|
||||
function Add(a: integer; b: integer): integer;
|
||||
begin
|
||||
@@ -213,11 +290,7 @@ begin
|
||||
end;
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- `func_ptr <> nil` 输出 `1`
|
||||
- `##wrapped_func(3, 4)` 输出 `7`
|
||||
- 说明 `makeInstance(...)` 生成的函数指针可以再通过 `function(...); external fp;` 包装回 TSL 侧调用
|
||||
结果说明:依次输出 `1`、`7`、`1`。
|
||||
|
||||
### 线程模式最小正例
|
||||
|
||||
@@ -229,7 +302,7 @@ end;
|
||||
|
||||
```tsl
|
||||
setGlobalCache("THREAD_TEST_KEY", 0);
|
||||
worker_ptr := makeInstance(thisFunction(Worker), "cdecl", 1);
|
||||
worker_ptr := MakeInstance(thisFunction(Worker), "cdecl", 1);
|
||||
thread_handle := CreateThread(nil, nil, worker_ptr, nil, 0, thread_id);
|
||||
writeLn(worker_ptr <> nil);
|
||||
writeLn(thread_handle <> nil);
|
||||
@@ -248,25 +321,13 @@ begin
|
||||
end;
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 依次输出 `1`、`1`、`1`、`1`
|
||||
- 说明 `makeInstance(..., "cdecl", 1)` 可以生成可用于 `CreateThread` 的回调指针
|
||||
- 也说明线程体里的 `setGlobalCache(...)` 可用于这个最小闭环
|
||||
结果说明:依次输出 `1`、`1`、`1`、`1`。
|
||||
|
||||
## 默认生成模板
|
||||
|
||||
<!-- section-id: syntax-17-010 -->
|
||||
|
||||
DLL 引入的最小默认骨架如下:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
writeLn(Tick64Alias() > 0);
|
||||
|
||||
function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCount64";
|
||||
```
|
||||
默认使用 `syntax-17-004` 的显式调用约定、`external` 和 `name` 骨架。
|
||||
|
||||
## 禁止项
|
||||
|
||||
@@ -274,5 +335,5 @@ function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCou
|
||||
|
||||
- 把 `external` 的 DLL 名直接写成字符串拼接表达式。
|
||||
- 省略了外部函数的参数类型或返回类型。
|
||||
- 把 `makeInstance(...)` 生成的结果默认写成普通函数名直调,而不是先包装或用 `##f(...)`。
|
||||
- 直接把 Windows 线程示例当成跨平台事实。
|
||||
- 把 `MakeInstance(...)` 生成的结果默认写成普通函数名直调,而不是先包装或用 `##f(...)`。
|
||||
- 使用与目标平台不匹配的库名或调用约定。
|
||||
|
||||
Reference in New Issue
Block a user