科学プログラムでFortranの関数ポインターを操作する方法


11

Cでの関数ポインターの典型的な使用法を次に示します。Fortranでも同様のことをしたいと思います。私はいくつかのアイデアを持っていますが、そうするための標準的な方法があるかどうかを知りたいです。

ユーザーによって渡された関数ポインターとコンテキストは保存され、後で呼び出されます。

typedef PetscErrorCode (*TSIFunction)(TS,PetscReal,Vec,Vec,Vec,void*);
PetscErrorCode TSSetIFunction(TS ts,Vec res,TSIFunction f,void *ctx);

ユーザーの関数は、さまざまな時間にコンテキストを使用してコールバックされます。

PETScでは、文字列->関数ポインターテーブルも多用します。すべてがプラグインであるため、ユーザーは独自の実装を登録でき、一流です。

#define PCGAMG "gamg"
  PCRegisterDynamic(PCGAMG         ,path,"PCCreate_GAMG",PCCreate_GAMG);

これにより、作成ルーチンが「FList」に登録され、PCSetFromOptions()がこのメソッドを他の選択肢と比較して選択できるようになります。システムが動的ロードをサポートしている場合、PCCreate_GAMGシンボルのコンパイル時の依存関係をスキップしてNULLを渡すだけで、シンボルは実行時に共有ライブラリで検索されます。

これは「工場」を超えた一歩であり、マーティン・ファウラーが「サービスロケーター」と呼ぶものに似た制御デバイスの反転であることに注意してください。

注:これは、Jed Brownとの個人的なやり取りで出てきたもので、Jed Brownがこの質問をしてくれました。私はそれを外部委託し、人々が思い付くことができる答えを見ることにしました。

回答:


5

転送を使用void *して最新のFortranコードをエミュレートする必要はありません。代わりに、すべてのメインストリームFortranコンパイラーでサポートされているISO_C_BINDING組み込みモジュールを使用してください。このモジュールにより、FortranとCの間のインターフェースが非常に簡単になりますが、いくつかの非常に小さな警告があります。関数C_LOCC_FUNLOC関数を使用して、FortranデータとプロシージャへのCポインターをそれぞれ取得できます。

上記のPETSCの例に関して、コンテキストは通常​​、Fortranの派生データ型に相当するユーザー定義構造へのポインターであると想定しています。それはを使用して処理する問題ではないはずC_LOCです。不透明なTSIFunctionハンドルも非常に簡単に処理できます。C c_ptrと同等のISO_C_BINDINGデータ型を使用するだけです。Fortranでvoid *記述されたライブラリはc_ptr、最新のFortranの厳密な型チェックを回避する必要がある場合に使用できます。


ブライアン、はい、その間、ジェッドと私はコールバックのかなりの数の解決策を見つけました。こちらをご覧ください:fortran90.org/src/best-practices.html#type-casting-in-callbacks、type(c_ptr)セクション番号Vです。
OndřejČertík12年

9

あなたの質問にはPETSc固有の言語であると私が推測しているものがたくさんあります(私は慣れていない)ので、ここにはしわがあるかもしれません始めました。

基本的には、プロシージャのインターフェイスを定義する必要があり、このインターフェイスに続く関数にポインタを渡すことができます。次のコードは例を示しています。まず、インターフェイスを定義し、そのインターフェイスに従うユーザーが提供するルーチンを実行するコードの塊の簡単な例を示すモジュールがあります。次は、ユーザーがこのモジュールをどのように使用して実行する機能を定義するかを示すプログラムです。

MODULE xmod

  ABSTRACT INTERFACE
  FUNCTION function_template(n,x) RESULT(y)
      INTEGER, INTENT(in) :: n
      REAL, INTENT(in) :: x(n)
      REAL :: y
  END FUNCTION function_template
  END INTERFACE

CONTAINS

  SUBROUTINE execute_function(n,x,func,y)
    INTEGER, INTENT(in) :: n
    REAL, INTENT(in) :: x(n)
    PROCEDURE(function_template), POINTER :: func
    REAL, INTENT(out) :: y
    y = func(n,x)
  END SUBROUTINE execute_function

END MODULE xmod


PROGRAM xprog

  USE xmod

  REAL :: x(4), y
  PROCEDURE(function_template), POINTER :: func

  x = [1.0, 2.0, 3.0, 4.0]
  func => summation

  CALL execute_function(4,x,func,y)

  PRINT*, y  ! should give 10.0

CONTAINS

  FUNCTION summation(n,x) RESULT(y)
    INTEGER, INTENT(in) :: n
    REAL, INTENT(in) :: x(n)
    REAL :: y
    y = SUM(x)
  END FUNCTION summation

END PROGRAM xprog

答えてくれてありがとう。上記のPETScの例では、関数ポインターもいくつかの内部データ構造に格納されていますが、内部的に保存するのは非常に簡単だと思いますPROCEDURE(function_template), POINTER :: func
オンドレジ・セティク

ポインターは、そのコードのアドレスではなく不透明なオブジェクトであるため、Cとの相互運用性はないことを知っています。PETScでは、これらのCラッパーの関数ポインターのテーブルを維持する必要があります。
マットネプリー

PETScの例には、関数ポインターとコンテキスト(関数が呼び出されたときに返されるプライベートユーザーデータ)の両方が格納されます。コンテキストは非常に重要です。そうしないと、ユーザーはグローバル参照などの恐ろしいことをしてしまいます。に相当するものがないためvoid*、ユーザーはライブラリ関数自体のインターフェイスを記述する必要があります。ライブラリをCで実装する場合はこれで十分ですが、Fortranで実装する場合は、コンパイラがユーザーのINTERFACEと同時にライブラリの「ダミー」INTERFACEを認識しないようにする必要があります。
ジェドブラウン

2
void*Fortranに相当するのはtransferメソッドです。使用例については、こちらをご覧ください。transferメソッド以外の3つのアプローチは、「作業配列」、「特定の派生型void *」ではなく、モジュールにローカルなモジュール変数を使用します。
オンドレジ・セティク

関数を呼び出すためだけに、ユーザーがtransfer無意味なタイプ(character (len=1), allocatable)をいじる必要があるのは残念です。
ジェドブラウン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.