C ++ライブラリのC ++ラッパーを作成するツール、Beautiful Capiの紹介

Beautiful Capiは、Cの外部インターフェイスを備えたC ++での動的ライブラリの作成を容易にするツールです。 このツールは、このCインターフェイスのC ++ラッパーも生成します。 Beautiful CapiはPython 3で書かれています。







単一のABI標準がない場合のC ++ライブラリ開発者の主な頭痛。 コンパイラごとに異なるABI、命名規則、例外キャッチスキームなどがあります。 したがって、C ++プログラマーは毎回ライブラリのソースコードを取得し、適切なコンパイラーを使用してコンパイルする必要があります。







ライブラリが人気があり、彼女にとって良い叔父がほとんどのC ++コンパイラのバイナリをすでに投稿しているのは良いことです。 繰り返しますが、ほとんどのコンパイラーです。 多くのC ++コンパイラがあり、互換性のないABIを備えた同じコンパイラの異なるバージョンを考慮すると、既にビルドされたライブラリが機能しない可能性が非常に高くなります。 さらに、これにバイナリ互換性に影響するさまざまなコンパイラ設定を追加します。







Beautiful Capiツールは、この問題に対する独自のレシピを提供します。 動的ライブラリの外部にはCインターフェイスのみが必要です。 int32_tuint8_tなど、サイズが厳密に固定されている型のみをインターフェースで使用する必要があります。 また、関数を呼び出す規則を忘れないでください。 Cプログラマーがライブラリを使用することに決めた場合、Cインターフェイスを直接使用できます。 C ++プログラマーが使用するために、Beautiful Capiが自動的に生成するC ++ラッパーがあります。 C#(.NET)、Java、Pythonなど、他の言語のラッパーを生成する計画があります。







Beautiful Capiツールは、同様のSwigシステムとは異なり、C ++ソースコードを解析しません。 ユーザー自身が、パブリックライブラリAPIの説明をツールに提供する必要があります。 パブリックライブラリAPIはXML形式で定義され、名前空間ごとにグループ化されたC ++クラス、メソッド、単純な関数、列挙子の記述のセットです。 パブリックAPIを記述するXML以外の形式を追加する予定です。







もう1つの大きな問題は、ヒープメモリマネージャです。 C ++ライブラリで使用されるこのようなマネージャーの実装は、クライアントアプリケーションでの実装とは異なる場合があります。 その結果、クライアントアプリケーションのC ++ライブラリに割り当てられたメモリブロックを解放しようとすると、このアプリケーションがクラッシュします。 このツールは、C ++ライブラリのヒープマネージャーを使用して、クラスの各インスタンスの作成と削除を保証します。







Beautiful Capiプロジェクトはオープンソースであり、GNU GPLの下でライセンスされています。 ただし、Beautiful Capiは実際にはgitまたはその他の外部ツールであるため、これはプロプライエタリプロジェクトでの使用を停止しません。







こんにちは世界!



最初のコンパイラに依存しないC ++ライブラリの例を考えてみましょう。 HelloWorld名前空間にPrinterImplクラスがあります。 このクラスには、大切なアクションを実行する単一のShow()メソッドがあります。







実際、PrinterImplクラス自体:







namespace HelloWorld { class PrinterImpl { public: void Show() const { std::cout << "Hello Beautiful World!" << std::endl; } }; }
      
      





Beautiful Capiツールの場合、次のXMLファイルを作成する必要があります。







 <?xml version="1.0" encoding="utf-8" ?> <hello_world:api xmlns:hello_world="http://gkmsoft.ru/beautifulcapi" project_name="HelloWorld"> <namespace name="HelloWorld"> <class name="Printer" implementation_class_name="HelloWorld::PrinterImpl" implementation_class_header="PrinterImpl.h" lifecycle="copy_semantic"> <constructor name="Default"/> <method name="Show" const="true"/> </class> </namespace> </hello_world:api>
      
      





ここですべてが明らかになることを願っています 実装クラスがPrinterImplと呼ばれるという事実にもかかわらず、私たちはそのパブリック名を単にPrinterと呼ぶことにしました。 implementation_class_nameおよびimplementation_class_header属性は、実装クラスの名前と、その説明が使用可能なヘッダーファイルの名前を指定します。 ライフサイクル属性は、オブジェクトのライフサイクルシナリオを指定します 。 事前に、3種類のライフサイクルセマンティクス、 copy_semanticreference_counted、およびraw_pointer_semanticが現在サポートされていると言います。 コピーセマンティクスは、対応するC ++ラッパークラスがコピーされるたびに実装クラスがコピーされることを意味します。







どのC関数が生成されるかを示します。 Beautiful Capiツールを使用して生成されたAutoGenWrap.cppファイルをライブラリに含める必要があります。







 void* hello_world_printer_default() { return new HelloWorld::PrinterImpl(); } void hello_world_printer_show_const(void* object_pointer) { const HelloWorld::PrinterImpl* self = static_cast<HelloWorld::PrinterImpl*>(object_pointer); self->Show(); } void* hello_world_printer_copy(void* object_pointer) { return new HelloWorld::PrinterImpl(*static_cast<HelloWorld::PrinterImpl*>(object_pointer)); } void hello_world_printer_delete(void* object_pointer) { delete static_cast<HelloWorld::PrinterImpl*>(object_pointer); }
      
      





説明のために、すべての関数呼び出し規約とextern "C"キーワードは省略されています。 ご覧のとおり、暗黙の引数はvoid *型の最初の引数です。







hello_world_printer_default関数には引数がありません;ヒープに実装オブジェクトを作成し、それへのポインターをvoidへのポインターとして返します。 hello_world_printer_show_const関数自体は、単にShow()メソッドを呼び出します。 hello_world_printer_copy関数は実装オブジェクトをコピーし、そのコピーへのポインターを返します。 hello_world_printer_delete関数は、実装オブジェクトを削除します。







生成されたC ++ラッパー、 Printer.hファイル:







 namespace HelloWorld { class Printer { public: Printer() { SetObject(hello_world_printer_default()); } void Show() const { hello_world_printer_show_const(GetRawPointer()); } Printer(const Printer& other) { if (other.GetRawPointer()) { SetObject(hello_world_printer_copy(other.GetRawPointer())); } else { SetObject(0); } } ~Printer() { if (GetRawPointer()) { hello_world_printer_delete(GetRawPointer()); SetObject(0); } } void* GetRawPointer() const { return mObject; } protected: void SetObject(void* object_pointer) { mObject = object_pointer; } void* mObject; }; }
      
      





クライアント側コード:







 #include "HelloWorld.h" int main() { HelloWorld::Printer printer; printer.Show(); return EXIT_SUCCESS; }
      
      





プログラムの結果:







 Hello Beautiful World!
      
      





真のクロスコンパイラ



Microsoft WindowsオペレーティングシステムでMicrosoft Visual C ++コンパイラを使用して(だけでなく)動的ライブラリを作成する1つの機能に注目する価値があります。 デフォルトでは、動的ライブラリsome_name.dllに対して、静的ライブラリsome_name.libが作成されますが 、これはすでにライブラリクライアントにリンクしています。 しかし問題は、互換性のない.libファイル形式が2つあることです。1つはMicrosoftからのもので、もう1つは現在亡くなっているBorlandからのものです。 また、たとえばMinGW GCCコンパイラを使用してクライアントで動的ライブラリを使用する場合は、サードパーティのユーティリティを使用して.libファイルを変換するか、 .libファイルの使用を拒否する必要があります。 幸いなことに、Beautiful Capiツールを使用すると、動的ローダーを使用できます。これにより、静的ライブラリを完全に破棄できます。







 #include <iostream> #include <cstdlib> #define HELLOWORLD_CAPI_USE_DYNAMIC_LOADER #define HELLOWORLD_CAPI_DEFINE_FUNCTION_POINTERS #include "HelloWorld.h" int main() { try { #ifdef _WIN32 HelloWorld::Initialization module_init("hello_world.dll"); #elif __APPLE__ HelloWorld::Initialization module_init("libhello_world.dylib"); #else HelloWorld::Initialization module_init("libhello_world.so"); #endif HelloWorld::Printer printer; printer.Show(); } catch (const std::exception& exception) { std::cout << "Exception: " << exception.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
      
      





hello_world.dll動的ライブラリ使用できない場合、 std :: runtime_errorタイプの例外がエラーの説明とともにスローされます。







Cygwin Clangコンパイラを使用してこの例をテストしました。動的ライブラリバイナリは、Microsoft Visual C ++ 2015コンパイラを使用して作成されました。







おわりに



この短い記事では、例外をキャッチして処理する組織、クライアント側インターフェイスの実装、C ++テンプレートのサポート、Beautiful Capiツールに実装されている他の多くの重要な側面については考慮しませんでした。







また、Beautiful Capiおよびタスクを実行できる他のツール、つまりSwigラッパー生成システム、Microsoft COMテクノロジー、およびそのクロスプラットフォームアナログに基づいたソリューションの比較分析も提供されていません。







ただし、この記事では、Beautiful Capiツールとそれが解決するタスクの概要を説明します。 レビューの量と質は、著者が続編を書くか、この記事を補足することを奨励します。







参照資料



  1. 美しいCapiツール
  2. バイナリアプリケーションインターフェイス、 ABI
  3. Swigプログラムとライブラリをリンクするための無料ツール



All Articles