# TSL 外部调用与线程 这一篇吸收函数专题里和外部系统交互有关的部分:`external`、动态库调用、原生函数指针包装、C 回调和线程调用。 ## 本篇职责 回答“外部 DLL 声明、原生函数指针包装、C 回调和多线程调用有哪些文档明确写法”。本页只覆盖系统交互能力,普通 TSL 函数的定义与调用不在本页收口。 ## 核心规则 - 外部函数声明的文档明确形态是 `function Name(...): Type; stdcall|cdecl; external "dll" [name "symbol"];`。 - 当 TSL 函数名和 DLL 导出名一致时,`name "symbol"` 可以省略。 - Windows 示例默认显式写调用约定;不要把省略调用约定当成跨平台默认规则。 - 无返回值的外部接口可声明为 `procedure Name(...); ... external ...;`。 - `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`。 ## 可直接照写示例 ### 最小 `external` 声明 代码块身份:可直接照写示例 ```tsl writeLn(Tick64Alias() > 0); function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCount64"; ``` 结果说明: - 输出 `1` - 说明 `stdcall` + `external "dll" name "symbol"` 是本页明确的外部函数声明骨架 - 也说明 TSL 里的函数名可以和 DLL 导出名不同,再通过 `name "ExportName"` 绑定 代码块身份:输出片段 ```text 1 ``` Linux / POSIX 环境的同类最小骨架: 代码块身份:可直接照写示例 代码块说明:仅类 Unix 环境可执行(依赖 `libc.so.6`);Windows 下不可照抄,本块只演示 `.so` 库名写法。 ```tsl writeLn(getpid() > 0); function getpid(): integer; cdecl; external "libc.so.6"; ``` 代码块身份:输出片段 ```text 1 ``` 这段说明 Linux / POSIX 目标下可以用 `.so` 库名声明外部函数;生成代码时仍要先判断用户的目标平台。 当本地函数名和 DLL 导出名一致时,`name` 可以省略: 代码块身份:可直接照写示例 ```tsl writeLn(GetTickCount64() > 0); function GetTickCount64(): int64; stdcall; external "kernel32.dll"; ``` 结果说明: - 输出 `1` - 说明当本地函数名和导出名一致时,`name "symbol"` 不是强制写法 Windows 的同一 API 示例里,省略调用约定与显式 `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` - 这只说明 Windows 的同一 API 示例里,这两种写法属于文档边界 - 不要把这个结果直接泛化成“所有平台、所有架构下调用约定都等价” ### `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` - 说明 `function(...); ... external fp;` 不只适用于 `makeInstance(...)` 的结果,也适用于 `GetProcAddress(...)` 返回的原生函数指针 ### 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` - 说明类常量字符串(如 `kKernelDll`)可以用于 `external kKernelDll` 这种 DLL 名位置 不作为可写事实边界: 代码块身份:反例 / 不可照写 ```text function TickFromExpr(): int64; stdcall; external "kernel32"$"."$"dll" name "GetTickCount64"; ``` 上面这种 DLL 名字符串拼接表达式不作为可写事实,会报 `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)); function Add(a: integer; b: integer): integer; begin return a + b; end; ``` 结果说明: - `func_ptr <> nil` 输出 `1` - `##wrapped_func(3, 4)` 输出 `7` - 说明 `makeInstance(...)` 生成的函数指针可以再通过 `function(...); external fp;` 包装回 TSL 侧调用 ### 线程模式最小正例 代码块身份:可直接照写示例 ```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` - 说明 `makeInstance(..., "cdecl", 1)` 可以生成可用于 `CreateThread` 的回调指针 - 也说明线程体里的 `setGlobalCache(...)` 可用于这个最小闭环 ## 默认生成模板 DLL 引入的最小默认骨架如下: 代码块身份:可直接照写示例 ```tsl writeLn(Tick64Alias() > 0); function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCount64"; ``` ## 禁止项 - 把 `external` 的 DLL 名直接写成字符串拼接表达式。 - 省略了外部函数的参数类型或返回类型。 - 把 `makeInstance(...)` 生成的结果默认写成普通函数名直调,而不是先包装或用 `##f(...)`。 - 直接把 Windows 线程示例当成跨平台事实。