Files
csh 40a9885eb4 feat(tsl-syntax-reference): harden retrieval and restructure pages
- flag weak candidates (no intent/heading/identifier/tag hit) and exit 2
  when every candidate is weak: mis-hits used to be indistinguishable
  from real hits, so the retry-with-better-terms loop never fired
- accept multiple ids per --section for batch fetch, failing atomically
  on any unknown id so a partial fetch cannot pass as complete
- move query synonyms and page intent aliases to data/lexicon.json and
  enforce alias/page correspondence in --check; curation data no longer
  lives in the engine
- document the weak-hit rule, batch fetch and prelude-once guidance in
  SKILL.md, with curation discipline in data/README.md
- drop 11_pitfalls.md, renumber the trailing pages and spread retrieval
  tags across topics; lexicon keys are page filenames, so the renumbering
  and the new --check rule cannot land in separate commits
2026-07-29 15:45:47 +08:00

8.1 KiB

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 声明

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

writeLn(Tick64Alias() > 0);

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

结果说明:

  • 输出 1
  • 说明 stdcall + external "dll" name "symbol" 是本页明确的外部函数声明骨架
  • 也说明 TSL 里的函数名可以和 DLL 导出名不同,再通过 name "ExportName" 绑定

代码块身份:输出片段

1

Linux / POSIX 环境的同类最小骨架:

代码块身份:可直接照写示例 代码块说明:仅类 Unix 环境可执行(依赖 libc.so.6);Windows 下不可照抄,本块只演示 .so 库名写法。

writeLn(getpid() > 0);

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

代码块身份:输出片段

1

这段说明 Linux / POSIX 目标下可以用 .so 库名声明外部函数;生成代码时仍要先判断用户的目标平台。

当本地函数名和 DLL 导出名一致时,name 可以省略:

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

writeLn(GetTickCount64() > 0);

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

结果说明:

  • 输出 1
  • 说明当本地函数名和导出名一致时,name "symbol" 不是强制写法

Windows 的同一 API 示例里,省略调用约定与显式 cdecl 也列入文档边界:

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

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

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

结果说明:

  • 依次输出 11
  • 这只说明 Windows 的同一 API 示例里,这两种写法属于文档边界
  • 不要把这个结果直接泛化成“所有平台、所有架构下调用约定都等价”

procedure external

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

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

原生函数指针包装

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

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";

结果说明:

  • 依次输出 111
  • 说明 function(...); ... external fp; 不只适用于 makeInstance(...) 的结果,也适用于 GetProcAddress(...) 返回的原生函数指针

DLL 名的文档边界

类常量字符串:

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

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 名位置

不作为可写事实边界:

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

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

上面这种 DLL 名字符串拼接表达式不作为可写事实,会报 dll filename const string not found after external。本页只把字面量字符串和类常量字符串写成可靠规则。

makeInstance

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

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 侧调用

线程模式最小正例

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

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;

结果说明:

  • 依次输出 1111
  • 说明 makeInstance(..., "cdecl", 1) 可以生成可用于 CreateThread 的回调指针
  • 也说明线程体里的 setGlobalCache(...) 可用于这个最小闭环

默认生成模板

DLL 引入的最小默认骨架如下:

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

writeLn(Tick64Alias() > 0);

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

禁止项

  • external 的 DLL 名直接写成字符串拼接表达式。
  • 省略了外部函数的参数类型或返回类型。
  • makeInstance(...) 生成的结果默认写成普通函数名直调,而不是先包装或用 ##f(...)
  • 直接把 Windows 线程示例当成跨平台事实。