Assign an object's pointer-to-function property in generic function in Delphi - why does this code work?

268 views Asked by At

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:

  1. The Pointer type usage as a generic parameter seems to be restricted.
  2. 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 to PNativeUInt, and assign an acquired function address cast to PNativeUInt 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?

0

There are 0 answers