1

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?

AmigoJack
  • 5,234
  • 1
  • 15
  • 31
  • Do you want OOP in Pascal? Or is the library to link to OOP with function exports that are in fact methods? – AmigoJack Oct 08 '21 at 10:55
  • Did you consider wrapping up the exported functions as an interface? Delphi has supported them since v.2. See e.g. http://www.delphibasics.co.uk/Article.asp?Name=Interface. – MartynA Oct 08 '21 at 11:17
  • The shared library is my own modular cross-platform framework, written in C++. As far as Embarcadero's C++ team is not able to provide the same platform coverage, as their Delphi guys for many years, I had to create minimal C API wrapper on top of C++ reflected functionality, to be able to bridge framework to other languages, namely, Delphi with its great FMX. Being able to compile framework library with normal C++ toolchains, like GCC, Clang, etc for all platforms, I'm now on the way to integrate its functionality into pascal applications. – Всеволод Громов Oct 08 '21 at 11:57
  • There are three questions here and it's meant to be one question at a time. So that's a little off-putting. But also it seems kind of vague. What I don't understand is how you wrote the code without understanding it. – David Heffernan Oct 08 '21 at 12:25
  • 2
    I'm at a bit of a loss as to what this code is doing. But you shouldn't need to resort to Generics just to load function pointers at runtime. For instance, Indy has been loading system DLL functions dynamically for years without using Generics. Have a look at its `IdWinsock2`, `IdSSLOpenSSLHeaders`, and `IdZlibHeaders` units for examples. Also, don't forget that Delphi itself has its own [delay-loading feature](https://docwiki.embarcadero.com/RADStudio/en/Libraries_and_Packages_(Delphi)) via the `delayed` keyword on DLL function declarations – Remy Lebeau Oct 08 '21 at 16:13

0 Answers0