📝 docs(tsl): restructure agent-facing reference
Rework TSL syntax, catalog, modules, and routing docs around deterministic agent lookup and generation. Split large catalog pages into focused function fact pages, remove obsolete pending/verified/unavailable paths, and update consistency tests for the new structure.
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
# TSL 外部调用与线程
|
||||
|
||||
文档类型:语法深水专题
|
||||
是否可直接用于生成代码:仅部分
|
||||
是否含可直接照写示例:是
|
||||
是否含不可照写反例:是
|
||||
遇到不确定时:先按本页候选页继续判断;[05_functions_and_calls.md](05_functions_and_calls.md)、[19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)、[11_pitfalls.md](11_pitfalls.md);仍不命中时回到语法路由中心 [index.md](index.md);如果问题已经超出语法层,回到 TSL 总入口 [../index.md](../index.md)
|
||||
|
||||
这一篇吸收函数专题里和外部系统交互有关的部分:`external`、动态库调用、原生函数指针包装、C 回调和线程调用。
|
||||
|
||||
## 本篇职责
|
||||
|
||||
回答“普通函数怎么写已经清楚后,外部 DLL、原生函数指针、C 回调、多线程这些系统交互能力应该去哪里查”。
|
||||
|
||||
## 智能体外部调用/线程判断流程
|
||||
|
||||
1. 先判断要声明外部函数、包装原生函数指针、生成 C 回调,还是处理线程相关能力。
|
||||
2. 外部函数声明只照本页明确的调用约定、`external` 和可选 `name` 形态写。
|
||||
3. DLL 名优先写字面量或文档中的类常量,不要拼接表达式。
|
||||
4. 原生函数指针包装、C 回调和线程相关能力只照本页文档边界写。
|
||||
5. 没有对应代码块时不要发明外部调用/线程写法。
|
||||
|
||||
## 核心规则
|
||||
|
||||
- 外部函数声明的文档明确形态是 `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 环境的同类最小骨架:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```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
|
||||
t1 := Tick64();
|
||||
SleepMs(20);
|
||||
t2 := Tick64();
|
||||
writeLn(t2 >= t1);
|
||||
|
||||
function Tick64(): int64; stdcall; external "kernel32.dll" name "GetTickCount64";
|
||||
procedure SleepMs(ms: integer); stdcall; external "kernel32.dll" name "Sleep";
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 输出 `1`
|
||||
- 说明无返回值的外部过程可以直接声明为 `procedure`
|
||||
|
||||
### 原生函数指针包装
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
h := LoadLibraryA("kernel32.dll");
|
||||
fp := GetProcAddress(h, "GetTickCount64");
|
||||
f := function(): int64; stdcall; external fp;
|
||||
writeLn(h <> nil);
|
||||
writeLn(fp <> nil);
|
||||
writeLn(##f() > 0);
|
||||
|
||||
function LoadLibraryA(s: string): pointer; stdcall; external "kernel32.dll" name "LoadLibraryA";
|
||||
function GetProcAddress(hModule: pointer; lpProcName: string): pointer; stdcall; external "kernel32.dll" name "GetProcAddress";
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 依次输出 `1`、`1`、`1`
|
||||
- 说明 `function(...); ... external fp;` 不只适用于 `makeInstance(...)` 的结果,也适用于 `GetProcAddress(...)` 返回的原生函数指针
|
||||
|
||||
### DLL 名的文档边界
|
||||
|
||||
类常量字符串:
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
d := new Demo();
|
||||
writeLn(d.Run());
|
||||
|
||||
type Demo = class
|
||||
public
|
||||
const kernel_dll = "kernel32.dll";
|
||||
function Run();
|
||||
begin
|
||||
return TickConst() > 0;
|
||||
end;
|
||||
function TickConst(): int64; stdcall; external kernel_dll name "GetTickCount64";
|
||||
end;
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- 输出 `1`
|
||||
- 说明类常量字符串可以用于 `external kernel_dll` 这种 DLL 名位置
|
||||
|
||||
不作为可写事实边界:
|
||||
|
||||
代码块身份:反例 / 不可照写
|
||||
|
||||
```text
|
||||
function TickFromExpr(): int64; stdcall; external "kernel32"$"."$"dll" name "GetTickCount64";
|
||||
```
|
||||
|
||||
上面这种 DLL 名字符串拼接表达式不作为可写事实,会报 `dll filename const string not found after external`。本页只把字面量字符串和类常量字符串写成可靠规则。
|
||||
|
||||
### `makeInstance`
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
fp := makeInstance(thisFunction(Add), "cdecl", 0);
|
||||
f := function(a: integer; b: integer): integer; external fp;
|
||||
writeLn(fp <> nil);
|
||||
writeLn(##f(3, 4));
|
||||
|
||||
function Add(a: integer; b: integer): integer;
|
||||
begin
|
||||
return a + b;
|
||||
end;
|
||||
```
|
||||
|
||||
结果说明:
|
||||
|
||||
- `fp <> nil` 输出 `1`
|
||||
- `##f(3, 4)` 输出 `7`
|
||||
- 说明 `makeInstance(...)` 生成的函数指针可以再通过 `function(...); external fp;` 包装回 TSL 侧调用
|
||||
|
||||
### 线程模式最小正例
|
||||
|
||||
代码块身份:可直接照写示例
|
||||
|
||||
```tsl
|
||||
setGlobalCache("THREAD_TEST_KEY", 0);
|
||||
fp := makeInstance(thisFunction(Worker), "cdecl", 1);
|
||||
h := CreateThread(nil, nil, fp, nil, 0, tid);
|
||||
writeLn(fp <> nil);
|
||||
writeLn(h <> nil);
|
||||
writeLn(WaitForSingleObject(h, 5000) >= 0);
|
||||
getGlobalCache("THREAD_TEST_KEY", v);
|
||||
writeLn(v);
|
||||
CloseHandle(h);
|
||||
|
||||
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(h: pointer; timeout: Integer): Integer; stdcall; external "kernel32.dll" name "WaitForSingleObject";
|
||||
function CloseHandle(h: pointer): Integer; stdcall; external "kernel32.dll" name "CloseHandle";
|
||||
function Worker(p: 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 线程示例当成跨平台事实。
|
||||
Reference in New Issue
Block a user