MemoryMappedFileを利用したデータの転送に関して


yTake  2022-06-09 09:24:39  No: 150261  IP: [192.*.*.*]

yTakeです
一度、閉じてしまいましたので、別に始めます。
MemoryMappedFileは成功しているとお伝えしていましたが、あるデータが正しく転送できていないので、ご教授願えれば幸いです。
MemoryMappedFileで転送するデータは構造体で可変長な配列を含んでいます。
構造体は簡単にいうと次の様に定義しています
type
      RMMF_MTX = Record
             dimX  :  Word;
             dimY  :  Word;
                DPI  :  Word;
              MTX  :  Array [ 1 .. 3 ] of Array of Array of Word;
END;
転送時にエラーはありません。
読み出し時もエラーはありません。
また、可変長ではない部分(つまり、dimX, dimY, DPI)に関しては正しく転送されています。

また、可変長の配列のサイズは、別のMemoryMappedFileで予め取得しています。(この時も正常に転送)

読み出しは、
var
    ptr  :  PWord;
として置いて、

    GetMem( ptr, siz );
    CopyMemory( ptr, P, siz );
    rcv_mtx.dimX  := ptr^;
    inc( ptr );
    rcv_mtx.dimY  := ptr^;
    inc( ptr );
    rcv_mtx.DPI  := ptr^;
     inc( ptr );
    for  k := 1  to 3  do
        for  k := 1  to 3  do
            for  k := 1  to 3  do
            begin
                rcv_mtx.MTX[ k ][ i, j ]  :=  ptr^;
                inc( ptr );
            end;
の様にしています。

読み出し自体はエラーなく行われますが、値が、VCL側で登録した値と異なっています。

ポインターがズレてしまっている?

原因が分かりません。
よろしくお願いします。

編集    削除
yTake  2022-06-09 10:03:12  No: 150262  IP: [192.*.*.*]

自己レスです

実は、
    CopyMemory( ptr, P, siz ); 
のところを
 CopyMemory( rcv_mtx, P, siz );
などの様に、直接、受けの構造体へ転送できないか?
と思っていましたが、うまくコーディング出来ず、ポインターを介する事になりました。
GetMem( @rcv_mtx, siz );

GetMem( rcv_mtx, siz );
では、ダメみたいです。

この辺が何か関係しているでしょうか?


編集    削除
HFUKUSHI  2022-06-09 10:41:48  No: 150263  IP: [192.*.*.*]

> MTX  :  Array [ 1 .. 3 ] of Array of Array of Word; 
これはうまくない気がしますね。動的配列(Array of Array of Word)はポインタで、実際に格納されるデータはヒープ上に確保されています。
レコード型RMMF_MTXからMTXを外して、別途Wordの配列をメモリマップドファイルで渡す、という感じでしょうか。

編集    削除
yTake  2022-06-09 12:38:28  No: 150264  IP: [192.*.*.*]

HFUKUSHIさん
ありがとうございます。
そうですか、動的配列の場合はその実体は別のところ(ヒープ領域)に存在しているのですね。
動的配列部分を分離してみましたが、それだけではダメな様です。
@mmf_mtx.MTX[1]
@mmf_mtx.MTX[2]
@mmf_mtx.MTX[3]
とで、アドレスが相互に4バイトしか違いません。
これは実体はここに無く、実体へのアドレスが格納されていそうです。
そのアドレスの指し示すところの内容を転送すれば良い様に思いますが、実現方法が難しいです。
コンパイル時点でエラーになっていて、参照が正しくないのだと思います。
色々、試してみます。

編集    削除
AAA  2022-06-09 13:00:53  No: 150265  IP: [192.*.*.*]

  TA = Record
    A1: Integer;
    A2: array[0..1] of Integer;
    A3: array of Integer;
    A4: String;
  End;


var
    A: TA;
begin
    SetLength(A.A3,10);
    A.A4 := 'AAAAAAAA';
    Memo1.Lines.Add(IntToStr(Integer(@A)));
    Memo1.Lines.Add(IntToStr(Integer(@A.A1)));
    Memo1.Lines.Add(IntToStr(Integer(@A.A2)));
    Memo1.Lines.Add(IntToStr(Integer(@A.A3)));
    Memo1.Lines.Add(IntToStr(Integer(@A.A4)));
    Memo1.Lines.Add(IntToStr(Integer(@A.A3[1])));
    Memo1.Lines.Add(IntToStr(Integer(@A.A4[1])));

とやればわかるが、String とか array of Integer みたいのは
内容は別の領域に確保される。

編集    削除
AAA  2022-06-09 16:13:04  No: 150266  IP: [192.*.*.*]

話はかわるけど
FMX から VCL のフォーム呼び出しちゃえば?

編集    削除
yTake  2022-06-09 16:25:50  No: 150267  IP: [192.*.*.*]

AAAさん
ありがとうございます。

なるほど、配列要素のアドレスを参照すれば、実体の場所(アドレス)が分かるわけですね。

動的配列の実体のアドレスを得る事が出来ました。
動的配列は3つ使っています。これらも一連ではなく、各々異なった場所に格納されている様でした。
MemoryMappedFileも各々に用意し、転送する事としました。
一応、値自体はVCL側で取得した値が得られる様になった様です。

ここで疑問が生じました。配列の並び順です。
二次元配列である為、行と列があります。実体はシーケンシャルに並んでいるとしても、行と列は意味があるため、正しく取り出す必要があります。
VCL側でMemoryMappedFileへコピーする際は一括コピーの為、並び順を確認できません。
元の配列データは、行から埋めてゆき順次列方向に埋めていっています。
FMX側でMemoryMappedFileから取り出す際、一括コピー後、Word型ポインターでシーケンシャルに読みだしています。
それを配列に格納するのですが、元データと同じ様に行から埋めてゆくと、読み出される値の順が違っています。列方向に値が読み出されています。

自動的に列方向に優先でシーケンシャルに保存されているのでしょうか?

なお、未だ、転送内容が元と同じではない様です。
値を逐次確認出来ないので、画像化しているのですが、転送後の画像が転送前と全く異なってしまっています。
画像化の手順は同じはずなのですが、、、
読み出し順とは違う話の様に思ってはいます。

編集    削除
yTake  2022-06-09 16:30:20  No: 150268  IP: [192.*.*.*]

AAAさん
FMXからVCLのフォームを呼び出すとはどういう事でしょう?
現在は、FMXからVCLアプリケーションを起動し、データを取得してそれを共有メモリーへコピーし、FMXからその共有メモリーからデータをコピーしようとしています。

他にも、やり方があるという事でしょうか?

知識不足ですみません。

編集    削除
HFUKUSHI  2022-06-09 17:25:01  No: 150269  IP: [192.*.*.*]

試しにMTXを静的配列、つまりすべての次元であらかじめ要素数を定義した普通の3次元配列にしてみてはいかがですか?
MTX  :  Array [ 1 .. 3 ] of Array [0..99] of Array [0..99] of Word; 
この形式のデータであれば、メモリマップドファイルで簡単に渡せます。

ヘルプの配列型の項目も見直しておいたほうが良いと思います。
https://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E9.85.8D.E5.88.97.E5.9E.8B

編集    削除
yTake  2022-06-09 17:30:05  No: 150270  IP: [192.*.*.*]

FMXからVCLの呼び出しの件ですが、DEKOさんが紹介されている方法でしょうか?
何やら、色々と追加でインストールする必要があるのですよね。
なるべく、追加はしたくないと思っています。
他に手法が無ければ別ですが、MemoryMappedFileでうまく行きそうな気配はあるのですが、、、
今一歩のところです。

編集    削除
yTake  2022-06-09 18:45:37  No: 150272  IP: [192.*.*.*]

静的配列で試してみました。
うまく行きました。
VCL側で取得したデータをFMX側で確認出来ました。
画像化した形でも、値を抜き打ちで数点確認し全て合致しています。
先頭アドレスからの一括コピーと、シーケンシャル読み出しで問題ありません。

という事は、元の動的配列の実体への参照が問題という事になります。
動的配列で正しく受け渡しが出来ているのは、各[1], [2], [3]の1行目(1列目?)のみです。2行目から参照がズレています。
3つの動的配列自体は独立しているので、別々のアドレスで構わないと思いますが、各々の動的配列の中で行や列で参照先が連続的ではない点に、非常に違和感を覚えます。
これは動的配列の仕様なのでしょうか?
動的配列の全ての要素に正しくアクセスする方法はないのでしょうか?

取得するデータの大きさが可変である為、静的配列では難しいです。
動的配列が使えると良いのですが、、、
静的配列を予め大き目に用意しておくという事も考えられますが、その大きさを超えるデータが取得された時に困りますし、安全をみて余り大きく取り過ぎるのもスマートではないし、、、他にやり様がなければ致し方ありませんね。

編集    削除
HFUKUSHI  2022-06-09 20:09:01  No: 150273  IP: [192.*.*.*]

動的配列と多次元配列について、もう一度きちんと理解しなおしたほうがよさそうです。
静的配列では、リニアに(1次元で)確保したメモリに対して、インデックスを自動計算してマッピングしてくれるわけですが、動的配列ではそういうわけにはいきません。
逆に言えば、このインデックスの計算を自前でやれば、渡すものを1次元の動的配列にしてもうまくいく、ということになります。

編集    削除
yTake  2022-06-09 23:56:45  No: 150274  IP: [192.*.*.*]

思うのですが、元々CopyMemoryで配列名称を引数に使って、添え字でその要素にアクセスするのではないか?と思っていましたが、うまく記述出来ない(コンパイル・エラーを解消できない)ので、ポインターで引数としてみました。
ただ、配列の実体がシーケンシャルであれば問題ありませんが、その実体がランダムに存在している場合にはうまく行きません。
逆に言うと、配列を添え字でアクセスする場合、実体がシーケンシャルであるかランダムであるかに関わらず常に正しくアクセスできます。
つまり、CopyMemoryの引数として配列名を正しく用いる事が出来れば、全てうまく行く様に思うのですが、違うでしょうか?

編集    削除
yTake  2022-06-10 08:30:41  No: 150275  IP: [192.*.*.*]

配列名では難しそうです。

動的配列の実体へのアドレスは動的配列部分の行毎の先頭の要素のアドレスでアクセス出来そうです。
転送する列数分共有メモリーへの書き込みと読み出しが必要ですが、現実的ではありません。
共有アドレスへのマッピング時に列数分シーケンシャルにずらしてマッピングすれば良いのではと考えます。
MapViewOfFile関数ではオフセットや転送バイト数を設定できそうです。
これを試してみます。

編集    削除
yTake  2022-06-10 09:42:44  No: 150276  IP: [192.*.*.*]

部分的にマッピングしてゆくオフセットの設定がよくわかりません。

HFUKUSHIさんに照会頂いたサイトの例によりますと、先頭アドレスからのオフセットを指定する(前者に上位32ビット,後者に下位32ビットを指定する)という事ですが、上位・下位ビットというのがわかりません。オフセット先のアドレスの事と思いますが、オフセットの量は分かりますが、そのアドレスはわかりません。

以下の様にマップが済んだデータ量毎にオフセットさせています。(大幅なオフセット移動ではない様に思うので下位の方に設定(根拠はありません))1行目はコピー出来ている様子ですが、2行目のマッピングで戻り値P1=nilとなってしまいます。
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    siz := SizeOf( Word ) * dimX * dimY ;

    HFILE_1  := CreateFileMapping( INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, siz, '_FileMappingData1' );     //  ①

    if  ( HFILE_1 <> 0 ) then
    begin
        for j := 0 to dimY - 1 do
        begin
            P1 := MapViewOfFile( HFILE_1, FILE_MAP_WRITE, 0, ( SizeOf( Word ) * dimX * j ), SizeOf( Word ) * dimX );    //  ②
            ptr1 :=  @mmf_mtx.MTX[ 1 ][ 0, j ];                                                                                                                 //  ③

            if  ( P1 <> nil ) then
            begin
                CopyMemory( P1, ptr1, SizeOf( Word )* dimX );          //  ④
            end;
        end;
            UnmapViewOfFile( P1 );
    end;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

①:MemoryMappedFileの作成時は、全体のマトリックスサイズで作成
②:マッピング時に1行分のみマップし、次のマッピング時にそのマッピング分をオフセットしまた1行分マップします
③:動的配列の実体のアドレスを行毎に取得
④:MemoryMappedFileのマップ先へ1行コピー


マップ時のオフセット指定がうまく行けば、動的アドレスの場合でもMemoryMappedFileへ正しくコピーできるでしょうか?

編集    削除
AAA  2022-06-10 12:05:02  No: 150277  IP: [192.*.*.*]

これだけで呼び出せるよ?

implementation

uses
   VCLUnit1;

{$R *.fmx}

procedure TForm1.Button1Click(Sender: TObject);
var
    Form1: VCLUnit1.TVForm1;
begin
    //FMX の TForm1 と VCL の TForm1 が同じだとエラーになるので TVForm1 にしてある
    Form1 := VCLUnit1.TVForm1.Create(Nil);
    Form1.ShowModal;
    Form1.Free;
end;

end.


編集    削除
AAA  2022-06-11 10:08:32  No: 150278  IP: [192.*.*.*]

そもそもなんだけど

>主として、FMX環境で開発していますが、どうしてもVCL環境でしか使えないライブラリがあり、
取得されたデータ(構造体として保持)をFMXアプリケーションへ渡したいと考えています。
これがある限り

>現在は実現できているところから、徐々にFMX化プラットフォームフリー化出来たら良いと思います。
無理だよね?

やるなら
JCL側で 取り込みし、画像ファイルに保存(Windows専用)
FMX側で 画像ファイルを読み込み と 取り込み以外の機能を実現(プラットフォームフリー化)

1個のexeにする必要ないよね

このライブラリってのがメーカーが提供する DLL や ActiveX で Windows にしか
ないなら プラットフォームフリー なんて無理よ
Mac用とかにも提供されているなら それを使う事を考えないと



編集    削除
yTake  2022-06-11 20:44:14  No: 150279  IP: [192.*.*.*]

AAAさん
ありがとうございます。

何と、FMXからVCLのフォームを呼び出せました。
グローバルの変数も使えるので、構造体データの受け渡しも出来ます。
検証のための簡易的に試してみました。画像化部分で元のVCLフォームで取得時と同じ画像データをFMXフォームで描画できています。
実にあっさり、VCLー>FMXの画像転送が出来てしまいました。

ただ、VCLフォームとFMXフォームは同じプロジェクト内で共存できない為、VCLフォーム部分だけ別プロジェクトとなり、仕様変更などの際は各々読み込み直しが必要になり作業的にはVCLアプリとしてフィル保存し、FMXアプリでそのファイルを読み込んでいる現在の仕様と同じです。


ご指摘の通り、全てをFMXで実現する事がプラットフォームフリーには不可欠です。
実は、それもトライしているところです。
見通しとしては、可能と思っていますが、道半ばです。

編集    削除
AAA  2022-06-12 10:08:27  No: 150280  IP: [192.*.*.*]

>何と、FMXからVCLのフォームを呼び出せました。

>ただ、VCLフォームとFMXフォームは同じプロジェクト内で共存できない為、VCLフォーム部分だけ別プロジェクトとなり、仕様変更などの際は各々読み込
とは?

FMX上で VCL 側を開けば良いだけですよ? 
VCL側フォーム名は FMX側と同じしてはダメですよ




編集    削除
yTake  2022-06-12 18:33:35  No: 150281  IP: [192.*.*.*]

フォームの名前は変えてあります。
開くというか、
FMXのプロジェクトに、VCLフォームのユニットを追加しようとすると、「ユニット”VCLUnit2.pas”はプロジェクトで使用しているFMXフレームワークと互換性がありません。」「追加するとコンパイルエラーや予期しない動作が起きる可能性があります。続行しますか?」とのメッセージが出ます。
”いいえ”で応えると、VCLフォームは開かれます。
FMX側からuses節にvclフォームのソースを追加すると一応参照は可能な様ですが、実行(構文チェックでも)するとそのvclのdcuファイルが見つかりませんという致命的エラーになります。プロジェクに登録していない為ではないか?と思っています。
IDE環境の設定について理解が不十分な様です。
ただ、曲がりなりにもVCLのフォーム自体はFMX側から呼び出せているので、一旦、ファイルに保存することなく構造体データのVCLー>FMXは実現できている様です。
実機のテストが未だなので、安心はできませんが。

編集    削除
Mr.XRAY  2022-06-17 09:31:24  No: 150285  IP: [192.*.*.*]

メモリマップドファイルを使用するのが目的ではなかったようですが,
動的配列の操作があったので参考に.

  //===========================================================================
  //  N 行 M 列の 2 次元の Double 型の動的配列をメモリストリームに格納する例
  //  N, M : Integer;
  //  FArray : array of array of Double;
  //===========================================================================
  SetLength(FArray, N, M);
  
  //  ここに配列要素へ値を代入する処理があると仮定

   LStream := TMemoryStream.Create;
  try
    LStream.WriteData(N);
    LStream.WriteData(M);  
    for LRowIdx := 0 to N - 1 do begin
      LStream.Write(FArray[LRowIdx][0], M * SizeOf(Double));
    end;
  finally
    FreeAndNil(LStream);
  end;  
  
2 次元の動的配列は,各行が 1 次元の動的配列.
つまり,2 次元の動的配列は,ポインタのポインタ
そこで,上のような処理になります.
行と列の値も記録しておかないと配列として再現できません.
( 列数は計算でも算出可能 )

編集    削除
Mr.XRAY  2022-08-04 16:20:42  No: 150337  IP: [192.*.*.*]

改めて説明するまでもないと思いますが,
メモリストリームから読み込む時は,Write を Read にするだけです.

    LStream.Position := 0;
    LStream.ReadData(N);
    LStream.ReadData(M); 
    SetLength(FArray, N, M);  

    for LRowIdx := 0 to N - 1 do begin
      LStream.Read(FArray[LRowIdx][0], M * SizeOf(Double));
    end; 
    
共有メモリ (メモリマップドファイル) はメモリストームの操作です.
動的配列は,このように行単位で処理すると高速です.
経験がないと納得できないかも知れませんが,
TBitmap の画像のピクセル全体を処理する際,

  TBitmap.Canvas.Pixels[X, Y]
とするよりも,  
  TBitmap.ScanLine[Y]
  
として処理する方がはるかに高速です (数十倍速くなります).
これと同じ原理です.

編集    削除