3

私の主な質問は、なぜ配列がそのような奇妙なことをするのか、そして「クリーンな」方法で次のことを行う方法があるかどうかです。

私は現在、おおよそ以下のコードのように、foo.cFortranプログラムとインターフェースするCプログラムを持っていbar.f90ます。dlopen/dlsym

foo.c:

#include <dlfcn.h>
#include <stdio.h>

int main()
{
int i, k = 4;
double arr[k];
char * e;

void * bar = dlopen("Code/Test/bar.so", RTLD_NOW | RTLD_LOCAL);

void (*allocArray)(int*);
*(void **)(&allocArray) = dlsym(bar, "__bar_MOD_allocarray");
void (*fillArray)(double*);
*(void **)(&fillArray) = dlsym(bar, "__bar_MOD_fillarray");
void (*printArray)(void);
*(void **)(&printArray) = dlsym(bar, "__bar_MOD_printarray");
double *a = (double*)dlsym(bar, "__bar_MOD_a");

for(i = 0; i < k; i++)
    arr[i] = i * 3.14;

(*allocArray)(&k);
(*fillArray)(arr);
(*printArray)();
for(i = 0; i < 4; i++)
    printf("%f ", a[i]);
printf("\n");

return 0;
}

bar.f90:

module bar

integer, parameter :: pa = selected_real_kind(15, 307)
real(pa), dimension(:), allocatable :: a
integer :: as

contains

subroutine allocArray(asize)
    integer, intent(in) :: asize

    as = asize
    allocate(a(asize))

    return
end subroutine

subroutine fillArray(values)
    real(pa), dimension(as), intent(in) :: values

    a = values
    return
end subroutine

subroutine printArray()
    write(*,*) a
    return
end subroutine

end module

主な利回りを実行します

0.0000000000000000        3.1400000000000001        6.2800000000000002        9.4199999999999999     
0.000000 -nan 0.000000 0.000000 

これは、Fortranが配列を正しく割り当て、指定された値を正しく格納していることを示していますが、dlsymを介してアクセスできなくなりました(そのデータで作業するとsegfaultが発生します)。固定サイズの配列でもこれを試しましたが、結果は同じです。

誰かがこの行動の理由を知っていますか?個人的には、双方向で動作するか、まったく動作しないことを期待していました。この「FortranはC配列を受け入れますが、その逆はありません」ということで、この方法でCから配列にアクセスする際に基本的な間違いがあったのではないかと思います。

もう1つの(そしてさらに重要な)質問は、これらの「正しい方法」のような配列アクセスをどのように行うかです。現在、「Fortran as .so」インターフェースに固執することが良い方法であるかどうかさえわかりません。この場合、混合プログラミングを試みることも可能だと思います。それにもかかわらず、配列の問題は残っています-これはISO Cバインディングを使用して何らかの方法で解決できることを読みましたが、まだその方法を理解できませんでした(Fortranではあまり作業していませんが、特に上記のバインディングでは作業していません) 、この問題についてのヘルプをいただければ幸いです。

編集:

さて、私はISO Cバインディングをもう少し読んで、ここで非常に便利なアプローチを見つけました。を使用C_LOCすると、Fortran構造体へのCポインターを取得できます。残念ながら、配列へのポインタはポインタへのポインタのようであり、C配列として扱う前に、Cコードで逆参照する必要があります。

編集:

少なくとも大部分は、ウラジミールFが指摘したように、Cバインディングを使用してプログラムを動作させることができました。CファイルとFortranファイルがリンクされたので、少なくともFortranの部分ではlibdlインターフェイスを回避できます。動的Cライブラリをロードし、そこにあるシンボルの1つへの関数ポインターを取得して、それを渡す必要があります。後で計算の一部としてその関数を呼び出すFortranへの関数ポインターとして。上記の関数はdouble*s [arrays]を期待しているので、奇妙なことに、C_LOCを使用してFortran配列を渡すことができませんでした。C_LOC(array)またC_LOC(array(1))、正しいポインターをC関数に戻すこともできませんでした。array(1)しかし、トリックをしました。悲しいことに、これはこれを行うための「最もクリーンな」方法ではありません。誰かが私にこれを使用してこれを行う方法のヒントを得た場合C_LOC機能、それは素晴らしいでしょう。それにもかかわらず、私はそれがより安全な解決策であると考えるので、ウラジミールFの答えを受け入れます。

4

2 に答える 2

4

多くのFortranコンパイラーは、配列記述子と呼ばれるものを内部的に使用します。これは、配列の形状(つまり、各次元のサイズと範囲、および実際のデータへのポインター)を保持する構造体です。これにより、想定される形状の配列引数、配列ポインター、割り当て可能な配列などの実装が機能します。シンボルを介してアクセスしているの__bar_MOD_aは、割り当て可能な配列の記述子であり、そのデータではありません。

配列記述子はコンパイラ固有であり、特定の記述子形式に依存するコードは移植性がありません。記述子の例:

それらでさえ、それらのコンパイラのいくつかのバージョンに固有であることに注意してください。たとえば、Intelは、現在の記述子形式はIntelFortran7.0で使用されている形式と互換性がないと述べています。

両方の記述子を見ると、それらがより大きく類似しており、最初の要素が配列データへのポインターであることがわかります。double **したがって、次の代わりにを使用してデータを簡単に読み取ることができますdouble *

double **a_descr = (double**)dlsym(bar, "__bar_MOD_a");
...
for(i = 0; i < 4; i++)
    printf("%f ", (*a_descr)[i]);

繰り返しになりますが、これらの記述子の形式は将来変更される可能性があるため、これは移植性がありません(ただし、データポインターが記述子の先頭以外の場所に移動されることはないと思います)。すべての記述子フォーマットを統一しようとする仕様案がありますが、それがさまざまなコンパイラベンダーによっていつどのように採用されるかは明確ではありません。

編集:C_LOC()モジュールから使用するアクセサ関数を使用しISO_C_BINDINGて、割り当て可能な配列へのポインタを移植可能に取得する方法は次のとおりです。

Fortranコード:

module bar
  use iso_c_binding
  ...
  ! Note that the array should be a pointer target
  real(pa), dimension(:), allocatable, target :: a
  ...
contains
  ...

  function getArrayPtr() result(cptr)
    type(c_ptr) :: cptr

    cptr = c_loc(a)
  end function

end module

Cコード:

...
void * (*getArrayPtr)(void);
*(void **)(&getArrayPtr) = dlsym(bar, "__bar_MOD_getarrayptr");
...
double *a = (*getArrayPtr)();
for(i = 0; i < 4; i++)
    printf("%f ", a[i]);
...

結果:

$ ./prog.x
   0.0000000000000000        3.1400000000000001        6.2800000000000002
 9.4199999999999999
0.000000 3.140000 6.280000 9.420000
于 2012-08-13T14:05:17.430 に答える
3

私の意見では、Fortranライブラリのグローバルデータにアクセスしようとするのは良い習慣ではありません。COMMONブロックを使用して実行できますが、それらは悪であり、静的なサイズの配列を必要とします。一般的に、ストレージの関連付けは悪いことです。

モジュールシンボルに「__bar_MOD_a」としてアクセスしないでください。これらはコンパイラ固有であり、直接使用するためのものではありません。関数とサブルーチンを使用してpoitersを渡します。

配列をサブルーチン引数として渡します。配列をCで割り当てて、Fortranに渡すこともできます。また、配列の最初の要素へのポインターを取得することもできます。配列へのCポインタを提供します。

私の解決策は、.soを使わずに簡単にするために、次のように追加するのは簡単です。

bar.f90

module bar
 use iso_C_binding

implicit none

integer, parameter :: pa = selected_real_kind(15, 307)

real(pa), dimension(:), allocatable,target :: a
integer :: as

contains

subroutine allocArray(asize,ptr) bind(C,name="allocArray")
    integer, intent(in) :: asize
    type(c_ptr),intent(out) :: ptr

    as = asize
    allocate(a(asize))

    ptr = c_loc(a(1))
end subroutine

subroutine fillArray(values) bind(C,name="fillArray")
    real(pa), dimension(as), intent(in) :: values

    a = values
end subroutine

subroutine printArray()  bind(C,name="printArray")

    write(*,*) a
end subroutine

end module

main.c

#include <dlfcn.h>
#include <stdio.h>

int main()
{
int i, k = 4;
double arr[k];
char * e;
double *a;
void allocArray(int*,double**);
void fillArray(double*);
void allocArray();


for(i = 0; i < k; i++)
    arr[i] = i * 3.14;

allocArray(&k,&a);
fillArray(arr);
printArray();
for(i = 0; i < 4; i++)
    printf("%f ", a[i]);
printf("\n");

return 0;
}

コンパイルして実行します。

gcc -c -g main.c

gfortran -c -g -fcheck=all bar.f90

gfortran main.o bar.o

./a.out
0.0000000000000000        3.1400000000000001        6.2800000000000002        9.4199999999999999     
0.000000 3.140000 6.280000 9.420000 

注:Fortranサブルーチンで返される理由はなく、コードがわかりにくくなるだけです。

于 2012-08-13T15:12:16.390 に答える