我已经有 20 多年没有使用 Delphi Pascal 编程了。当前的挑战是以或多或少的 OOP 方式在 Pascal 中包装 C 共享库 API。
我使用的模式是运行时的惰性绑定,即加载库和链接到函数是按需执行的。Pascal 端 API 被声明为具有许多指定类型的函数指针成员的对象 - 每个成员都GetProcAddress
通过适当的后检查初始化为 's 结果。
我决定包装代码中最烦人和重复的部分,即按名称获取地址、分配、检查结果、检索错误消息并在 Pascal 泛型中报告结果。使用 C++ 模板可以轻松完成的事情在 Pascal 中出现了自己的挑战:
- 作为泛型参数的
Pointer
类型使用似乎受到限制。 - 没有简单的方法可以将简单(不是对象成员)函数指针转换为无类型指针或从无类型指针转换。
我找到的唯一可行的解决方案如下:
type PfnMemberT<pfnT> = record
type PpfnT = ^pfnT;
public
class function assign(var member : PpfnT; const name : UnicodeString; var error : UnicodeString) : Boolean; static;
end;
class function PfnMemberT<pfnT>.assign(var member : PpfnT; const name : UnicodeString; var error : UnicodeString) : Boolean;
begin
Result := false;
Assert(EsCore.instance.isLoaded); {EsCore.instance is a pointer to a library loader singleton}
var pfnAddr : NativeUInt := NativeUInt(
GetProcAddress(
EsCore.instance.hlib,
PWideChar(name)
)
);
if 0 = pfnAddr then begin
error := 'Error binding function ''' + name
{$IFNDEF POSIX}
+ ''': ''' + SysErrorMessage(GetLastError()) + ''''
{$ENDIF}
;
end else begin
Result := true;
PNativeUInt(member) := PNativeUInt(pfnAddr);
end;
end;
它在如下使用时起作用:
if
not PfnMemberT<esResultStringGetPfn>.assign(
PfnMemberT<esResultStringGetPfn>.PpfnT( @m_esResultStringGet ),
'esResultStringGet',
m_errmsg
)
then
exit;
if
not PfnMemberT<esResultSeverityGetPfn>.assign(
PfnMemberT<esResultSeverityGetPfn>.PpfnT( @m_esResultSeverityGet ),
'esResultSeverityGet',
m_errmsg
)
then
exit;
if
not PfnMemberT<esResultFacilityPfn>.assign(
PfnMemberT<esResultFacilityPfn>.PpfnT( @m_esResultFacilityGet ),
'esResultFacilityGet',
m_errmsg
)
then
exit;
我不想说:我不完全理解它为什么起作用。而且我不喜欢我不完全理解的代码。
- 指向对象成员变量的指针作为第一个参数传递。
- 在我看来,参数的类型应该是指向函数的指针。
- 后来我计划取消对它的引用并将一个获取的函数地址分配给它的值转换为
NativeUInt
. 不过,它起作用的唯一方法是将指针指向函数的指针转换为PNativeUInt
,并将获取的函数地址转换为PNativeUInt
返回给它。根据我的理解,它应该更改了指向指针的值,而不是它指向的指针。但不知何故,尽管很明显,它确实改变了指向的价值。
任何具有现代 Pascal 知识的人都可以向我解释我的代码中发生了什么吗?