回到 在 C++ Builder 中使用 Delphi 特性
本主题描述了一些你可能在面对匿名函数时遇到的编程问题-一个 Delphi 的新特性。
说起来,Delphi 是通过接口的一个 Invoke(…) 函数来实现匿名函数的(也被称为函数引用)。
所以一个 Delphi 中的函数引用参数导出到 C++ 就成了一个拥有函数的接口,下面是一个例子:
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;
下面是根据上面生成的 .hpp 文件:
typedef System::DelphiInterface<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 */;
就象上面所看到的,TFilterPredicate 在 C++ 这边被导出为一个接口。C++ 代码中使用一个包含 Invoke() 方法的接口来做为函数引用参数。
下面提供了两种方法来实现这一接口:
- 使用一个函数对象
一个 C++ 模板可以用来封装一个接口并暴露 Invoke() 方法。
下面的 C++ 代码显示了一个可以用于传递C++方法或成员函数做为函数引用给 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); } };
下面的代码演示了如何使用模板来调用 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; }
[俺的评价:依然很不舒服]
- 使用一上 Lamda 表达式
当一个 API 要求一个匿名函数时,你也可以使用一个 Lambda 表达式。Delphi 接口类被更新以自动转换为 lambda 表达式。这个特性仅适用于基于 Clang 的 C++ 编译器。
下面是示例解决方法:
#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;
[好吧,舒服多了,不过遗憾的是 BCC32 明显不支持这种表达方式]