How to Handle Delphi Anonymous Methods in C++

From RAD Studio
Jump to: navigation, search

Go Up to Handling Delphi Features in C++Builder Index


This topic describes some programming issues that you might encounter when dealing with anonymous methods, one of Delphi's newer features.

Under the cover, Delphi implements anonymous methods types (also known as method references) via an interface that implements an Invoke(...) method.

So a method that takes a method reference parameter in Delphi is exposed to C++ as a method that takes an interface. Here's an example:

 interface

    type
      TFilterPredicate = reference to function(const Path: string;
          const SearchRec: TSearchRec): Boolean;
    // ...
    class function GetFiles(const Path: string;
        const Predicate: TFilterPredicate): TStringDynArray;
        overload; inline; static;

The following is the .hpp file generated for the above:

  typedef System::{{Delphi}}Interface<TFilterPredicate> _di_TFilterPredicate;
  __interface TFilterPredicate  : public System::IInterface 
  {
    virtual bool __fastcall Invoke(const System::UnicodeString Path, const System::Sysutils::TSearchRec &SearchRec) = 0 ;
  };
  // .. 
  static System::TStringDynArray __fastcall GetFiles(const System::UnicodeString Path, const _di_TFilterPredicate Predicate)/* overload */;

As shown above, TFilterPredicate is exposed as an interface on the C++ side. C++ code that seeks to specify a function or member function as a method reference parameter has to wrap the latter behind an interface that exposes the Invoke() method.

The following section describes two ways to implement that interface.

Using a Functor (Function Object)

A C++ template can be used to encapsulate an interface that exposes an Invoke() method.

The following C++ code shows an example of a template that can be used to pass C++ methods or member functions as method references to Delphi:

#include <System.hpp>
 
enum _DummyType{};      // Parameter used as default
 
template <typename INTF, // Interface with Invoke
          typename F,    // Functor type
          typename R,    // Return type
          typename P1 = _DummyType,  // Param #1
          typename P2 = _DummyType,  // Param #2
          typename P3 = _DummyType,  // Param #3
          typename P4 = _DummyType,  // Param #4
          typename P5 = _DummyType>  // Param #5
class TMethodRef : public TCppInterfacedObject<INTF>
{
private:
  F callback;
public:
  TMethodRef(F _callback) : callback(_callback) {}
  INTFOBJECT_IMPL_IUNKNOWN(TInterfacedObject);
 
  R __fastcall Invoke(P1 p1) {
    return callback(p1);
  }
  R __fastcall Invoke(P1 p1, P2 p2) {
    return callback(p1, p2);
  }
  R __fastcall Invoke(P1 p1, P2 p2, P3 p3) {
    return callback(p1, p2, p3);
  }
  R __fastcall Invoke(P1 p1, P2 p2, P3 p3, P4 p4) {
    return callback(p1, p2, p3, p4);
  }
  R __fastcall Invoke(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) {
    return callback(p1, p2, p3, p4, p5);
  }
};

The following code shows how to use the template shown above to call GetFiles:

#include <System.IOUtils.hpp>
#include <iostream>
 
struct Filter {
  Filter(String _ext) : ext(_ext)
  {}
  bool operator()(const System::UnicodeString, const System::Sysutils::TSearchRec &SearchRec) {
    return ExtractFileExt(SearchRec.Name) == ext;
  }
  String ext;
};
 
int main()
{
  typedef TMethodRef<TDirectory::TFilterPredicate,
                     Filter,
                     bool,
                     const System::UnicodeString,
                     const System::Sysutils::TSearchRec&> MyMethRef;
  String ext(".cpp");                   
  TStringDynArray files = TDirectory::GetFiles(TDirectory::GetCurrentDirectory(),
                          TDirectory::_di_TFilterPredicate(new MyMethRef(Filter(ext))));
  std::cout << "Found " << files.Length 
            << " files with ext: '" << AnsiString(ext).c_str() << "'\n";
  for (int i=0; i<files.Length; ++i)
    std::cout << AnsiString(files[i]).c_str() << std::endl;
}

Using a Lambda Expression

You can also use a lambda wherever an API expects an anonymous method. The DelphiInterface class has been updated to auto-convert to a lambda. This feature can only be used with the Clang-enhanced C++ Compilers.

The following example illustrates:

#include <System.hpp>
#include <System.IOUtils.hpp>
#include <iostream>
 
int main()
{
  String ext(".cpp");                   
  TStringDynArray files = TDirectory::GetFiles(TDirectory::GetCurrentDirectory(),
                          [ext](const String Path, const System::Sysutils::TSearchRec &SearchRec) -> bool
                          {
                            return ExtractFileExt(SearchRec.Name) == ext;
                          });
  std::cout << "Found " << files.Length 
            << " files with ext: '" << AnsiString(ext).c_str() << "'\n";
  for (int i=0; i<files.Length; ++i)
    std::cout << AnsiString(files[i]).c_str() << std::endl;
}

See Also