トップ回答者
WindowsAPI CopyMemory について

質問
-
質問します。 WinodowsAPIのCopyMemoryですが、RtlMoveMemoryと動作が同じと思って
コードを書いたのですが、下記の様に違った動作をしています。どこがまずいのでしょうか?
Option Explicit Private Declare PtrSafe Sub CopyMemory _ Lib "kernel32" Alias "RtlMoveMemory" _ (Destination As Any, Source As Any, ByVal Length As LongPtr) Private Declare PtrSafe Sub RtlMoveMemory Lib "kernel32.dll" _ (ByVal Destination As Any, ByVal Source As Any, ByVal Length As LongPtr) ' Win32API_PtrSafe.TXTにはない Sub M230111_214442() Const BASE = 1 Dim Target As Integer, cbInteger As Long, Source As LongPtr Dim DestinationA As LongPtr, DestinationB As LongPtr cbInteger = LenB(Target) Source = VarPtr(Target) Target = 3654 ' ReDim bufA(BASE To cbInteger) As Byte DestinationA = VarPtr(bufA(BASE)) CopyMemory DestinationA, Source, cbInteger ReDim bufB(BASE To cbInteger) As Byte DestinationB = VarPtr(bufB(BASE)) RtlMoveMemory DestinationB, Source, cbInteger Stop ' 結果 ' : cbInteger : 2 : Long ' : Source : 2820401392160^ : LongLong ' : DestinationA : 2820586072608^ : LongLong ' : DestinationB : 2820586048976^ : LongLong ' bufA(1) : 0 : Byte ' bufA(2) : 0 : Byte ' ' bufB(1) : 70 : Byte →&h46 ' bufB(2) : 14 : Byte →&hE hexToDec(E46)=3654 End Sub
以上よろしくお願いします。
回答
-
CopyMemoryの方の定義の第一、第二引数にByVal,ByRefが指定されていないのでByRef扱いとなってます。
そのため第一引数は変数DestinatinAのアドレスを渡し、第二引数は変数Sourceのアドレスを渡してます。
結果としてSourceの値の2バイトをDestinationAにコピーしてる処理になってます。Option Explicit '引数にByVal,ByRefが無い場合はByRefとして扱われる Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr) '間違った定義 Private Declare PtrSafe Sub CopyMemory_ByRef Lib "kernel32" Alias "RtlMoveMemory" (ByRef pDestination As LongPtr, ByRef pSource As LongPtr, ByVal Length As LongPtr) '正しい定義 Private Declare PtrSafe Sub CopyMemory_ByVal Lib "kernel32" Alias "RtlMoveMemory" (ByVal pDestination As LongPtr, ByVal pSource As LongPtr, ByVal Length As LongPtr) 'これも正しい。(最初の返信時に「ただし配列の途中などは渡せない」と書いたが、渡せるようだ」) Private Declare PtrSafe Sub CopyMemory_ByRefAny Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As LongPtr) Sub M230111_214442() Const BASE = 1 Dim Source As Integer Dim cbInteger As Long Dim pSource As LongPtr Dim pDestinationNon As LongPtr Dim pDestinationRef As LongPtr Dim pDestinationVal As LongPtr cbInteger = LenB(Source) pSource = VarPtr(Source) Source = &HE46 ReDim bufNon(BASE To BASE + LenB(Source) - 1) As Byte ReDim bufRef(BASE To BASE + LenB(Source) - 1) As Byte ReDim bufVal(BASE To BASE + LenB(Source) - 1) As Byte pDestinationNon = VarPtr(bufNon(BASE)) pDestinationRef = VarPtr(bufRef(BASE)) pDestinationVal = VarPtr(bufVal(BASE)) Debug.Print "未指定実行前" & vbTab & pDestinationNon CopyMemory pDestinationNon, pSource, cbInteger Debug.Print "未指定実行後" & vbTab & pDestinationNon '定義が間違っているのでpDestinationNonが変更されている If (&HE = bufNon(BASE + 1)) And (&H46 = bufNon(BASE)) Then Debug.Print "未指定 OK" Else Debug.Print "未指定 NG" End If Debug.Print ''' Debug.Print "ByRef 実行前" & vbTab & pDestinationRef CopyMemory_ByRef pDestinationRef, pSource, cbInteger Debug.Print "ByRef 実行前" & vbTab & pDestinationRef '定義が間違っているのでpDestinationRefが変更されている If (&HE = bufRef(BASE + 1)) And (&H46 = bufRef(BASE)) Then Debug.Print "ByRef OK" Else Debug.Print "ByRef NG" End If Debug.Print ''' Debug.Print "ByVal 実行前" & vbTab & pDestinationVal; "" CopyMemory_ByVal pDestinationVal, pSource, cbInteger Debug.Print "ByVal 実行前" & vbTab & pDestinationVal; "" If (&HE = bufVal(BASE + 1)) And (&H46 = bufVal(BASE)) Then Debug.Print "ByVal OK" Else Debug.Print "ByVal NG" End If Debug.Print ''' Dim dest As Integer CopyMemory_ByRefAny dest, Source, cbInteger If dest = Source Then Debug.Print "ByRefAny OK" Else Debug.Print "ByRefAny NG" End If End Sub
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
すべての返信
-
CopyMemoryの方の定義の第一、第二引数にByVal,ByRefが指定されていないのでByRef扱いとなってます。
そのため第一引数は変数DestinatinAのアドレスを渡し、第二引数は変数Sourceのアドレスを渡してます。
結果としてSourceの値の2バイトをDestinationAにコピーしてる処理になってます。Option Explicit '引数にByVal,ByRefが無い場合はByRefとして扱われる Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr) '間違った定義 Private Declare PtrSafe Sub CopyMemory_ByRef Lib "kernel32" Alias "RtlMoveMemory" (ByRef pDestination As LongPtr, ByRef pSource As LongPtr, ByVal Length As LongPtr) '正しい定義 Private Declare PtrSafe Sub CopyMemory_ByVal Lib "kernel32" Alias "RtlMoveMemory" (ByVal pDestination As LongPtr, ByVal pSource As LongPtr, ByVal Length As LongPtr) 'これも正しい。(最初の返信時に「ただし配列の途中などは渡せない」と書いたが、渡せるようだ」) Private Declare PtrSafe Sub CopyMemory_ByRefAny Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As LongPtr) Sub M230111_214442() Const BASE = 1 Dim Source As Integer Dim cbInteger As Long Dim pSource As LongPtr Dim pDestinationNon As LongPtr Dim pDestinationRef As LongPtr Dim pDestinationVal As LongPtr cbInteger = LenB(Source) pSource = VarPtr(Source) Source = &HE46 ReDim bufNon(BASE To BASE + LenB(Source) - 1) As Byte ReDim bufRef(BASE To BASE + LenB(Source) - 1) As Byte ReDim bufVal(BASE To BASE + LenB(Source) - 1) As Byte pDestinationNon = VarPtr(bufNon(BASE)) pDestinationRef = VarPtr(bufRef(BASE)) pDestinationVal = VarPtr(bufVal(BASE)) Debug.Print "未指定実行前" & vbTab & pDestinationNon CopyMemory pDestinationNon, pSource, cbInteger Debug.Print "未指定実行後" & vbTab & pDestinationNon '定義が間違っているのでpDestinationNonが変更されている If (&HE = bufNon(BASE + 1)) And (&H46 = bufNon(BASE)) Then Debug.Print "未指定 OK" Else Debug.Print "未指定 NG" End If Debug.Print ''' Debug.Print "ByRef 実行前" & vbTab & pDestinationRef CopyMemory_ByRef pDestinationRef, pSource, cbInteger Debug.Print "ByRef 実行前" & vbTab & pDestinationRef '定義が間違っているのでpDestinationRefが変更されている If (&HE = bufRef(BASE + 1)) And (&H46 = bufRef(BASE)) Then Debug.Print "ByRef OK" Else Debug.Print "ByRef NG" End If Debug.Print ''' Debug.Print "ByVal 実行前" & vbTab & pDestinationVal; "" CopyMemory_ByVal pDestinationVal, pSource, cbInteger Debug.Print "ByVal 実行前" & vbTab & pDestinationVal; "" If (&HE = bufVal(BASE + 1)) And (&H46 = bufVal(BASE)) Then Debug.Print "ByVal OK" Else Debug.Print "ByVal NG" End If Debug.Print ''' Dim dest As Integer CopyMemory_ByRefAny dest, Source, cbInteger If dest = Source Then Debug.Print "ByRefAny OK" Else Debug.Print "ByRefAny NG" End If End Sub
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
-
'これも正しい。ただし配列の途中などは渡せない Private Declare PtrSafe Sub CopyMemory_ByRefAny Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef pSource As Any, ByVal Length As LongPtr)
配列の途中を渡すと、何かマズイんでしたっけ?
Dim src() As Integer Dim dst() As Integer Dim n As Long Dim s1 As String, s2 As String ReDim src(20), dst(20) For n = 0 To 20 src(n) = 1000 + n dst(n) = 2000 + n Next 'src(12~17)の6要素を dst(3~8) に転写 CopyMemory_ByRefAny dst(3), src(12), 6 * CLngPtr(LenB(src(0))) ' src= 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011,[1012, 1013, 1014, 1015, 1016, 1017],1018, 1019, 1020 ' dst= 2000, 2001, 2002,[1012, 1013, 1014, 1015, 1016, 1017],2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 s1 = "": s2 = "" For n = 0 To 20 s1 = s1 & ("," & CStr(src(n))) s2 = s2 & ("," & CStr(dst(n))) Next s1 = Mid(s1, 2) s2 = Mid(s2, 2) Debug.Print "src="; s1 Debug.Print "dst="; s2 'src(0~2)の3要素を dst(17~19) に転写 CopyMemory_ByRefAny ByVal VarPtr(dst(17)), ByVal VarPtr(src(0)), 3 * CLngPtr(LenB(src(0))) ' src=<1000, 1001, 1002>,1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020 ' dst= 2000, 2001, 2002,[1012, 1013, 1014, 1015, 1016, 1017,]2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,<1000, 1001, 1002>,2020 s1 = "": s2 = "" For n = 0 To 20 s1 = s1 & ("," & CStr(src(n))) s2 = s2 & ("," & CStr(dst(n))) Next s1 = Mid(s1, 2) s2 = Mid(s2, 2) Debug.Print "src="; s1 Debug.Print "dst="; s2
-
回答ありがとうございました。
追加で質問お願いします。
⓵Win32API_PtrSafe.TXT からコピペすると
Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory"
(Destination As Any, Source As Any, ByVal Length As LongPtr)
です。これは、ByValを宣言していないのですけど、何か、意図があるのでしょうか?
当方、なんの疑いもなくそのままだったのですが。
当該ファイルでByrefを検索すると宣言しているのは2関数だけでした。
今回の件で明らかとは思うのですが、
ByVal,Byref どちらを選ぶかは、ユーザー責任であって限定でいないという、意味でしょうか?
⓶「Sourceの値の2バイトをDestinationAにコピーしてる処理」
の部分が余り納得できていない。DestinationAの値が変わるのが不思議です。
一応検証してみました。がまだポカンとしています。
Option Explicit #Const BYVALUE = 1 #If BYVALUE = 0 Then Private Declare PtrSafe Sub CopyMemory _ Lib "kernel32" Alias "RtlMoveMemory" _ (ByVal Destination As Any, ByVal Source As Any, ByVal Length As LongPtr) #Else Private Declare PtrSafe Sub CopyMemory _ Lib "kernel32" Alias "RtlMoveMemory" _ (Destination As Any, ByVal Source As Any, ByVal Length As LongPtr) #End If Private Const BASE = 1 Private Const VALUE=4972 Private A As Integer Private pA As LongPtr Private ppA As LongPtr Private cbA As Integer Private cbLP As Integer Private pB As LongPtr Private ppB As LongPtr Private Buf() As Byte Sub Macro1() Initialize PrintDump "BeforeCopyMemory" CopyMemory pB, pA, cbA PrintDump "AfterCopyMemory" '結果 ' BeforeCopyMemory ' A::2392F307EF0 : 6C 13 : →4972 ' pA::2392F307EF8 : F0 7E 30 2F 39 02 00 00 : →2444628098800 ' B::23935058960 : 00 00 : →0 ' pB::2392F307F10 : 60 89 05 35 39 02 00 00 : →2444725946720 ' AfterCopyMemory ' A::2392F307EF0 : 6C 13 : →4972 ' pA::2392F307EF8 : F0 7E 30 2F 39 02 00 00 : →2444628098800 ' B::2393505136C : 7A A2 : →41594 ' pB::2392F307F10 : 6C 13 05 35 39 02 00 00 : →2444725916524 End Sub Private Sub Dump(ByVal aTarget As LongPtr, ByVal aSize As Long, astrVarName As String) With New TMemoryDumper .Initialize aTarget, aSize Debug.Print astrVarName & "::" & .AddressHex & " : " & .StrHex & " : →" & .GetValueDec.ValueDec End With End Sub Private Sub PrintDump(aTitle As String) Debug.Print aTitle Dump pA, cbA, "A" Dump ppA, cbLP, "pA" Dump pB, cbA, "B" Dump ppB, cbLP, "pB" Debug.Print End Sub Private Sub Initialize() A = VALUE pA = VarPtr(A) ppA = VarPtr(pA) cbA = LenB(A) cbLP = LenB(pA) ppB = VarPtr(pB) ReDim Buf(BASE To cbA) pB = VarPtr(Buf(BASE)) End Sub
変数Aの値 6C 13 が 配列のアドレス値pBの頭の2バイトに置き換わっていることが確認できました。
Byrefの場合アドレス値そのものを変更して今うのですね。かなりやばいですよね