# TSL 外部调用与线程

本页说明动态库声明、原生函数指针、C 回调和线程调用。`MakeInstance`、`DeleteInstance` 是 builtin API；`external`、`name`、`KeepResident`、`cdecl`、`stdcall` 和外部参数声明是语法。

## 本篇职责

<!-- section-id: syntax-17-001 -->

回答外部动态库、函数指针、C 回调和线程调用的写法。`LoadLibraryA`、`GetProcAddress`、`dlopen`、`dlsym` 是宿主 API；`TSL_ScriptGo` 等 `TSSVRAPI.h` 名称是 C/C++ SDK API，均不进入 TSL builtin 索引。

## 核心规则

<!-- section-id: syntax-17-002 -->

- 外部函数声明的文档明确形态是 `function Name(...): Type; stdcall|cdecl; external "dll" [name "symbol"];`。
- 当 TSL 函数名和 DLL 导出名一致时，`name "symbol"` 可以省略。
- Win32 下调用约定必须与宿主函数一致；Linux 与 Win64 下 `stdcall` 和 `cdecl` 使用相同调用模式。
- 无返回值的外部接口可声明为 `procedure Name(...); ... external ...;`。
- 外部函数需要写回调用方变量时，在参数名前写 `var`；TSL 参数类型必须与原生 ABI 的参数宽度和含义一致。
- `KeepResident` 写在外部库声明末尾，用于保留动态库句柄。
- `function(...): ...; external fp;` 可以把原生函数指针重新包装成 TSL 可调用对象。
- 库名可使用字面量字符串或类常量字符串，不能使用字符串拼接表达式。
- `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`。

## 可直接照写示例

<!-- section-id: syntax-17-003 -->

### 最小 `external` 声明

<!-- section-id: syntax-17-004 -->

<!-- tags: 调外部函数, 声明外部函数, 绑 dll 函数, 导入系统函数 -->

代码块身份：可直接照写示例

```tsl
writeLn(Tick64Alias() > 0);

function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCount64";
```

结果说明：输出 `1`；`name` 用于绑定不同名的导出符号。

代码块身份：输出片段

```text
1
```

Linux 的同类声明：

代码块身份：可直接照写示例
代码块说明：依赖 `libc.so.6`。

```tsl
writeLn(getpid() > 0);

function getpid(): integer; cdecl; external "libc.so.6";
```

代码块身份：输出片段

```text
1
```

当本地函数名和 DLL 导出名一致时，`name` 可以省略：

代码块身份：可直接照写示例

```tsl
writeLn(GetTickCount64() > 0);

function GetTickCount64(): int64; stdcall; external "kernel32.dll";
```

结果说明：输出 `1`。

也可以省略调用约定或显式写 `cdecl`：

代码块身份：可直接照写示例

```tsl
writeLn(TickNoConv() > 0);
writeLn(TickCdecl() > 0);

function TickNoConv(): int64; external "kernel32.dll" name "GetTickCount64";
function TickCdecl(): int64; cdecl; external "kernel32.dll" name "GetTickCount64";
```

结果说明：

- 依次输出 `1`、`1`
- Win32 下仍须按原函数的真实调用约定声明

### `procedure external`

<!-- section-id: syntax-17-005 -->

<!-- tags: 外部过程, 无返回值的外部函数, 调系统 API -->

代码块身份：可直接照写示例

```tsl
tick_before := Tick64();
SleepMs(20);
tick_after := Tick64();
writeLn(tick_after >= tick_before);

function Tick64(): int64; stdcall; external "kernel32.dll" name "GetTickCount64";
procedure SleepMs(ms: integer); stdcall; external "kernel32.dll" name "Sleep";
```

结果说明：输出 `1`；无返回值接口可声明为 `procedure`。

### 原生函数指针包装

<!-- section-id: syntax-17-006 -->

<!-- tags: LoadLibrary, 拿函数地址, 动态加载, GetProcAddress -->

代码块身份：可直接照写示例

```tsl
module_handle := LoadLibraryA("kernel32.dll");
func_ptr := GetProcAddress(module_handle, "GetTickCount64");
wrapped_func := function(): int64; stdcall; external func_ptr;
writeLn(module_handle <> nil);
writeLn(func_ptr <> nil);
writeLn(##wrapped_func() > 0);

function LoadLibraryA(lib_name: string): pointer; stdcall; external "kernel32.dll" name "LoadLibraryA";
function GetProcAddress(module_handle: pointer; proc_name: string): pointer; stdcall; external "kernel32.dll" name "GetProcAddress";
```

结果说明：依次输出 `1`、`1`、`1`；`GetProcAddress(...)` 返回的指针可用 `external fp` 包装。

### DLL 名的文档边界

<!-- section-id: syntax-17-007 -->

<!-- tags: dll 名写在哪, 库名怎么给, 常量放路径 -->

代码块身份：可直接照写示例

```tsl
demo := new Demo();
writeLn(demo.Run());

type Demo = class
public
    const kKernelDll = "kernel32.dll";
    function Run();
    begin
        return TickConst() > 0;
    end;
    function TickConst(): int64; stdcall; external kKernelDll name "GetTickCount64";
end;
```

结果说明：输出 `1`；类常量字符串可以用在 DLL 名位置。

代码块身份：反例 / 不可照写

```text
function TickFromExpr(): int64; stdcall; external "kernel32"$"."$"dll" name "GetTickCount64";
```

字符串拼接会报 `dll filename const string not found after external`。

### `MakeInstance`

<!-- section-id: syntax-17-008 -->

<!-- tags: 回调函数, 把 TSL 函数给 C 用, 生成函数实例, 函数指针释放, DeleteInstance, MakeInstance 生命周期 -->

代码块身份：可直接照写示例

```tsl
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
    return a + b;
end;
```

结果说明：依次输出 `1`、`7`、`1`。

### 线程模式最小正例

<!-- section-id: syntax-17-009 -->

<!-- tags: 开线程, 多线程, 并发执行, 后台跑 -->

代码块身份：可直接照写示例

```tsl
setGlobalCache("THREAD_TEST_KEY", 0);
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);
writeLn(WaitForSingleObject(thread_handle, 5000) >= 0);
getGlobalCache("THREAD_TEST_KEY", result_value);
writeLn(result_value);
CloseHandle(thread_handle);

function CreateThread(attr: pointer; size: pointer; addr: pointer; p: pointer; flag: Integer; var thread_id: Integer): pointer; stdcall; external "kernel32.dll" name "CreateThread";
function WaitForSingleObject(handle: pointer; timeout: Integer): Integer; stdcall; external "kernel32.dll" name "WaitForSingleObject";
function CloseHandle(handle: pointer): Integer; stdcall; external "kernel32.dll" name "CloseHandle";
function Worker(param: pointer): integer;
begin
    setGlobalCache("THREAD_TEST_KEY", 1);
    return 1;
end;
```

结果说明：依次输出 `1`、`1`、`1`、`1`。

## 默认生成模板

<!-- section-id: syntax-17-010 -->

默认使用 `syntax-17-004` 的显式调用约定、`external` 和 `name` 骨架。

## 禁止项

<!-- section-id: syntax-17-011 -->

- 把 `external` 的 DLL 名直接写成字符串拼接表达式。
- 省略了外部函数的参数类型或返回类型。
- 把 `MakeInstance(...)` 生成的结果默认写成普通函数名直调，而不是先包装或用 `##f(...)`。
- 使用与目标平台不匹配的库名或调用约定。
