Files
playbook/skills/tsl-syntax-reference/references/17_external_calls_and_threads.md
T

11 KiB
Raw Blame History

TSL 外部调用与线程

本页说明动态库声明、原生函数指针、C 回调和线程调用。MakeInstanceDeleteInstance 是 builtin APIexternalnameKeepResidentcdeclstdcall 和外部参数声明是语法。

本篇职责

回答外部动态库、函数指针、C 回调和线程调用的写法。LoadLibraryAGetProcAddressdlopendlsym 是宿主 APITSL_ScriptGoTSSVRAPI.h 名称是 C/C++ SDK API,均不进入 TSL builtin 索引。

核心规则

  • 外部函数声明的文档明确形态是 function Name(...): Type; stdcall|cdecl; external "dll" [name "symbol"];
  • 当 TSL 函数名和 DLL 导出名一致时,name "symbol" 可以省略。
  • Win32 下调用约定必须与宿主函数一致;Linux 与 Win64 下 stdcallcdecl 使用相同调用模式。
  • 无返回值的外部接口可声明为 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 使用 DLLLinux 使用对应 .so
  • 文档列出的基础外部类型包括 IntegerStringDoubleSingleBooleanPointerPCharInt64ShortByte,未列出的类型按 String 处理。结构体等复合类型仍须按原生 ABI 组装内存;不能直接导入 C++ 引出类。

外部 ABI 与动态库常驻

external 是 TSL 与宿主 ABI 的边界,不是 builtin 定义。参数类型、宽度、对齐和调用约定必须与原生函数一致;结构体等复合参数须自行封装内存布局。

KeepResident 放在 external 库声明末尾,用于要求动态库句柄在调用后保持常驻。例如:

代码块身份:可直接照写示例 代码块说明:Linux 示例,依赖 libc.so.6

writeLn(GetPidResident() > 0);

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

代码块身份:输出片段

1

外部出参与 ABI 写回

当外部函数需要通过指针写回调用方变量时,在参数名前使用 var。这条规则只说明 TSL 声明形态;具体类型、宽度、对齐和调用约定仍必须与原生函数一致。

下面用 libc time 演示 var 外部出参:

代码块身份:可直接照写示例 代码块说明:Linux x86_64 示例,依赖 libc.so.6time_t 按该 ABI 映射为 int64

stamp := 0;
result := UnixTime(stamp);
writeLn(result > 0);
writeLn(stamp = result);

function UnixTime(var stamp: int64): int64; cdecl; external "libc.so.6" name "time";

代码块身份:输出片段

1
1

UnixTime 是脚本别名,time 是 libc 导出符号,二者都不是 TSL builtin。

Linux 动态加载与函数指针生命周期

Linux 可用 dlopen / dlsym 取得原生函数指针,再用 external fp 包装。Windows 对应使用 LoadLibraryA / GetProcAddress。这些名称都是宿主 API,不是 TSL builtin。

代码块身份:可直接照写示例 代码块说明:Linux 示例,依赖 libdl.so.2libc.so.61 为该环境的 RTLD_LAZY

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

代码块身份:输出片段

1
1
1
1

释放顺序是先将包装变量设为 nil,再调用 dlclose

MakeInstance 生成的指针由 DeleteInstance 释放;若已包装为 TSL 函数变量,先将包装变量设为 nil。示例见 syntax-17-008API 签名见 tsl-api-reference

可直接照写示例

最小 external 声明

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

writeLn(Tick64Alias() > 0);

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

结果说明:输出 1name 用于绑定不同名的导出符号。

代码块身份:输出片段

1

Linux 的同类声明:

代码块身份:可直接照写示例 代码块说明:依赖 libc.so.6

writeLn(getpid() > 0);

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

代码块身份:输出片段

1

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

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

writeLn(GetTickCount64() > 0);

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

结果说明:输出 1

也可以省略调用约定或显式写 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
  • Win32 下仍须按原函数的真实调用约定声明

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

结果说明:依次输出 111GetProcAddress(...) 返回的指针可用 external fp 包装。

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;类常量字符串可以用在 DLL 名位置。

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

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

字符串拼接会报 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));
wrapped_func := nil;
writeLn(DeleteInstance(func_ptr));

function Add(a: integer; b: integer): integer;
begin
    return a + b;
end;

结果说明:依次输出 171

线程模式最小正例

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

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

默认生成模板

默认使用 syntax-17-004 的显式调用约定、externalname 骨架。

禁止项

  • external 的 DLL 名直接写成字符串拼接表达式。
  • 省略了外部函数的参数类型或返回类型。
  • MakeInstance(...) 生成的结果默认写成普通函数名直调,而不是先包装或用 ##f(...)
  • 使用与目标平台不匹配的库名或调用约定。