I was not programming in Delphi Pascal for over 20 years. The current challenge is to wrap a C-shared library API in Pascal in more or less OOP way.
The pattern I used is lazy binding at runtime, i.e. loading a library and linking to the functions is performed on demand. A Pascal side API is declared as an object with many pointer-to-function members of specified types - each member is initialized to GetProcAddress
's result with proper post-checking.
I decided to wrap the most annoying and repetitive part of code, i.e. get address by name, assign, check result, retrieve an error message, and report result in a Pascal generic. What could easily be done with a C++ template emerged a challenge of its own in Pascal:
- The
Pointer
type usage as a generic parameter seems to be restricted. - There is no easy way to cast simple (not object member) function pointers to/from untyped pointers.
The only working solution I came to is the following:
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;
And it's working when used as follows:
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;
I hate to say though: I do not fully understand why it's working. And I do not like the code I do not fully understand.
- The pointer to an object member variable is passed as first parameter.
- The type of the parameter, as I see it, should be pointer to pointer to function.
- Later I planned to dereference it and assign an aquired function address to its value cast to
NativeUInt
. Though, the only way it worked was to cast pointer-to-pointer to function toPNativeUInt
, and assign an acquired function address cast toPNativeUInt
back to it. From my understanding it should have changed a value of a pointer-to-pointer, not a pointer it points to. But somehow, despite obvious, it did change the pointed-to value.
Could anyone with modern Pascal knowledge explain to me what is going on in my code?