おかどちがいな質問だと思うのですが、他にアテがないので質問させて下さい。
Excel2000でVBAを勉強しているのですが、ChooseFontダイアログの処理がうまくいきません。
ダイアログ自体は表示されるのですが、LOGFONT構造体の設定をダイアログに反映させたり、
ダイアログの設定を取得したいのですが出来ません。似たような処理として以下のような簡
単なテストプログラムを作りました。
Public Const GHND = &H42
Declare Function GlobalAlloc Lib "Kernel32.dll" (ByVal uFlags As Long, ByVal dwBytes As Long) As Long
Declare Function GlobalFree Lib "Kernel32.dll" (ByVal hMem As Long) As Long
Declare Function GlobalLock Lib "Kernel32.dll" (ByVal hMem As Long) As Long
Declare Function GlobalUnlock Lib "Kernel32.dll" (ByVal hMem As Long) As Long
Declare Sub CopyMemory Lib "Kernel32.dll" Alias "RtlMoveMemory" (ByRef lpDest As Any, _
ByRef lpSrc As Any, ByVal lSize As Long)
Type TestMM
lNum1 As Long
lNum2 As Long
End Type
Sub TestCopyMemory()
Dim oMM As TestMM
Dim hGAlloc As Long, hGLock As Long
Dim lBytes As Long
lBytes = Len(oMM)
hGAlloc = GlobalAlloc(uFlags:=GHND, dwBytes:=lBytes )
If hGAlloc <> 0 Then
hGLock = GlobalLock(hMem:=hGAlloc)
If hGLock <> 0 Then
oMM.lNum1 = 1
oMM.lNum2 = 2
Call CopyMemory(lpDest:=hGLock, lpSrc:=oMM, lSize:=lBytes)
Call GlobalUnlock(hMem:=hGLock)
End If
Call GlobalFree(hMem:=hGAlloc)
End If
End Sub
このプログラムでCopyMemoryを実行すると、変数hGLockの値が変わってしまうのです。
具体的にはhGLockにはアドレスの値が入ってるハズなのですが、"2"になってしまい、
hGAllocの値が"1"になってしまうのです。
hGLockはLong型であり、変数へコピーしているのだからサイズ的にTestMM構造体の4バ
イト分がコピーされてしまうと考えれば納得できます。でも、他のサイトと見てもこ
のような方法で確保したメモリにコピーできるようなのです。
私はCopyMemoryの第1と第2引数の型のAnyがよく分かっていません。
CopyMemoryの引数に渡された変数のアドレスで内部処理をしているような考えなのです
が、第1引数に渡されるLong型がそのままアドレスとして認識されるのであれば、その
アドレスが指し示す領域に指定したサイズのデータがコピーされると思います。
VisualBasicの掲示板にVBAの質問で申し訳ないのですが、出来れば答えてもらえないで
しょうか。
そのコードだと、メモリ破壊を引き起こしてしまうかと。
> lBytes = Len(oMM)
これは 8 バイトになっていますよね。
> oMM.lNum1 = 1
> oMM.lNum2 = 2
そしてこの時点で、oMM が指すメモリは
01 00 00 00 02 00 00 00
という 8 バイトのデータになります。
> Call CopyMemory(lpDest:=hGLock, lpSrc:=oMM, lSize:=lBytes)
ここに問題があります。
hGLock という値は Long 型です。4 バイト分の領域しかないにも関わらず、
そこに 8 バイトのデータを書き込もうとしている事になります。
残りの 4 バイトは、変数 hGLock 以外の領域に対して書き込まれる事になります。
今回は、それがたまたま hGAlloc に書き込まれてしまった、という事でしょう。
> このプログラムでCopyMemoryを実行すると、変数hGLockの値が変わってしまうのです。
> 具体的にはhGLockにはアドレスの値が入ってるハズなのですが、"2"になってしまい、
> hGAllocの値が"1"になってしまうのです。
それは、hGLock と hGAlloc の変数領域が、隣あうメモリ空間に配置されていたためです。
変数宣言が
Dim hGAlloc As Long, hGLock As Long
ではなく
Dim dummy1 As Long
Dim hGAlloc As Long
Dim dummy2 As Long
Dim hGLock As Long
Dim dummy3 As Long
になっていれば、hGLock = 1、dummy2 = 2 となるかも知れません。
何にしても、他の変数領域にデータを書き込んでしまっている以上、
CopyMemory API の使い方が間違っている、という事になりますね。
> 第1引数に渡されるLong型がそのままアドレスとして認識されるのであれば、その
> アドレスが指し示す領域に指定したサイズのデータがコピーされると思います。
今のコードは、「hGLock が指し示すアドレス」に対して書き込むのではなく、
「hGLock そのもの」に対して書き込むコードになっています。
アドレスに対して書き込みたいのであれば、API の呼び出し部分を
Call CopyMemory(lpDest:=ByVal hGLock, lpSrc:=oMM, lSize:=lBytes)
のように『ByVal』指定にするか、もしくは CopyMemory API の宣言部で、
第一引数を「ByVal Long」か「ByVal Any」にしておく必要があります。
(API 宣言が異なれば、API の呼び出し方も変わる事に注意してください)
> でも、他のサイトと見てもこのような方法で確保したメモリにコピーできるようなのです。
ByVal/ByRef の扱いに誤りが無いか、もう一度確認してみてください。
早速の回答ありがとうございます。
ご指摘のようにCopyMemoryの第1引数の宣言を「ByVal Any」にすると
うまくいきました。私の注意不足でした。
「ByRef」とした場合は参照渡しになるということは理解してるつもり
です。
「ByVal」とした場合は値渡しになり、CopyMemoryの場合は第1引数が
指し示すアドレスへ指定サイズ分コピーするということになるのでし
ょうか?
また、第2引数はアドレスではなく構造体を渡していますが、これは
どのように考えればよいのでしょうか?
最初の質問のテストプログラムにCopyMemory2を追加をして以下のよう
なプログラムにしました。
Public Const GHND = &H42
Declare Function GlobalAlloc Lib "Kernel32.dll" (ByVal uFlags As Long, ByVal dwBytes As Long) As Long
Declare Function GlobalFree Lib "Kernel32.dll" (ByVal hMem As Long) As Long
Declare Function GlobalLock Lib "Kernel32.dll" (ByVal hMem As Long) As Long
Declare Function GlobalUnlock Lib "Kernel32.dll" (ByVal hMem As Long) As Long
Declare Sub CopyMemory Lib "Kernel32.dll" Alias "RtlMoveMemory" (ByRef lpDest As Any, _
ByRef lpSrc As Any, ByVal lSize As Long)
Declare Sub CopyMemory2 Lib "Kernel32.dll" Alias "RtlMoveMemory" (ByRef lpDest As Any, _
ByVal lpSrc As Any, ByVal lSize As Long)
Type TestMM
lNum1 As Long
lNum2 As Long
End Type
Sub TestCopyMemory()
Dim oMM As TestMM, oMM2 As TestMM
Dim hGAlloc As Long, hGLock As Long
Dim lBytes As Long
lBytes = Len(oMM)
hGAlloc = GlobalAlloc(uFlags:=GHND, dwBytes:=lBytes )
If hGAlloc <> 0 Then
hGLock = GlobalLock(hMem:=hGAlloc)
If hGLock <> 0 Then
oMM.lNum1 = 1
oMM.lNum2 = 2
Call CopyMemory(lpDest:=hGLock, lpSrc:=oMM, lSize:=lBytes)
Call CopyMemory2(lpDest:=oMM2, lpSrc:=hGLock, lSize:=lBytes)
Call GlobalUnlock(hMem:=hGLock)
End If
Call GlobalFree(hMem:=hGAlloc)
End If
End Sub
このプログラムを実行するとoMM2には期待通り
oMM2.lNum1 に 1
oMM2.lNum2 に 2
が入ります。この掲示板などで調べると、CopyMemoryの引数の型によ
って宣言を変えないといけないようなのですが、そのような理解でよ
いのでしょうか?
すみません。
誤り Declare Sub CopyMemory Lib "Kernel32.dll" Alias "RtlMoveMemory" (ByRef lpDest As Any, _
ByRef lpSrc As Any, ByVal lSize As Long)
正 Declare Sub CopyMemory Lib "Kernel32.dll" Alias "RtlMoveMemory" (ByVal lpDest As Any, _
ByRef lpSrc As Any, ByVal lSize As Long)
ご存知かもしれませんが、『lngAddress = VarPtr(変数)』とすると、変数のアドレスが取得できます。
> 「ByRef」とした場合は参照渡しになるということは理解してるつもり
> です。
ByRef の場合、変数それ自体が読み書きの対象となります。
また、Declare 側で ByRef 定義してあった場合でも、呼び出し側で「ByVal」を
指定すると、その引数を値渡しとして扱う事が出来ます。
> 「ByVal」とした場合は値渡しになり、CopyMemoryの場合は第1引数が
> 指し示すアドレスへ指定サイズ分コピーするということになるのでし
> ょうか?
CopyMemory の第1引数は、コピー先の開始アドレスへのポインタを指定する場所です。
VB 側の定義が ByVal であれ ByRef であれ、Kernel32.DLL 側では、
ここに渡された値が示すアドレスに向かってコピーを行います。
Declare で≪ByVal Any≫とした場合、それは値渡しです。ここに「hGLock」を渡すと、
「hGLock 変数が指している値」を渡す事になります。hgLock に書かれている値は、
メモリブロックの先頭アドレスなので、そのメモリブロックに対して書き込まれます。
一方、≪ByRef≫であった場合、それは参照渡しとなるわけですが、ここに
「hGLock」を渡すと、「hGLock 変数のアドレス」が渡される事になります。
そのため、このアドレスに向かって書き込みが行われ、hGLock の内容が書き換えられます。
> また、第2引数はアドレスではなく構造体を渡していますが、これは
> どのように考えればよいのでしょうか?
構造体というか、ユーザー定義型ですね。
CopyMemory の第2引数は、コピー元のメモリブロックの開始アドレスへのポインタを指定する場所です。
Declare が ≪ByRef≫ であるため、ここに「oMM」を渡すと、「oMM 変数のアドレス」が
渡される事になります。この場合、そのアドレスが指し示す位置からの lBytes バイト分の
エリアが転送対象となります。
また、Declare を≪ByVal Long≫にして、ここに先の「VarPtr(oMM)」を渡す事でも
同じ効果が得られます。
# ちなみに、VBA では構造体を値渡しすることはできないため、
# ≪ByVal Any≫に対して oMM を直接渡すような事は出来ません。
> 最初の質問のテストプログラムにCopyMemory2を追加をして以下のよう
> なプログラムにしました。
今回は渡す型が決まっているので、CopyMemory は Any 型で宣言するのではなく、
それぞれを ByVal Long, ByRef TestMM, ByVal Long として Declare するのも手です。
> CopyMemoryの引数の型によって宣言を変えないといけないようなのですが、
今回のように、ポインタを受け取るような引数の場合には、
宣言を変えることも変えないこともできます。
変えるのであれば、Any は用いず、上記のように特定の型で宣言した方が良いでしょう。
変えない場合には、≪ByRef Any≫もしくは≪ByVal Long≫にしておけば OK です。
宣言が ByRef x As Any の場合は、変数を直接渡せば、その変数自体のポインタが渡されますし、
「ByVal lngValue」のように ByVal 付きで渡せば、そのLong変数が指すアドレスが渡されます。
宣言が ByVal x As Long の場合には、変数を直接渡せば、その変数が指すアドレスが渡されますし、
「VarPtr(lngValue)」のように呼び出せば、変数自体のポインタを渡すことができます。
大変解りやすい回答ありがとうございます。
いろいろ調べても、なんとなくしか解らなかったことが整理して理解できたと
思います。また、解らないことがありましたらよろしくお願いします。
ツイート | ![]() |