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 からどのようにして各メンバをコントロールに表示したら
良いかが分かりません。ご教授お願い致します。
とりあえず、構造体サイズが微妙な(※)のでうまくいくのかわかりませんが、
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
あー上の例は、上にあるCのコードをVBにしただけです。
読み込むのであるならば、逆の手順にしてください。
それと、構造体が4バイト語境界であれば、VB側でC側同様に構造体で扱えるとおもいます。
ちなみに、C言語側の構造体のサイズもチェックしたほうがいいですね。
おそらく何もいじっていなければ sizeof(t_record)は12
参考:パディングとアライメント
http://www.kab-studio.biz/Programing/PragmaTwice/Main/234.html
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は現在試行中です。
> 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に変換してください。
>Public Type RECORD
> name As String * 8
> average As Long
>End Type
APIによるメモリコピーなので
string(VB)←→char(C)の変換はないので
stringをByteにする必要があります。
string * 1は1バイトじゃないですよ。
>Public Type RECORD
> name(7) As Byte
> average As Long
>End Type
教えてください
name(7) As Byte は 8でなく7なのは
なんででしたっけ?
>name(7) As Byte は 8でなく7なのは
VBの配列は0から始まり、指定した値までなので、0〜7 で 8
Cの配列は0から始まり、指定した値-1までなので、char name[8];ならば、0〜7 で 8
ということ。
>Cの配列は0から始まり、指定した値-1までなので、char name[8];ならば、0〜7 で 8
ちょっと言い方が違いました。
C言語の配列は指定した数だけ要素があるため、char name[8];ならば 8 になるには 0〜7 という範囲になる。
ああ そうでした
ありがとう。
Blueさん、引き続きアドバイスありがとうございます。
Type内の name をByte型で宣言すると
MoveMemoryする際にVBごと落とされてしまいます。
String型で宣言した場合は前回報告致しました通り、
意図した出力ではないもののMoveMemoryが通るの
ですが・・・
引き続き調べてみますが、何かありましたらお願いします
現行のコードは結局どうなっているのでしょうか?
>Blue 2006/10/03(火) 22:10:09
のDeclare宣言と、MoveMemoryの記述になっているのですか?
ところで、
>HOUSE_CAPACITY
ってのがいきなり出てきましたが、この値は12ですか?
LenB(test)としたほうがベターのようですが。
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と"名前")をもう一度入れ直して
いますが移動したメモリから参照できないのでしょうか?
こちらの質問は初歩的でお恥ずかしいのですが・・・
>>i = 80&
>と
>>s = StrConv("名前", vbFromUnicode)
>は、VB側でもVCの入力(80と"名前")をもう一度入れ直して
>いますが移動したメモリから参照できないのでしょうか?
いや、最初の方に書きましたが、それは勘違いしてコードを書いています。
>あー上の例は、上にあるCのコードをVBにしただけです。
>読み込むのであるならば、逆の手順にしてください。
ですので、
>Dim test As RECORD
>MoveMemory(test, ByVal nAddress, HOUSE_CAPACITY)
で、コピーされるはずなんですが。
Σ(‾口‾;)!?
>読み込むのであるならば、逆の手順にしてください。
この意味を完全に勘違いしておりました。
きちんと考えると、どう見ても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 には変換できませんでした。
何度もすみませんが宜しければご教授ください。
>test.name = 150
>test.average = -858993664
うーむ、うまく出来ていないっぽいですね。
ちなみに、VC同士であればうまくいくのでしょうか?
うまくいくのであればそのコードを見せてもらってもよいでしょうか?
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
と表示されました。
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
でうまくいっているようなんだけどなぁ。
VCの方で
#define MAP_NAME "Object"
が抜けていました。
問題点は別の所にあったようです・・・。
name は、フォームコード部分では何の問題もありませんが
Type宣言をしたモジュール(Bas)の方では何と
予約語だったようです・・・。
そのため、Byte配列の領域が上手く確保されず、特に二番目の
メンバであった average の方が明らかにおかしな値になって
しまっていたようです。
今少し時間が空いていないので、落ち着き次第最終的なソースを
添えて解決としたいと思います。(解決チェックはその時に致します)
Blue さんには本当にお世話になりました。
度々の質問にも関わらず、すぐお返事を頂けて大変助かりました。
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さんに感謝致します。
ツイート | ![]() |