Squashed 'docs/standards/playbook/' changes from c3f8137..25d895d
25d895d 🐛 fix(gitea_workflow): clean up temp repos after job steps 2bc3b11 🐛 fix(gitea_workflow): clean up temporary repo dirs in workflows 98c3f30 📝 docs(agent_rules): allow plan execution on current branch 16c7230 📝 docs(prompts): define custom verify layering 8efc4dd 🐛 fix(skills): quote commit-message description bc8498f 🐛 fix(ci): install tomli for gitea tests 55cda3b 🐛 fix(tests): report missing toml parser clearly c0729c7 🐛 fix(playbook): import Optional for cli compatibility 63e24bf 📦 deps(skills): sync thirdparty skills d2f9356 🐛 fix(ci): isolate gitea workflow repos 588b81d 🐛 fix(ci): inline gitea workflow bootstrap e0b1c3a ♻️ refactor(skills): standardize first-party skill contracts 2c5050d ♻️ refactor(skills): rename repo skills source dir f049dfb 📦 deps(skills): drop duplicate first-party superpowers skills 234b335 ✨ feat(workflow): add superpowers planning and execution state tracking c1702a6 📝 docs(markdown): format tracked markdown and drop stale templates 2325409 📝 docs(markdown): clarify optional markdownlint usage 214c44e 🔧 chore(markdown): add markdownlint baseline and lint fixes a22b324 📝 docs(templates): add execution and memory-bank prompt templates 223a797 📝 docs(templates): update README for Claude Code and current features 4ac8672 📝 docs: simplify README + platform-agnostic tools + auto-create local rules 2431c9d 📝 docs: add claude_md config and use cross-platform paths d64b248 📝 docs: fix README.md inaccuracies and add Claude Code info c8d6bf2 🐛 fix(playbook): use relative paths in CLAUDE.md when not at project root 6518f0f ✨ feat(playbook): auto-create CLAUDE.md with path discovery 6ec9a45 ✨ feat(skills): add skill_link symlink support + platform-agnostic prompt 9f8b6b5 📝 docs: update README and config example for Claude Code support 79cff6c 📝 docs(skills): add Claude Code platform support 452c6f5 ✨ feat(playbook): auto-inject AGENTS.md into CLAUDE.md e1dbf3c 🐛 fix(skills): remove dual-path from commit-message skill f3a7259 🔧 chore(ci): use prepare_repo.sh in both workflows da08212 🔧 chore(ci): extract prepare_repo.sh and clean up workflows 7ade85e 🗑️ remove(tsl): drop syntax_book/, data/ source and build script f94dba0 ♻️ refactor(skills): update playbook.py and tests for thirdparty/ layout b3df412 ♻️ refactor(skills): separate thirdparty skills into thirdparty/ subdirectory 64950e7 📦 deps(skills): sync thirdparty skills a2e3cb0 ✨ feat(playbook): add no_backup deploy controls 8609d59 🐛 fix(docs): repair reference catalog source links 956da11 🐛 fix(playbook): publish hidden ci test fixes 3f67754 📦 deps(skills): sync thirdparty skills 08ca87b 📦 deps(skills): add karpathy thirdparty sync 96b705b 📝 docs(tsl): rebuild canonical syntax and routing manual 3ed5052 📦 deps(skills): sync thirdparty skills 60108dd 📦 deps(skills): sync thirdparty skills da85d4e 🐛 fix(thirdparty): prune nested project snapshots a2a697e 📦 deps(skills): sync thirdparty skills 9df610a 🐛 fix(thirdparty): exclude duplicated superpowers skills 33dd5bb 🐛 fix(thirdparty): preserve optional manifest fields 91b0ea7 🐛 fix(thirdparty): preserve manifest during snapshot update 2e26f98 🔧 chore(thirdparty): generalize skills sync pipeline 5b9c1e3 📦 deps(skills): sync superpowers 2f2d34a 📝 docs(readme): normalize subtree command spacing 62db7db 🐛 fix(ci): serialize superpowers update and sync 3463223 🐛 fix(ci): use literal superpowers sync paths 48f6de8 📦 deps(skills): sync superpowers 4b23529 🔧 chore(ci): merge superpowers update and sync workflow a56d75b 📦 deps(skills): sync superpowers 84bcefa 🔧 chore(ci): use ci[bot] commit author name 00a07e5 📦 deps(skills): sync superpowers 7b84daf 🐛 fix(templates): enforce main loop progress tracking 51373d7 🔧 chore(ci): automate superpowers sync workflow eaaa39c 🐛 fix(ci): prevent stale superpowers sync from restoring skills block 79755c6 📦 deps(skills): sync superpowers 836d878 📦 deps(skills): sync superpowers 8216c9f 📦 deps(skills): sync superpowers 9439505 🐛 fix(playbook): address reported repo issues git-subtree-dir: docs/standards/playbook git-subtree-split: 25d895d8b3f56624ccfe99ad7289e9eb49e0f316
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
# External Calls And Threads
|
||||
|
||||
文档类型:语法主线
|
||||
是否可直接用于生成代码:仅部分
|
||||
是否含已验证可执行示例:是
|
||||
是否含已验证反例:是
|
||||
遇到不确定时跳转到:[06_functions_and_calls.md](06_functions_and_calls.md)、[22_namespace_libpath_and_unit_runtime.md](22_namespace_libpath_and_unit_runtime.md)、[12_pitfalls.md](12_pitfalls.md)
|
||||
|
||||
手册位置:第 21 篇,共 32 篇。上一篇:[20_strings_and_text.md](20_strings_and_text.md)。下一篇:[22_namespace_libpath_and_unit_runtime.md](22_namespace_libpath_and_unit_runtime.md)。
|
||||
|
||||
这一篇吸收函数专题里和外部系统交互有关的部分:`external`、动态库调用、函数指针包装、线程调用。
|
||||
|
||||
## 这一篇解决什么问题
|
||||
|
||||
回答“普通函数怎么写已经清楚后,外部 DLL、函数指针、多线程这些系统交互能力应该去哪里查”。
|
||||
|
||||
## 必须记住的规则
|
||||
|
||||
- 当前解释器接受 `function Name(...): Type; stdcall|cdecl; external "dll" [name "symbol"];` 这类外部函数声明。
|
||||
- 当 TSL 函数名和 DLL 导出名一致时,`name "symbol"` 可以省略。
|
||||
- 在当前 `Win64` 解释器上,同一个 `GetTickCount64` 例子里,`stdcall`、`cdecl`、省略调用约定都已跑通;手册示例仍默认把调用约定显式写出来。
|
||||
- `procedure Name(...); ... external ...;` 当前也已验证可用。
|
||||
- `function(...): ...; external fp;` 当前可以把原生函数指针重新包装成 TSL 可调用对象。
|
||||
- 当前已验证的 DLL 名写法包括字面量字符串和类常量字符串;不要把字符串拼接表达式直接当成稳定写法。
|
||||
- `MakeInstance(ThisFunction(Func), "cdecl", 0)` 当前可以把 TSL 函数包装成 C 调用约定函数指针。
|
||||
- `MakeInstance(..., "cdecl", 1)` 当前在 `Win64` 下已经拿到最小线程正例,可以直接交给 `CreateThread`。
|
||||
- 当前页的线程示例是 Windows 专题,用到了 `kernel32.dll`。
|
||||
|
||||
## 已验证语法
|
||||
|
||||
### 最小 `external` 声明
|
||||
|
||||
代码块身份:已验证可执行示例
|
||||
|
||||
```tsl
|
||||
program test;
|
||||
function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCount64";
|
||||
begin
|
||||
WriteLn(Tick64Alias() > 0);
|
||||
end.
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- 输出 `1`
|
||||
- 说明当前解释器接受 `stdcall` + `external "dll" name "symbol"` 这类外部函数声明骨架
|
||||
- 也说明 TSL 里的函数名可以和 DLL 导出名不同,再通过 `name "ExportName"` 绑定
|
||||
|
||||
当本地函数名和 DLL 导出名一致时,`name` 可以省略:
|
||||
|
||||
代码块身份:已验证可执行示例
|
||||
|
||||
```tsl
|
||||
program test;
|
||||
function GetTickCount64(): int64; stdcall; external "kernel32.dll";
|
||||
begin
|
||||
WriteLn(GetTickCount64() > 0);
|
||||
end.
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- 输出 `1`
|
||||
- 说明 `name "symbol"` 不是当前解释器下的强制写法
|
||||
|
||||
当前 `Win64` 基线下,省略调用约定与显式 `cdecl` 也通过了同一 API 的最小验证:
|
||||
|
||||
代码块身份:已验证可执行示例
|
||||
|
||||
```tsl
|
||||
program test;
|
||||
function TickNoConv(): int64; external "kernel32.dll" name "GetTickCount64";
|
||||
function TickCdecl(): int64; cdecl; external "kernel32.dll" name "GetTickCount64";
|
||||
begin
|
||||
WriteLn(TickNoConv() > 0);
|
||||
WriteLn(TickCdecl() > 0);
|
||||
end.
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- 依次输出 `1`、`1`
|
||||
- 这只说明当前 `Win64` 解释器下,这两种写法都能跑通这个例子
|
||||
- 不要把这个结果直接泛化成“所有平台、所有架构下调用约定都等价”
|
||||
|
||||
### `procedure external`
|
||||
|
||||
代码块身份:已验证可执行示例
|
||||
|
||||
```tsl
|
||||
program test;
|
||||
function Tick64(): int64; stdcall; external "kernel32.dll" name "GetTickCount64";
|
||||
procedure SleepMs(ms: integer); stdcall; external "kernel32.dll" name "Sleep";
|
||||
begin
|
||||
t1 := Tick64();
|
||||
SleepMs(20);
|
||||
t2 := Tick64();
|
||||
WriteLn(t2 >= t1);
|
||||
end.
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- 输出 `1`
|
||||
- 说明无返回值的外部过程可以直接声明为 `procedure`
|
||||
|
||||
### 原生函数指针包装
|
||||
|
||||
代码块身份:已验证可执行示例
|
||||
|
||||
```tsl
|
||||
program test;
|
||||
function LoadLibraryA(s: string): pointer; stdcall; external "kernel32.dll" name "LoadLibraryA";
|
||||
function GetProcAddress(hModule: pointer; lpProcName: string): pointer; stdcall; external "kernel32.dll" name "GetProcAddress";
|
||||
begin
|
||||
h := LoadLibraryA("kernel32.dll");
|
||||
fp := GetProcAddress(h, "GetTickCount64");
|
||||
f := function(): int64; stdcall; external fp;
|
||||
WriteLn(h <> nil);
|
||||
WriteLn(fp <> nil);
|
||||
WriteLn(##f() > 0);
|
||||
end.
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- 依次输出 `1`、`1`、`1`
|
||||
- 说明 `function(...); ... external fp;` 不只适用于 `MakeInstance(...)` 的结果,也适用于 `GetProcAddress(...)` 返回的原生函数指针
|
||||
|
||||
### DLL 名的已验证边界
|
||||
|
||||
类常量字符串:
|
||||
|
||||
代码块身份:已验证可执行示例
|
||||
|
||||
```tsl
|
||||
program test;
|
||||
type Demo = class
|
||||
const KRNL = "kernel32.dll";
|
||||
public
|
||||
function Run();
|
||||
begin
|
||||
return TickConst() > 0;
|
||||
end;
|
||||
function TickConst(): int64; stdcall; external KRNL name "GetTickCount64";
|
||||
end;
|
||||
begin
|
||||
d := new Demo();
|
||||
WriteLn(d.Run());
|
||||
end.
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- 输出 `1`
|
||||
- 说明当前解释器接受“类常量字符串 -> `external KRNL`”这条最小路径
|
||||
|
||||
当前已验证的反向边界:
|
||||
|
||||
代码块身份:反例 / 不可照写
|
||||
|
||||
```text
|
||||
function TickFromExpr(): int64; stdcall; external "kernel32"$"."$"dll" name "GetTickCount64";
|
||||
```
|
||||
|
||||
上面这种 DLL 名字符串拼接表达式在当前解释器里会报 `dll filename const string not found after external`。因此当前手册只把字面量字符串和已单独验证过的类常量字符串写成可靠规则。
|
||||
|
||||
### `MakeInstance`
|
||||
|
||||
代码块身份:已验证可执行示例
|
||||
|
||||
```tsl
|
||||
program test;
|
||||
function Add(a: integer; b: integer): integer;
|
||||
begin
|
||||
return a + b;
|
||||
end;
|
||||
begin
|
||||
fp := MakeInstance(ThisFunction(Add), "cdecl", 0);
|
||||
f := function(a: integer; b: integer): integer; external fp;
|
||||
WriteLn(fp <> nil);
|
||||
WriteLn(##f(3, 4));
|
||||
end.
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- `fp <> nil` 输出 `1`
|
||||
- `##f(3, 4)` 输出 `7`
|
||||
- 说明当前解释器里,`MakeInstance(...)` 生成的函数指针可以再通过 `function(...); external fp;` 包装回 TSL 侧调用
|
||||
|
||||
### 线程模式最小正例
|
||||
|
||||
代码块身份:已验证可执行示例
|
||||
|
||||
```tsl
|
||||
program test;
|
||||
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;
|
||||
begin
|
||||
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);
|
||||
end.
|
||||
```
|
||||
|
||||
已验证运行结果:
|
||||
|
||||
- 依次输出 `1`、`1`、`1`、`1`
|
||||
- 说明 `MakeInstance(..., "cdecl", 1)` 当前可以生成可用于 `CreateThread` 的回调指针
|
||||
- 也说明线程体里的 `SetGlobalCache(...)` 当前能够跑通这个最小闭环
|
||||
|
||||
## 最小可编译示例
|
||||
|
||||
如果你只是要先记住最小的 DLL 引入骨架,用这个:
|
||||
|
||||
代码块身份:已验证可执行示例
|
||||
|
||||
```tsl
|
||||
program test;
|
||||
function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCount64";
|
||||
begin
|
||||
WriteLn(Tick64Alias() > 0);
|
||||
end.
|
||||
```
|
||||
|
||||
## 常见误写
|
||||
|
||||
- 把 `external` 的 DLL 名直接写成字符串拼接表达式。
|
||||
- 省略了外部函数的参数类型或返回类型。
|
||||
- 把 `MakeInstance(...)` 生成的结果默认写成普通函数名直调,而不是先包装或用 `##`.
|
||||
- 直接把 Windows 线程示例当成跨平台事实。
|
||||
|
||||
## 跳转指引
|
||||
|
||||
- 回看函数主线:见 [06_functions_and_calls.md](06_functions_and_calls.md)
|
||||
- 回看运行时边界:见 [16_debug_and_profiler.md](16_debug_and_profiler.md)
|
||||
- 看 unit / namespace / 查找路径:见 [22_namespace_libpath_and_unit_runtime.md](22_namespace_libpath_and_unit_runtime.md)
|
||||
Reference in New Issue
Block a user