none
WindowsAPI CopyMemory について RRS feed

  • 質問

  • 質問します。 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
    

    以上よろしくお願いします。

    2023年1月11日 13:44

回答

  • 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!)

    • 回答としてマーク my_think 2023年1月12日 4:17
    • 編集済み gekkaMVP 2023年1月12日 11:02 ByRefAnyのコメント間違いの訂正と、配列の大きさ指定ミス訂正
    2023年1月11日 16:41

すべての返信

  • 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!)

    • 回答としてマーク my_think 2023年1月12日 4:17
    • 編集済み gekkaMVP 2023年1月12日 11:02 ByRefAnyのコメント間違いの訂正と、配列の大きさ指定ミス訂正
    2023年1月11日 16:41
  • 'これも正しい。ただし配列の途中などは渡せない
    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
    2023年1月12日 2:22
  • 配列の途中を渡すと、何かマズイんでしたっけ?

    もう一度確認しなおしてみましたが、配列のインデックス指定でも参照渡しできてますね。

    配列の値がとりだされて一時変数に格納されて、その一時変数への参照になってしまうと思ってましたが、勘違いだったようです。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2023年1月12日 11:00
  • 回答ありがとうございました。

    追加で質問お願いします。

    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の場合アドレス値そのものを変更して今うのですね。かなりやばいですよね


    2023年1月14日 7:18