web-dev-qa-db-ja.com

emscriptenコンパイル済みコードへの配列ポインタの受け渡しを処理する方法は?

私はEmscripten/javascriptとこのOverstackコミュニティに不慣れです。私の状況がすでに解決されている場合は、事前に謝罪します。

Windows 7環境から、emccを使用して、配列を受け入れて変更する単純なcプログラムをコンパイルしました(以下を参照)。

double* displayArray(double *doubleVector) {

   for (int cnt = 0; cnt < 3; cnt++) 
       printf("doubleVector[%d] = %f\n", cnt, doubleVector[cnt]);

   doubleVector[0] = 98;
   doubleVector[1] = 99;
   doubleVector[2] = 100;

   for (int cnt1 = 0; cnt1 < 3; cnt1++) 
       printf("modified doubleVector[%d] = %f\n", cnt1, doubleVector[cnt1]);

   return doubleVector;
}

int main() {

   double d1, d2, d3;
   double array1[3];
   double *array2;

   array1[0] = 1.00000;
   array1[1] = 2.000000;
   array1[2] = 3.000000;

   array2 = displayArray(array1);

   for (int cntr =0; cntr < 3; cntr++)
       printf("array1[%d] = %f\n", cntr, array1[cntr]);

   for (int cnt = 0; cnt < 3; cnt++)
       printf("array2[%d] = %f\n", cnt, array2[cnt]);

   return 1;
}

Emccの-oオプションを使用して、ブラウザー(Chrome)にロードした.htmlファイルを生成しました。

python emcc displayArray7.c -o displayArray7.html -s EXPORTED_FUNCTIONS="['_main', '_displayArray'

ロードすると、ブラウザウィンドウ内で生成される出力が期待どおりであることがわかります(以下を参照)。

doubleVector[0] = 1.000000
doubleVector[1] = 2.000000
doubleVector[2] = 3.000000
modified doubleVector[0] = 98.000000
modified doubleVector[1] = 99.000000
modified doubleVector[2] = 100.000000
array1[0] = 98.000000
array1[1] = 99.000000
array1[2] = 100.000000
array2[0] = 98.000000
array2[1] = 99.000000
array2[2] = 100.000000

ただし、javascriptコンソールを介してmodule.cwrap()コマンドを使用し、関数を直接(main()の外部で)呼び出そうとすると、

> displayArray=Module.cwrap('displayArray', '[number]', ['[number]'])

> result = displayArray([1.0,2.0,3.0])
[1, 2, 3]
> result
[1, 2, 3]

次のものがブラウザで生成/表示されていますが、これは私が期待しているものではありません。

doubleVector[0] = 0.000000
doubleVector[1] = 0.000000
doubleVector[2] = 0.000000
modified doubleVector[0] = 100.000000
modified doubleVector[1] = 100.000000
modified doubleVector[2] = 100.000000   

次の質問があります。

  1. Module.cwrap()の呼び出しで、戻り値の型とパラメーターリストの構文が正しいですか?チュートリアルの「コードとの相互作用」セクションで、int_sqrt()の単純でわかりやすい例を正常に実行しました。この例では、非ポインター変数をint_sqrt()ルーチンに渡します。

  2. 配列やポインタがemscriptenで生成されたjavascriptコードに渡される(または返される)ときに起こっている何か違うものはありますか?

  3. Main()から呼び出されたときに、関数displayArray()のブラウザーで生成された出力が(期待どおりに)どのように機能するか。しかし、javascriptコンソール経由ではありませんか?

私はEmscripten/javascriptを初めて使用するので、情報や支援をいただければ幸いです。

ありがとうございました、

FC

33
user2618901

Module.cwrapの予想される形式では、 '配列を関数に渡すことができますが、結果をアサートし、配列を返そうとすると失敗します

displayArrayA=Module.cwrap('displayArray','array',['array'])
displayArrayA([1,2,3]) 
// Assertion failed: ccallFunc, fromC assert(type != 'array')

これの2番目の制限は、着信配列がバイト配列であると予想されることです。つまり、着信二重配列を符号なし8ビット数に変換する必要があります。

displayArrayA=Module.cwrap('displayArray','number',['array'])
displayArrayA(new Uint8Array(new Float64Array([1,2,3]).buffer))

この方法でメソッドを呼び出すと、関数が呼び出され、呼び出された関数の実行後にリセットされるEmscriptenスタックに配列が一時的にコピーされ、スタックスペースが解放されるため、返される配列オフセットが使用できなくなる可能性があります。

関数の結果が必要な場合は、Emscriptensヒープシステム内で配列を割り当てて保持することをお勧めします。

Emscriptenコードは、Emscriptenのヒープスペース内に割り当てられたメモリにのみアクセスできます。関数に渡そうとしている配列は、Emscriptenコードが実行されているヒープの外部に割り当てられており、着信引数で予期される生のポインター型と一致しません。

配列にアクセスして関数にデータを渡す方法はいくつかあります。これらはすべて、Emscripenがemscripten Module.HEAP *内のメモリの場所を知っている必要があるため、最初のステップは、ある時点でEmscriptenの「_malloc」関数を呼び出すことです。

var offset = Module._malloc(24)

これにより、3x 8バイトのdouble配列に必要なEmscriptenヒープに必要な24バイトを割り当てることができ、配列用に予約されているU8TypedArrayオフセットを示すNumberオフセットがEmscriptenヒープに返されます。このオフセットはポインターであり、生のポインターオフセットを使用するように構成されている場合、cwrapdisplayArray関数に渡されて自動的に機能します。

displayArray=Module.cwrap('displayArray','number',['number'])

この時点で、配列の内容にアクセスまたは変更する場合は、mallocが有効である限り、少なくとも次のオプションがあります。

  1. 一時的にラップされたFloat64配列を使用してメモリを設定します。次の2つのアクセス方法を除いて、値を回復する簡単な方法はありません。

    Module.HEAPF64.set(new Float64Array([1,2,3]), offset/8);
    displayArray(offset);
    
  2. Module.setValueは、「double」ヒントを使用して、HEAPF64オフセットを8で割った値を自動的に変更します。

    Module.setValue(offset, 1, 'double')
    Module.setValue(offset+8, 2, 'double')
    Module.setValue(offset+16, 3, 'double')
    displayArray(offset)
    var result = [];
    result[0] = Module.getValue(offset,'double'); //98
    result[1] = Module.getValue(offset+8,'double') //99
    result[2] = Module.getValue(offset+16,'double') //100
    
  3. Javascript側でポインターをより広範囲に使用したい場合は、サブアレイTypedArrayをHEAPF64エントリーから手動でプルできます。これにより、関数の実行が終了した後、値を簡単に読み取ることができます。このTypedArrayは、残りのEmscriptenと同じヒープによってサポートされているため、Javascript側で実行されたすべての変更は、Emscripten側に反映されます。その逆も同様です。

    var doublePtr = Module.HEAPF64.subarray(offset/8, offset/8 + 3);
    doublePtr[0] = 1;
    doublePtr[1] = 2;
    doublePtr[2] = 3;
    // Although we have access directly to the HEAPF64 of the pointer,
    // we still refer to it by the pointer's byte offset when calling the function
    displayArray(offset);
    //doublePtr[] now contains the 98,99,100 values
    
35
phoenixillusion

他の答えへのボーナスとして、ここにfloat64配列を割り当てるための単一の便利な関数があります

function cArray(size) {
    var offset = Module._malloc(size * 8);
    Module.HEAPF64.set(new Float64Array(size), offset / 8);
    return {
        "data": Module.HEAPF64.subarray(offset / 8, offset / 8 + size),
        "offset": offset
    }
}
var myArray = cArray(3) // {data: Float64Array(3), offset: 5247688}


var displayArray = Module.cwrap('displayArray','number',['number'])
displayArray(myArray.offset)

元の関数からの出力を提供します。

doubleVector[0] = 98.000000
doubleVector[1] = 99.000000
doubleVector[2] = 100.000000
modified doubleVector[0] = 98.000000
doubleVector[1] = 99.000000
modified doubleVector[2] = 100.000000
0
dtasev