6

C プログラムから呼び出される Fortran DLL があり、プロシージャの 1 つは、C プログラムによって提供されるコールバック関数を定期的に呼び出す必要があります。現在、「単純な」形式でうまく機能していますが、Fortran コード内でより簡単に受け渡しできるように、コールバック ポインターを派生型内に格納できるようにしたいと考えています。これまでのところ、私が試したことは何もないようです。

まず、これが私が現時点で持っているものであり、これはうまくいきます:

C (OK、実際には C++) プログラムで開始すると、コールバックのヘッダー プロトタイプは次のようになります。

typedef void (*fcb)(void *)

Fortran 呼び出しのプロトタイプは次のとおりです。

extern "C" __declspec(dllexport) int fortran_function(int n,
                                                      uchar *image_buffer,
                                                      fcb callback,
                                                      void *object);

実際のコールバック関数は次のとおりです。

void callback(void* pObject)
{
    // Cast the void pointer back to the appropriate class type:
    MyClass *pMyObject = static_cast<MyClass *>(pObject);
    pMyObject -> updateImageInGUI();
}

C++ からの Fortran コードへの呼び出しは次のとおりです。

int error = fortran_function(m_image.size(), m_image.data, callback, this);

ここm_imageで、現在のオブジェクトのメンバー属性である画像データの配列です。C++ が生の画像データを Fortran DLL に渡し、Fortran にそれを処理するように要求します。これには長い時間がかかるため、Fortran は定期的に画像バッファーを更新し、コールバックを呼び出して GUI を更新します。とにかく、Fortran 側に移り、C コールバックのインターフェイスを定義します。

abstract interface
    subroutine c_callback(c_object) bind(c)   
        use, intrinsic :: iso_c_binding
        type(c_ptr), intent(in) :: c_object     
    end subroutine c_callback
end interface

メインの Fortran ルーチンを次のように定義します。

integer(c_int) fortran_function(n, image, callback, c_object)     &
                                bind(c, name='fortran_function')

    integer(c_int), value :: n
    integer(4), intent(inout), dimension(n) :: image
    procedure(c_callback) :: callback
    type(c_ptr), intent(in) :: c_object

メインルーチンのどこかで、サブルーチンを呼び出しますfoo:

call foo(data, callback, c_object)

...ここfooで、次のように定義されます。

subroutine foo(data, callback, c_object)

    type(my_type), intent(inout) :: data
    procedure(c_callback) :: callback
    type(c_ptr), intent(in) :: c_object
    ...
    call callback(c_object)
    ...
end function foo

私が言ったように、これはすべてうまく機能し、長い間そうしてきました.

今、私が試したがうまくいかないことについて:

引数を構造体のフィールドにコピーするだけの素朴なアプローチ

私がしているのは、元の要素を変更せずに構造にコピーすることだけなので、これが機能することを期待しています。C 側でも、主要な Fortran 関数の定義でも、 への抽象インターフェイスでも、何も変更されていませんc_callback。新しい Fortran 派生型を作成するだけです。

type :: callback_data
    procedure(c_callback), pointer, nopass :: callback => null()
    type(c_ptr) :: c_object
end type callback_data

次に、メイン関数で、C アプリケーションから受け取った値を入力します。

data%callback_data%callback => callback
data%callback_data%c_object = c_object
call foo(data)

サブルーチン foo が少し変更され、構造内でコールバックと C オブジェクトを検索するようになりました。

subroutine foo(data)
    type(my_augmented_type), intent(inout) :: data
    ...
    call data%callback_data%callback(data%callback_data%c_object)
    ...
end function foo

これは、「アクセス違反読み取り場所 0xffffffffffffffff」の呼び出しで失敗します。

より多くの iso_c_binding 機能を使用した洗練されたアプローチ

C 側では何も変更しませんが、メイン関数の Fortran 側を次のようにコールバックを受け取るように変更しますc_funptr

integer(c_int) fortran_function(n, image, callback, c_object)     &
                                bind(c, name='fortran_function')

    integer(c_int), value :: n
    integer(4), intent(inout), dimension(n) :: image
    type(c_funptr), intent(in) :: callback
    type(c_ptr), intent(in) :: c_object

抽象インターフェイスをsubroutine c_callback前と同じように定義しますが、その部分を残したりbind(c)、省略したりして実験しました。サブルーチンを呼び出すメイン関数内のコードは次のfooとおりです。

call c_f_procpointer(callback, data%callback_data%callback)
data%callback_data%c_object = c_object
call foo(data)

...サブルーチン foo 自体は、前の例のように定義されたままです。

残念ながら、これは前の例とまったく同じように失敗します。

ここで達成しようとしていることを達成するための正しい構文があると思います。アドバイスをいただければ幸いです。

4

1 に答える 1

4

BIND(C)VALUE 引数を持たない属性を持つ Fortran プロシージャーの仮引数は、C 側ではポインター パラメーターと同等です (これは、参照によって渡されるものの通常の Fortran 規則と広く一致しています)。したがって、Fortran 側にINTEGER(C_INT) :: a(value 属性がない) 場合、C 側では と同等int *aです。

おそらくそれは明らかですが、驚くべき結果をもたらしますTYPE(C_PTR) :: p.C_PTRvoid **pはポインターであるため、値なしで渡されたC_PTRはポインターへのポインターです。これを考えると、コールバックのインターフェイスはアウトです (追加する必要がありますVALUE)。

Fortran の関数への C ポインター (これは、関数名が括弧を除いたものです) に対する型の意味での相互運用可能なアナログは、 aTYPE(C_FUNPTR)です。VALUE 属性がない場合と同じ考慮事項がC_PTR適用されます。宣言された引数TYPE(C_FUNPTR) :: fは、関数へのポインターへのポインターです。これと Fortran の C 側の呼び出しを考えると、関数ポインタに対応する引数にはVALUE属性が必要です。

Fortran プロシージャ ポインタがたまたま機能するという事実は、C 関数ポインタと Fortran プロシージャ ポインタの基本的な実装と、Fortran プロシージャ ポインタが渡される方法の単なる (それほど驚くべきことではない) 偶然の一致です。

Fortran プロシージャーには、おそらく次のようなインターフェースが必要です。

integer(c_int) fortran_function(n, image, callback, c_object)     &
                                bind(c, name='fortran_function')

  integer(c_int), value :: n
  integer(c_signed_char), intent(inout), dimension(n) :: image
  type(c_funptr), intent(in), value :: callback
  type(c_ptr), intent(in), value :: c_object

(元のコードでの画像配列の宣言は間違っているようです-おそらく上記は適切ですが、おそらくそうではありません)

また、C コールバックのインターフェイスの宣言には、次のインターフェイスが必要です。

abstract interface
  subroutine c_callback(c_object) bind(c)   
    use, intrinsic :: iso_c_binding
    implicit none
    type(c_ptr), intent(in), value :: c_object     
  end subroutine c_callback
end interface

(過去数か月にわたって Intel フォーラムで議論されたように (どこに行ったことがありますか?)、現在の ifort は および の処理に問題がある可能性がC_PTRありVALUEます。)

于 2014-01-17T10:18:20.887 に答える