# TSL 外部调用与线程 本页说明动态库声明、原生函数指针、C 回调和线程调用。`MakeInstance`、`DeleteInstance` 是 builtin API;`external`、`name`、`KeepResident`、`cdecl`、`stdcall` 和外部参数声明是语法。 ## 本篇职责 回答外部动态库、函数指针、C 回调和线程调用的写法。`LoadLibraryA`、`GetProcAddress`、`dlopen`、`dlsym` 是宿主 API;`TSL_ScriptGo` 等 `TSSVRAPI.h` 名称是 C/C++ SDK API,均不进入 TSL builtin 索引。 ## 核心规则 - 外部函数声明的文档明确形态是 `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 与动态库常驻 `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 写回 当外部函数需要通过指针写回调用方变量时,在参数名前使用 `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 动态加载与函数指针生命周期 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`。 ## 可直接照写示例 ### 最小 `external` 声明 代码块身份:可直接照写示例 ```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` 代码块身份:可直接照写示例 ```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`。 ### 原生函数指针包装 代码块身份:可直接照写示例 ```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 名的文档边界 代码块身份:可直接照写示例 ```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` 代码块身份:可直接照写示例 ```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`。 ### 线程模式最小正例 代码块身份:可直接照写示例 ```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`。 ## 默认生成模板 默认使用 `syntax-17-004` 的显式调用约定、`external` 和 `name` 骨架。 ## 禁止项 - 把 `external` 的 DLL 名直接写成字符串拼接表达式。 - 省略了外部函数的参数类型或返回类型。 - 把 `MakeInstance(...)` 生成的结果默认写成普通函数名直调,而不是先包装或用 `##f(...)`。 - 使用与目标平台不匹配的库名或调用约定。