関数のランタイムインポートを可能にするC ++プログラムの設計方法


10

本日は、特定のソフトウェアアーキテクチャを実現するためのC ++の機能についてお伺いします。

もちろん、私は検索を使用しましたが、直接リンクされた回答は見つかりませんでした。

基本的に、私の目標は、ユーザーが任意に構成された物理システム(運転中の自動車など)のモデリングとシミュレーションを行えるプログラムを構築することです。私は、物理モデル(クラス内の関数)のライブラリがあると想定しています。各関数は、基礎となる物理的記述に応じて、いくつかの入力を持ち、いくつかの出力を返す場合があります。たとえば、燃焼エンジンモデル、空力抵抗モデル、ホイールモデルなどです。

ここでのアイデアは、ユーザーが自分のニーズに応じて任意の機能を構成できるようにするフレームワークをユーザーに提供することです。つまり、物理的な動作をマップすることです。フレームワークは、さまざまな機能の出力と入力を接続する機能を提供する必要があります。したがって、フレームワークはコンテナークラスを提供します。これをCOMPONENTと呼び、1つまたは複数のモデルオブジェクト(FUNCTION)を保持できます。これらのコンテナーは、他のコンポーネント(複合パターンを参照)や、関数パラメーター間の接続(CONNECTOR)も保持できます。さらに、コンポーネントクラスは、数学ソルバーなどのいくつかの一般的な数値機能を提供します。

関数の構成は実行時に行う必要があります。最初の例では、ユーザーは、コンポジション構造を定義するXMLをインポートすることでコンポジションをセットアップできる必要があります。その後、GUIを追加することを考えることができます。

ここで理解を深めるために、非常に単純化した例を示します。

<COMPONENT name="Main">
  <COMPONENT name="A">
    <FUNCTION name="A1" path="lib/functionA1" />
  </COMPONENT>
  <COMPONENT name="B">
    <FUNCTION name="B1" path="lib/functionB1" />
    <FUNCTION name="B2" path="lib/functionB2" />
  </COMPONENT>
  <CONNECTIONS>
    <CONNECTOR source="A1" target="B1" />
    <CONNECTOR source="B1" target="B2" />
  </CONNECTIONS>        
</COMPONENT>

私の問題ははるかに一般的であるため、フレームワークの機能について詳しく説明する必要はありません。フレームワークのコード/プログラムがコンパイルされたとき、物理的な問題の説明とユーザー定義関数は不明です。ユーザーが(XMLまたは後でGUIを介して)関数を選択すると、フレームワークは関数情報を読み取る必要があります。つまり、ユーザーに関数を相互接続するオプションを提供するために、入力パラメーターと出力パラメーターの情報を取得する必要があります。

私はリフレクションの原理を知っており、C ++がこの機能を提供していないことを知っています。ただし、「実行時にオブジェクトを構築する」という概念は非常に頻繁に必要になると思います。目標を達成するには、C ++でソフトウェアアーキテクチャをどのように設定すればよいですか?C ++は正しい言語ですか?私は何を見落としていますか?

前もって感謝します!

乾杯、オリバー


C ++には、関数ポインターと関数オブジェクトがあります。すべての関数が実行可能ファイルにコンパイルされていますか、それとも(どのプラットフォームの)動的ライブラリにありますか?
Caleth、2017年

1
電気工学/ [電子設計自動化(EDA)](en.wikipedia.org/wiki/Electronic_design_automation)または機械工学/ コンピューター支援設計(CAD)のいずれかで大学の学位が必要となるという意味で、質問は広すぎます。比較すると、C / C ++ダイナミックライブラリの呼び出しは非常に簡単です。x86のC呼び出し規約を参照してください。ただし、(CPUスタックポインターを介して)スタックとCPUレジスタ値を操作する必要があります。
rwong 2017年

1
関数の動的ロードは、C ++言語ではサポートされていません。プラットフォーム固有のものを確認する必要があります。たとえば、WindowsのC ++コンパイラはWindows DLLをサポートする必要があります。これは、一種のリフレクションをサポートしています。
Simon B

C ++では、コンパイル時にシグネチャ(引数と戻り値の型)が不明な関数を呼び出すのは非常に困難です。そのためには、選択したプラットフォームのアセンブリレベルで関数呼び出しがどのように機能するかを知る必要があります。
Bart van Ingen Schenau 2017年

2
私がこれを解決する方法は、evalコマンドをサポートする任意の言語のインタープリターを作成するc ++コードをコンパイルすることです。C ++を使用して強打の問題を解決しました。:Pそれが十分ではない理由を考えて、質問を更新してください。実際の要件が明確な場合に役立ちます。
candied_orange 2017年

回答:


13

純粋な標準C ++では、「実行時の関数のインポートを許可」することはできません。標準によれば、C ++関数のセットは、プログラムを構成するすべての翻訳単位の和集合から修正されるため、ビルド時(実際にはリンク時)に静的に認識されます。

実際には、ほとんどの場合(組み込みシステムを除く)、C ++プログラムは一部のオペレーティングシステム上で実行されます。概要については、オペレーティングシステム:3つの簡単なピースをお読みください。

いくつかの最新のオペレーティングシステムでは、プラグインを動的にロードできます。POSIXは特にdlopen&を指定していますdlsym。Windowsには何か別のものがありますLoadLibrary(リンクモデルも劣っています。プラグインが提供、提供する、またはプラグインが使用する関数に明示的に注釈を付ける必要があります)。LinuxでBTWを使用すると、実質的にdlopen大量のプラグインを作成できます(私のmanydl.cプログラムを参照してください。十分な忍耐力があれば、100万個近くのプラグインを生成してロードできます)。したがって、XMLの事柄がプラグインのロードを促進する可能性があります。あなたのマルチコンポーネント/マルチコネクタの説明を思い出させるQtのシグナルとスロット(これは必要とmocプリプロセッサを、あなたもそのようなことが必要になる場合があります)。

ほとんどのC ++実装は名前マングリングを使用します。そのため、extern "C"プラグインに関連する関数として宣言します(プラグインで定義しdlsym、メインプログラムからアクセスします)。C ++ dlopen mini HowTo(少なくともLinuxの場合)を読んでください。

ところで、QtPOCOはC ++フレームワークであり、プラグインへのいくつかのポータブルでより高レベルのアプローチを提供しています。また、libffiを使用すると、実行時にのみ署名がわかる関数を呼び出すことができます。

もう1つの可能性は、LuaGuileなどのインタープリターをプログラムに組み込む(またはEmacsのように独自のインタープリターを作成する)ことです。これは強力なアーキテクチャ設計の決定です。あなたは読みたいと思うかもしれLispでは小片プログラミング言語語用論の多くのため。

これらのアプローチのバリアントまたは組み合わせがあります。いくつかのJITコンパイルライブラリ(libgccjitやなどasmjit)を使用できます。実行時に一時ファイルにCおよびC ++コードを生成、それを一時プラグインとしてコンパイルし、そのプラグインを動的にロードすることができます(GCC MELTでこのようなアプローチを使用しました)。

これらすべてのアプローチにおいて、メモリ管理は重要な関心事です(これは「プログラム全体」のプロパティであり、実際にプログラムの「エンベロープ」は「変化」しています)。ガベージコレクションに関する文化が少なくとも必要です。用語については、GCハンドブックをお読みください。多くの場合、(任意で循環参照弱いポインタは予測できない)、参照カウント C ++へのスキームの親愛なるスマートポインタは十分ではないかもしれません。参照してくださいこれを

動的ソフトウェア更新についてもお読みください。

いくつかのプログラミング言語、特にCommon Lisp(およびSmalltalk)は、ランタイムインポート関数のアイデアに、より友好的であることに注意してください。SBCLはCommon Lispの無料のソフトウェア実装であり、REPLの対話ごとにマシンコードにコンパイルされます(マシンコードのガベージコレクションも可能で、コアイメージファイル全体を保存して後で簡単に再起動できます)。


3

明らかに、独自のスタイルのSimulinkまたはLabVIEWタイプのソフトウェアをロールバックしようとしているのですが、XMLコンポーネントが不適切です。

最も基本的には、グラフ指向のデータ構造を探しています。物理モデルは、ノード(コンポーネントと呼ぶ)とエッジ(命名上のコネクタ)で構成されます。

これを行うために言語を強制するメカニズムはなく、リフレクションを使用することもないため、代わりにAPIを作成する必要があり、再生するコンポーネントはいくつかの関数を実装し、APIで規定されたルールに従う必要があります。

各コンポーネントは、次のようなことを行うために一連の関数を実装する必要があります。

  • コンポーネントの名前またはそれに関するその他の詳細を取得します
  • コンポーネントが公開する入力または出力の数を取得します
  • 特定の入力と出力についてコンポーネントに問い合わせる
  • 入力と出力を一緒に接続する
  • その他

そして、それは単にグラフを設定するためのものです。モデルが実際に実行される方法を整理するために定義された追加の関数が必要になります。各関数には特定の名前が付けられ、すべてのコンポーネントにそれらの関数が必要です。コンポーネントに固有のすべてのものは、コンポーネント間で同じ方法で、そのAPIを介して到達可能でなければなりません。

プログラムは、これらの「ユーザー定義関数」を呼び出そうとしてはなりません。代わりに、各コンポーネントで汎用の「計算」関数などを呼び出す必要があり、コンポーネント自体がその関数を呼び出して、その入力を出力に変換します。入力と出力の接続は、その関数の抽象化であり、プログラムが見る必要があるのはそれだけです。

要するに、これは実際にはC ++に固有のものではありませんが、特定の問題のドメインに合わせて、実行時の型情報を実装する必要があります。APIで定義された各関数を使用すると、実行時に呼び出す関数名がわかり、それらの各呼び出しのデータ型がわかります。通常の古い動的ライブラリのロードを使用してそれを実行します。これにはかなりの量の定型文が付属しますが、それは人生の一部にすぎません。

ユーザーが独自のモジュールを提供している場合は、APIをC APIにして、異なるモジュールに異なるコンパイラーを使用できるようにするのが最善ですが、C ++固有の1つの側面に留意してください。

DirectShowは、私が説明したすべてのことを行うAPIであり、調べるのに適した例です。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.