VCの構造体をVBで参照してメンバを表示するには?

解決


はんどるねーむ  2006-10-04 01:23:25  No: 133503

VC++6.0から構造体の先頭アドレスをファイルマッピングオブジェクト
(共有メモリ)に保存してVB6.0で受け取り、メンバをテキストボックス
コントロールに表示したいのですが上手くいきません。

構造体のメンバにはLong型とChar型が入っています。

過去ログから似たものが検索できましたが、VB.netであったため少々
動作が違うようです。

VB側処理
------------------------------------------------
typedef struct _record {
    char name[5];
    long average;
} t_record
-------------------------------------------------
t_record test;
strcpy(&test.name[0],"名前");
test.average = 80;

——ファイルマッピング作成部分省略

lpStr = (LPSTR)MapViewOfFile
    (hFM[0],FILE_MAP_ALL_ACCESS, 0, 0, 0);    }

printf("構造体を書き込みます\n");
memcpy(lpStr, (char *)&test, sizeof(t_record));
UnmapViewOfFile(lpStr);
printf("書き込み完了しました\n");
-------------------------------------------------

VB側処理
---------------------------------------------------------
hOpened = OpenFileMapping(FILE_MAP_READ, 0, "Object0")
nAddress = MapViewOfFile(hOpened, FILE_MAP_READ, 0, 0, 0)

この後  nAddress からどのようにして各メンバをコントロールに表示したら
良いかが分かりません。ご教授お願い致します。


Blue  2006-10-04 02:37:28  No: 133504

とりあえず、構造体サイズが微妙な(※)のでうまくいくのかわかりませんが、
MoveMemoryあたりのAPIを使うことで何とかできませんか?

Private Declare Sub MoveMemory Lib "kernel32.dll" Alias "RtlMoveMemory" _
(Destination As Any, Source As Any, ByVal Length As Long)

Dim test(8) As Byte
Dim s() As Byte
Dim i As Long

s = StrConv("名前", vbFromUnicode) ' Shift_JIS変換
MoveMemory test(0), s(0), 4
test(4) = 0 ' 終端文字
i = 80&
MoveMemory test(5), i, 4

MoveMemory(ByVal nAddress, test(0), 9)

もしかしたら不正アクセスになる可能性があるので、バックアップはちゃんとしておいてください。

※4バイト語境界になっていない。
  やるのであれば、

typedef struct _record {
    char name[8];
    long average;
} t_record


Blue  2006-10-04 02:42:53  No: 133505

あー上の例は、上にあるCのコードをVBにしただけです。
読み込むのであるならば、逆の手順にしてください。

それと、構造体が4バイト語境界であれば、VB側でC側同様に構造体で扱えるとおもいます。


Blue  2006-10-04 02:56:22  No: 133506

ちなみに、C言語側の構造体のサイズもチェックしたほうがいいですね。

おそらく何もいじっていなければ sizeof(t_record)は12

参考:パディングとアライメント 
http://www.kab-studio.biz/Programing/PragmaTwice/Main/234.html


はんどるねーむ  2006-10-04 05:09:51  No: 133507

Blueさん、ご回答ありがとうございます。

表示が成功した際に、構造体メンバのCharが奇数バイトの場合は
次のメンバがバイナリずれの影響を受けると考えており、確認する
予定でしたが、4の倍数だったのですね。
紹介して頂いたURLもとても参考になりました。
感謝しております。

今回、VB側で
-------------------------------------------------------------
Public Type RECORD
    name As String * 8
    average As Long
End Type

Dim test As RECORD
MoveMemory(ByVal test, ByVal nAddress, HOUSE_CAPACITY)
-------------------------------------------------------------
と致しました所、

test.name = "・??炉??Q"
test.average = 0

と表示され、上手く拾えてないようでした。尚、VC側の構造体サイズは
12でした。また、構造体にint型のメンバを増やして検証したところ、
int型のメンバはVB側で正しく表示できるようです。

BlueさんのMoveMemoryは現在試行中です。


Blue  2006-10-04 07:10:09  No: 133508

>    name As String * 8
はやめたほうがいいです。
(そうするのであれば、C側はUnicode(wchar_t型配列)で扱わないとダメ)
Byte型の配列を使ってください。

>MoveMemory(ByVal test, ByVal nAddress, HOUSE_CAPACITY)

>Private Declare Sub MoveMemory Lib "kernel32.dll" Alias "RtlMoveMemory" _
>(Destination As Any, Source As Any, ByVal Length As Long)
というAPI宣言である場合、第一引数に ByVal は要りません。

Public Type RECORD
    name(7) As Byte
    average As Long
End Type

Dim test As RECORD
MoveMemory(test, ByVal nAddress, HOUSE_CAPACITY)

として、

test.name は 終端文字を含むShift_JISコードの配列であるので、StrConvで
Unicodeに変換してください。


あん  2006-10-04 07:43:44  No: 133509

>Public Type RECORD
>    name As String * 8
>    average As Long
>End Type
APIによるメモリコピーなので
string(VB)←→char(C)の変換はないので

stringをByteにする必要があります。
string * 1は1バイトじゃないですよ。


あん  2006-10-04 07:48:41  No: 133510

>Public Type RECORD
>    name(7) As Byte
>    average As Long
>End Type

教えてください
name(7) As Byte  は  8でなく7なのは
なんででしたっけ?


Blue  2006-10-04 08:25:13  No: 133511

>name(7) As Byte  は  8でなく7なのは
VBの配列は0から始まり、指定した値までなので、0〜7 で 8
Cの配列は0から始まり、指定した値-1までなので、char name[8];ならば、0〜7 で 8
ということ。


Blue  2006-10-04 08:27:18  No: 133512

>Cの配列は0から始まり、指定した値-1までなので、char name[8];ならば、0〜7 で 8
ちょっと言い方が違いました。

C言語の配列は指定した数だけ要素があるため、char name[8];ならば 8 になるには 0〜7 という範囲になる。


あん  2006-10-04 10:37:09  No: 133513

ああ  そうでした
ありがとう。


はんどるねーむ  2006-10-04 19:08:17  No: 133514

Blueさん、引き続きアドバイスありがとうございます。

Type内の name をByte型で宣言すると
MoveMemoryする際にVBごと落とされてしまいます。

String型で宣言した場合は前回報告致しました通り、
意図した出力ではないもののMoveMemoryが通るの
ですが・・・

引き続き調べてみますが、何かありましたらお願いします


Blue  2006-10-04 19:15:59  No: 133515

現行のコードは結局どうなっているのでしょうか?
>Blue 2006/10/03(火) 22:10:09
のDeclare宣言と、MoveMemoryの記述になっているのですか?

ところで、
>HOUSE_CAPACITY
ってのがいきなり出てきましたが、この値は12ですか?
LenB(test)としたほうがベターのようですが。


はんどるねーむ  2006-10-04 20:43:23  No: 133516

HOUSE_CAPACITY は、この処理とは別口で欲しい箇所がありました
のでConst宣言しておりました。

API宣言は、教えて頂いた
>Private Declare Sub MoveMemory Lib "kernel32.dll"_
>Alias "RtlMoveMemory"(Destination As Any, Source As Any, _
>ByVal Length As Long)
をそのまま頂きました。

MoveMemoryで落ちていた原因は
>Call MoveMemory(ByVal nAddress, test(0), 9)
が、メモリのコピー元とコピー先が逆だったようです。

Call MoveMemory(test(0), ByVal nAddress, 9)
Call MoveMemory(i, test(5), 4)
buf(4) = 0
Call MoveMemory(s(0), test(0), 4)

で、通りました。

>i = 80&

>s = StrConv("名前", vbFromUnicode)
は、VB側でもVCの入力(80と"名前")をもう一度入れ直して
いますが移動したメモリから参照できないのでしょうか?

こちらの質問は初歩的でお恥ずかしいのですが・・・


Blue  2006-10-04 20:46:43  No: 133517

>>i = 80&
>と
>>s = StrConv("名前", vbFromUnicode)
>は、VB側でもVCの入力(80と"名前")をもう一度入れ直して
>いますが移動したメモリから参照できないのでしょうか?
いや、最初の方に書きましたが、それは勘違いしてコードを書いています。
>あー上の例は、上にあるCのコードをVBにしただけです。
>読み込むのであるならば、逆の手順にしてください。

ですので、

>Dim test As RECORD
>MoveMemory(test, ByVal nAddress, HOUSE_CAPACITY)

で、コピーされるはずなんですが。


はんどるねーむ  2006-10-04 22:37:43  No: 133518

Σ(‾口‾;)!?
>読み込むのであるならば、逆の手順にしてください。

この意味を完全に勘違いしておりました。
きちんと考えると、どう見てもVB側からVCに送ろうと
していることが理解できました・・・。

>Dim test As RECORD
>MoveMemory(test, ByVal nAddress, HOUSE_CAPACITY)

にて、
test.name = 150
test.average = -858993664
が得られます。逆の手順ということで、
Dim str As String
str = Str(buf.name, vbUnicode)
としてみたのですが、"1・5・0・"と表示されます。
また、averageは上手く取得できているのでしょうか。
色々試してみましたが、VCで入れた 80 には変換できませんでした。

何度もすみませんが宜しければご教授ください。


Blue  2006-10-04 22:58:39  No: 133519

>test.name = 150
>test.average = -858993664
うーむ、うまく出来ていないっぽいですね。

ちなみに、VC同士であればうまくいくのでしょうか?
うまくいくのであればそのコードを見せてもらってもよいでしょうか?


はんどるねーむ  2006-10-04 23:46:23  No: 133520

VC同士の場合は共有メモリに置いたものを読み取れました。

読取側をVCで作った場合のコードは以下の通りです。
----------------------------------------------------
オープンマップとマッピング処理省略

lpStr = (LPSTR)MapViewOfFile(hFM, FILE_MAP_ALL_ACCESS, _
        0, 0, 0);
memcpy((char *)&test, lpStr, sizeof(t_record));
printf("第一メンバ書き込み内容 -- %s\n", &test.name);
printf("第ニメンバ書き込み内容 -- %ld\n", &test.average);

以下マッピングオブジェクトとハンドルクローズ
---------------------------------------------------------
これで、第一メンバ書き込み内容 -- 名前
        第ニメンバ書き込み内容 -- 80
と表示されました。


Blue  2006-10-05 00:59:38  No: 133521

VCでDLLを作って試してみました。

VC
---------------------------------------------------------
typedef struct tagRECODE
{
    char sName[ 8 ];
    int  Ave;
} RECODE;

void WINAPI MapTest1()
{
    RECODE r = { "名前", 80 };

    HANDLE hMapFile = ::CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof( r ), MAP_NAME );
    if ( hMapFile == NULL )
    {
        ::MessageBox( NULL, "CreateFileMapping失敗", NULL, MB_OK );
        return;
    }
    LPVOID lpMapAddress = ::MapViewOfFile( hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
    if ( lpMapAddress != NULL )
    {
        memcpy( lpMapAddress, &r, sizeof( r ) );
        ::UnmapViewOfFile( lpMapAddress );
    }
    else
    {
        ::MessageBox( NULL, "MapViewOfFile失敗", NULL, MB_OK );
        ::CloseHandle( hMapFile );
    }
    // CloseHandleするとOpenFileMappingで取得できない
}

VB(というか、VBの環境が今ないのでVBAです)

Option Explicit

Private Declare Function OpenFileMapping Lib "Kernel32" Alias "OpenFileMappingA" _
    (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal lpName As String) As Long
Private Declare Function CloseHandle Lib "Kernel32" (ByVal hObject As Long) As Long
Private Declare Function MapViewOfFile Lib "Kernel32" _
    (ByVal hFileMappingObject As Long, ByVal dwDesiredAccess As Long, _
     ByVal dwFileOffsetHigh As Long, ByVal dwFileOffsetLow As Long, _
     ByVal dwNumberOfBytesToMap As Long) As Long
Private Declare Function UnmapViewOfFile Lib "Kernel32" (ByVal lpBaseAddress As Long) As Long
Private Declare Sub MoveMemory Lib "kernel32.dll" Alias "RtlMoveMemory" _
    (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)

Private Declare Sub MapTest1 Lib "VBDLL.dll" ()

Private Type RECODE
    sName(7) As Byte
    Ave      As Long
End Type

Sub test()
    Dim s As String
    ChDrive ThisWorkbook.Path
    ChDir ThisWorkbook.Path
    MapTest1
    MapTest2
End Sub

Sub MapTest2()
    Dim hMapFile As Long
    Dim hAddress As Long
    Dim r As RECODE
    Dim s As String
    
    hMapFile = OpenFileMapping(983071, 0, "Object")
    If hMapFile <> 0 Then
        hAddress = MapViewOfFile(hMapFile, 983071, 0, 0, 0)
        If hAddress <> 0 Then
            MoveMemory r, ByVal hAddress, LenB(r)
            s = StrConv(r.sName, vbUnicode)
            s = Left$(s, InStr(s, vbNullChar) - 1)
            MsgBox "Name = " & s & " , Ave = " & r.Ave
            UnmapViewOfFile hAddress
        End If
    End If
    CloseHandle hMapFile
End Sub

でうまくいっているようなんだけどなぁ。


Blue  2006-10-05 01:01:06  No: 133522

VCの方で

#define MAP_NAME "Object"

が抜けていました。


はんどるねーむ  2006-10-05 01:31:11  No: 133523

問題点は別の所にあったようです・・・。

name は、フォームコード部分では何の問題もありませんが
Type宣言をしたモジュール(Bas)の方では何と
予約語だったようです・・・。

そのため、Byte配列の領域が上手く確保されず、特に二番目の
メンバであった average の方が明らかにおかしな値になって
しまっていたようです。

今少し時間が空いていないので、落ち着き次第最終的なソースを
添えて解決としたいと思います。(解決チェックはその時に致します)

Blue さんには本当にお世話になりました。
度々の質問にも関わらず、すぐお返事を頂けて大変助かりました。


はんどるねーむ  2006-10-05 19:42:08  No: 133524

VC側
--------------------------------------------------
typedef struct _record {
    char name[8];
    long average;
} t_record;

main(){
    Create ・・・ // 省略
    lpStr = (LPSTR)MapViewOfFile(hFM,
  FILE_MAP_ALL_ACCESS, 0, 0, 0);

    memcpy(lpStr, (char *)&test, sizeof(t_record));
    UnmapViewOfFile(lpStr);

    _getch(); // 入力待ち状態
    CloseHandle(hFM);
-----------------------------------------------------

VB(6.0)側
-----------------------------------------------------
Declare Sub MoveMemory Lib "kernel32.dll" Alias "RtlMoveMemory"_ (Destination As Any, Source As Any, ByVal length As Long)

'以下 Declare宣言省略
Declare Function CloseHandle・・・
Declare Function MapViewOfFile・・・
Declare Function OpenFileMapping・・・
Declare Function UnmapViewOfFile・・・

Public Type RECORD
    s_name(7) As Byte
    average As Long
End Type

Private Sub cmdStart_Click()
Dim buf As RECORD
Dim n As Long
Dim str As String

Me.txtDisplay = ""
Me.txtDisplay.Refresh

test = FileRead()

str = StrConv(test.s_name, vbUnicode)
str = Left(str, InStr(str, vbNullChar) - 1)
n = buf.average

Me.txtDisplay.Text1 = str
Me.txtDisplay.Text2 = n

Private Function FileRead() As RECORD
Dim hOpened As Long
Dim nAddress As Long
Dim test As RECORD

hOpened = OpenFile・・・
nAddress = MapViewOfFile・・・

Call MoveMemory(test, ByVal nAddress, LenB(test))
Call UnmapViewOfFile(nAddress)

Call CloseHandle(hOpened)
Call CloseHandle(hFileMap)
Call CloseHandle(hFile)

FileRead = test
End Function
-------------------------------------------------------
以上で、VBのコントロールに構造体のメンバが表示できました。
尚、VC側のchar配列が4の倍数ではなくてもアライメントの都合上
構造体はサイズは4の倍数にされ、VB側のByte配列宣言さえ
気をつければ一応ズレはないようですね。

ただし、VC側で#pragma pack()した場合はズレが発生してしまい、
VB側でCopymemoryを使用してPack幅と同じようにセットする必要
があるようです。

制御・通信はあまり触れたことがなく、途方に暮れていましたが
お蔭様で何とかできそうです。改めてBlueさんに感謝致します。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加