11 KiB
TSL 外部调用与线程
本页说明动态库声明、原生函数指针、C 回调和线程调用。MakeInstance、DeleteInstance 是 builtin API;external、name、KeepResident、cdecl、stdcall 和外部参数声明是语法。
本篇职责
回答外部动态库、函数指针、C 回调和线程调用的写法。LoadLibraryA、GetProcAddress、dlopen、dlsym 是宿主 API;TSL_ScriptGo 等 TSSVRAPI.h 名称是 C/C++ SDK API,均不进入 TSL builtin 索引。
核心规则
- 外部函数声明的文档明确形态是
function Name(...): Type; stdcall|cdecl; external "dll" [name "symbol"];。 - 当 TSL 函数名和 DLL 导出名一致时,
name "symbol"可以省略。 - Win32 下调用约定必须与宿主函数一致;Linux 与 Win64 下
stdcall和cdecl使用相同调用模式。 - 无返回值的外部接口可声明为
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 使用 DLL,Linux 使用对应
.so。 - 文档列出的基础外部类型包括
Integer、String、Double、Single、Boolean、Pointer、PChar、Int64、Short和Byte,未列出的类型按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.6;time_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.2 和 libc.so.6;1 为该环境的 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-008,API 签名见 tsl-api-reference。
可直接照写示例
最小 external 声明
代码块身份:可直接照写示例
writeLn(Tick64Alias() > 0);
function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCount64";
结果说明:输出 1;name 用于绑定不同名的导出符号。
代码块身份:输出片段
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";
结果说明:
- 依次输出
1、1 - 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";
结果说明:依次输出 1、1、1;GetProcAddress(...) 返回的指针可用 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;
结果说明:依次输出 1、7、1。
线程模式最小正例
代码块身份:可直接照写示例
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。
默认生成模板
默认使用 syntax-17-004 的显式调用约定、external 和 name 骨架。
禁止项
- 把
external的 DLL 名直接写成字符串拼接表达式。 - 省略了外部函数的参数类型或返回类型。
- 把
MakeInstance(...)生成的结果默认写成普通函数名直调,而不是先包装或用##f(...)。 - 使用与目标平台不匹配的库名或调用约定。