文件操作源码分享

VB 文件目录操作变动即时监视器(重命名、新建、删除)监控

有时候需要监控某个指定的目录下的文件/文件夹操作记录,有不少群友们建议用定时器定时分析比较,这是比较累的做法。

其实操作系统提供了一些 api 可以实现这个需求,通过分析系统消息可以实时获取到文件系统的变化,从而取得需要的信息。

本项目来自 newxing,总共有一个模块文件,一个类模块文件,一个窗体文件,

其中窗体上放置一个 listbox控件,和一个按钮控件。然后引入这个 bas 和 cls 模块文件,就可以调用了,最终要写的业务代码就图中几行:看效果

哦,对了,还需要引用这两个东西:(其中 tlb 文件在本文底部可以下载)

以下给出源码:

模块代码:

modMonitorFolder.bas

'Download by http://www.NewXing.com
'保存接管之前的值
Public preMonitorFolderProc As Long

'保存热键对象句柄
Public objMonitorFolder As Long

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cBytes As Long)
Private Declare Function GetFullPathName Lib "kernel32" Alias "GetFullPathNameA" (ByVal lpFileName As String, ByVal nBufferLength As Long, ByVal lpBuffer As String, ByVal lpFilePart As String) As Long
Private Declare Function SHGetDesktopFolder Lib "shell32.dll" (ppshf As Folder) As Long

Private Const WM_NCDESTROY = &H82
Private Const GWL_WNDPROC = (-4)
Private Const OLDWNDPROC = "OldWndProc"

Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal _
        hWnd As Long, ByVal lpString As String) As Long
Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal _
        hWnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal _
        hWnd As Long, ByVal lpString As String) As Long

Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
        (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
        (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal uMsg As Long, _
        ByVal wParam As Long, ByVal lParam As Long) As Long

Private Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long)

Private Const MAX_PATH = 260

Private Enum SHSpecialFolderIDs      '列出所有Windows下特殊文件夹的ID
    CSIDL_DESKTOP = &H0
    CSIDL_INTERNET = &H1
    CSIDL_PROGRAMS = &H2
    CSIDL_CONTROLS = &H3
    CSIDL_PRINTERS = &H4
    CSIDL_PERSONAL = &H5
    CSIDL_FAVORITES = &H6
    CSIDL_STARTUP = &H7
    CSIDL_RECENT = &H8
    CSIDL_SENDTO = &H9
    CSIDL_BITBUCKET = &HA
    CSIDL_STARTMENU = &HB
    CSIDL_DESKTOPDIRECTORY = &H10
    CSIDL_DRIVES = &H11
    CSIDL_NETWORK = &H12
    CSIDL_NETHOOD = &H13
    CSIDL_FONTS = &H14
    CSIDL_TEMPLATES = &H15
    CSIDL_COMMON_STARTMENU = &H16
    CSIDL_COMMON_PROGRAMS = &H17
    CSIDL_COMMON_STARTUP = &H18
    CSIDL_COMMON_DESKTOPDIRECTORY = &H19
    CSIDL_APPDATA = &H1A
    CSIDL_PRINTHOOD = &H1B
    CSIDL_ALTSTARTUP = &H1D
    CSIDL_COMMON_ALTSTARTUP = &H1E
    CSIDL_COMMON_FAVORITES = &H1F
    CSIDL_INTERNET_CACHE = &H20
    CSIDL_COOKIES = &H21
    CSIDL_HISTORY = &H22
End Enum

Private Declare Function SHGetFileInfoPidl Lib "shell32" Alias "SHGetFileInfoA" _
                              (ByVal pidl As Long, _
                              ByVal dwFileAttributes As Long, _
                              psfib As SHFILEINFOBYTE, _
                              ByVal cbFileInfo As Long, _
                              ByVal uFlags As SHGFI_flags) As Long

Private Type SHFILEINFOBYTE
    hIcon As Long
    iIcon As Long
    dwAttributes As Long
    szDisplayName(1 To MAX_PATH) As Byte
    szTypeName(1 To 80) As Byte
End Type

Enum SHGFI_flags
    SHGFI_LARGEICON = &H0
    SHGFI_SMALLICON = &H1
    SHGFI_OPENICON = &H2
    SHGFI_SHELLICONSIZE = &H4
    SHGFI_PIDL = &H8
    SHGFI_USEFILEATTRIBUTES = &H10
    SHGFI_ICON = &H100
    SHGFI_DISPLAYNAME = &H200
    SHGFI_TYPENAME = &H400
    SHGFI_ATTRIBUTES = &H800
    SHGFI_ICONLOCATION = &H1000
    SHGFI_EXETYPE = &H2000
    SHGFI_SYSICONINDEX = &H4000
    SHGFI_LINKOVERLAY = &H8000
    SHGFI_SELECTED = &H10000
End Enum

Private m_hSHNotify As Long     '系统消息通告句柄
Private m_PathPIDL As Long      '被监视目录的PIDL

'定义系统通告的消息值
Private Const WM_SHNOTIFY = &H401

Private Type PIDLSTRUCT
    pidl As Long
    bWatchSubFolders As Long
End Type

Private Declare Function SHChangeNotifyRegister Lib "shell32" Alias "#2" _
                              (ByVal hWnd As Long, _
                              ByVal uFlags As SHCN_ItemFlags, _
                              ByVal dwEventID As SHCN_EventIDs, _
                              ByVal uMsg As Long, _
                              ByVal cItems As Long, _
                              lpps As PIDLSTRUCT) As Long

Private Declare Function SHChangeNotifyDeregister Lib "shell32" Alias "#4" _
        (ByVal hNotify As Long) As Boolean

Private Enum SHCN_EventIDs
    SHCNE_RENAMEITEM = &H1
    SHCNE_CREATE = &H2
    SHCNE_DELETE = &H4
    SHCNE_MKDIR = &H8
    SHCNE_RMDIR = &H10
    SHCNE_MEDIAINSERTED = &H20
    SHCNE_MEDIAREMOVED = &H40
    SHCNE_DRIVEREMOVED = &H80
    SHCNE_DRIVEADD = &H100
    SHCNE_NETSHARE = &H200
    SHCNE_NETUNSHARE = &H400
    SHCNE_ATTRIBUTES = &H800
    SHCNE_UPDATEDIR = &H1000
    SHCNE_UPDATEITEM = &H2000
    SHCNE_SERVERDISCONNECT = &H4000
    SHCNE_UPDATEIMAGE = &H8000&
    SHCNE_DRIVEADDGUI = &H10000
    SHCNE_RENAMEFOLDER = &H20000
    SHCNE_FREESPACE = &H40000
    SHCNE_ASSOCCHANGED = &H8000000

    SHCNE_DISKEVENTS = &H2381F
    SHCNE_GLOBALEVENTS = &HC0581E0
    SHCNE_ALLEVENTS = &H7FFFFFFF
    SHCNE_INTERRUPT = &H80000000
End Enum

Private Enum SHCN_ItemFlags
    SHCNF_IDLIST = &H0
    SHCNF_PATHA = &H1
    SHCNF_PRINTERA = &H2
    SHCNF_DWORD = &H3
    SHCNF_PATHW = &H5
    SHCNF_PRINTERW = &H6
    SHCNF_TYPE = &HFF
    SHCNF_FLUSH = &H1000
    SHCNF_FLUSHNOWAIT = &H2000

    #If UNICODE Then
        SHCNF_PATH = SHCNF_PATHW
        SHCNF_PRINTER = SHCNF_PRINTERW
    #Else
        SHCNF_PATH = SHCNF_PATHA
        SHCNF_PRINTER = SHCNF_PRINTERA
    #End If
End Enum

Function SHNotify_Register(ByVal hWnd As Long, ByVal sMonitorPath As String, Optional ByVal bWatchSubFolder As Boolean = True) As Boolean
    Dim PS As PIDLSTRUCT
  
    If (m_hSHNotify = 0) Then
          
        '获得被监视目录的PIDL
        m_PathPIDL = GetPIDLFromPath(sMonitorPath)
        If m_PathPIDL Then
      
            PS.pidl = m_PathPIDL
            PS.bWatchSubFolders = True
      
        
            '注册Windows监视,将获得的句柄保存到m_hSHNotify中
            m_hSHNotify = SHChangeNotifyRegister(hWnd, SHCNF_TYPE Or SHCNF_IDLIST, _
                                            SHCNE_ALLEVENTS Or SHCNE_INTERRUPT, _
                                            WM_SHNOTIFY, 1, PS)
                                            
            SHNotify_Register = CBool(m_hSHNotify)
    
        Else
            Call CoTaskMemFree(m_PathPIDL)
        End If
        
    End If
    
End Function

Function SHNotify_Unregister() As Boolean
    If m_hSHNotify Then
        If SHChangeNotifyDeregister(m_hSHNotify) Then
            m_hSHNotify = 0
            Call CoTaskMemFree(m_PathPIDL)
            m_PathPIDL = 0
            SHNotify_Unregister = True
        End If
        
    End If
    
End Function

Private Sub NotificationReceipt(wParam As Long, lParam As Long)
End Sub

Private Function GetPIDLFromPath(ByVal sPath As String) As Long
    Dim ISF As IShellFolder
    Dim pidlMain     As Long
    Dim cParsed     As Long
    Dim afItem     As Long
    Dim lFilePos     As Long
    Dim lR     As Long
    Dim sRet     As String * 255
      
    lR = GetFullPathName(sPath, MAX_PATH, sRet, lFilePos)
    sPath = Left$(sRet, lR)
    
    '将路径名称转换成PIDL
    Set ISF = GetDesktopFolder
    
    Call ISF.ParseDisplayName(0&, 0&, StrConv(sPath, vbUnicode), cParsed, pidlMain, afItem)
    GetPIDLFromPath = pidlMain
                      
End Function

Private Function GetDesktopFolder() As IShellFolder
    SHGetDesktopFolder GetDesktopFolder
    
End Function
     
Function GetDisplayNameFromPIDL(pidl As Long) As String
    Dim sfib As SHFILEINFOBYTE
    If SHGetFileInfoPidl(pidl, 0, sfib, Len(sfib), SHGFI_PIDL Or SHGFI_DISPLAYNAME Or SHGFI_TYPENAME Or SHGFI_USEFILEATTRIBUTES Or SHGFI_ICON Or SHGFI_DISPLAYNAME Or SHGFI_TYPENAME Or SHGFI_ATTRIBUTES Or SHGFI_EXETYPE) Then
        GetDisplayNameFromPIDL = GetStrFromBufferA(StrConv(sfib.szDisplayName, vbUnicode))
    
    End If

End Function

Private Function GetStrFromBufferA(sz As String) As String
    If InStr(sz, vbNullChar) Then
        GetStrFromBufferA = Left$(sz, InStr(sz, vbNullChar) - 1)
    Else
        GetStrFromBufferA = sz
    End If
    
End Function

Public Function SubClass(hWnd As Long) As Boolean
    Dim lpfnOld As Long
    Dim fSuccess As Boolean
  
    If (GetProp(hWnd, OLDWNDPROC) = 0) Then
        lpfnOld = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf WndProc)
        If lpfnOld Then
            fSuccess = SetProp(hWnd, OLDWNDPROC, lpfnOld)
        End If
    End If
  
    If fSuccess Then
        SubClass = True
    Else
        If lpfnOld Then Call UnSubClass(hWnd)
        MsgBox "Unable to successfully subclass &H" & Hex(hWnd), vbCritical
    End If
    
End Function

Public Function UnSubClass(hWnd As Long) As Boolean
    Dim lpfnOld As Long
  
    lpfnOld = GetProp(hWnd, OLDWNDPROC)
    If lpfnOld Then
        If RemoveProp(hWnd, OLDWNDPROC) Then
            UnSubClass = SetWindowLong(hWnd, GWL_WNDPROC, lpfnOld)
        End If
    End If
End Function

Private Function WndProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As _
        Long, ByVal lParam As Long) As Long
    Select Case uMsg
        Case WM_SHNOTIFY        '处理系统消息通告函数
            '返回热键的ID
            ptrMonitorFolder.FireEvent wParam, lParam

'            Call NotificationReceipt(wParam, lParam)
        
        Case WM_NCDESTROY
            Call UnSubClass(hWnd)
    
    End Select
    
    WndProc = CallWindowProc(GetProp(hWnd, OLDWNDPROC), hWnd, uMsg, wParam, lParam)
    
End Function

'处理目录监视指针问题
Private Function ptrMonitorFolder() As cyMonitorFolder
    
    Dim MF As cyMonitorFolder
    CopyMemory MF, objMonitorFolder, 4&
    Set ptrMonitorFolder = MF
    CopyMemory MF, 0&, 4&
    
End Function

类模块代码:

cyMonitorFolder.cls

Option Explicit
'Download by http://www.NewXing.com
'保存目录监视接收窗口
Dim m_iMonitorFolderHwnd As Long

'激活的事件
Public Event cyFolderChangeEvent(ByVal sEventString As String, ByVal sFile1 As String, ByVal sFile2 As String)

Private Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, _
        pSource As Any, ByVal dwLength As Long)

Private Type SHNOTIFYSTRUCT
    dwItem1 As Long
    dwItem2 As Long
End Type

Public Sub cySetMonitorFolder(ByVal hWnd As Long, ByVal sMonitorPath As String, Optional ByVal bWatchSubFolder As Boolean = False)
    '保存目录监视接收窗口
    m_iMonitorFolderHwnd = hWnd
    
    '获得本身的objprt
    modMonitorFolder.objMonitorFolder = ObjPtr(Me)

    If SubClass(hWnd) Then  '改变Form1的消息处理函数
        Call SHNotify_Register(m_iMonitorFolderHwnd, sMonitorPath, bWatchSubFolder)
        
    End If

End Sub

Private Sub Class_Terminate()
    Call SHNotify_Unregister
    Call UnSubClass(m_iMonitorFolderHwnd)

End Sub

'激活事件
Friend Function FireEvent(ByVal wParam As Long, ByVal lParam As Long)
    Dim SHNS As SHNOTIFYSTRUCT
    Dim sDisplayName1 As String
    Dim sDisplayName2 As String
    
    Const SHCNE_RENAMEITEM = &H1
    Const SHCNE_CREATE = &H2
    Const SHCNE_DELETE = &H4
    Const SHCNE_MKDIR = &H8
    Const SHCNE_RMDIR = &H10
    Const SHCNE_MEDIAINSERTED = &H20
    Const SHCNE_MEDIAREMOVED = &H40
    Const SHCNE_DRIVEREMOVED = &H80
    Const SHCNE_DRIVEADD = &H100
    Const SHCNE_NETSHARE = &H200
    Const SHCNE_UPDATEDIR = &H1000
    Const SHCNE_UPDATEITEM = &H2000
    Const SHCNE_SERVERDISCONNECT = &H4000
    Const SHCNE_UPDATEIMAGE = &H8000&
    Const SHCNE_DRIVEADDGUI = &H10000
    Const SHCNE_RENAMEFOLDER = &H20000
    Const SHCNE_FREESPACE = &H40000
    Const SHCNE_ASSOCCHANGED = &H8000000
    
    MoveMemory SHNS, ByVal wParam, Len(SHNS)
      
    If SHNS.dwItem1 Then
        sDisplayName1 = GetDisplayNameFromPIDL(SHNS.dwItem1)
    End If
    
    If SHNS.dwItem2 Then
        sDisplayName2 = GetDisplayNameFromPIDL(SHNS.dwItem2)
    End If
    
    Select Case lParam
        Case SHCNE_RENAMEITEM: RaiseEvent cyFolderChangeEvent("重命名文件", sDisplayName1, sDisplayName2)
        Case SHCNE_CREATE: RaiseEvent cyFolderChangeEvent("建立文件 文件名", sDisplayName1, sDisplayName2)
        Case SHCNE_DELETE: RaiseEvent cyFolderChangeEvent("删除文件 文件名", sDisplayName1, sDisplayName2)
        Case SHCNE_MKDIR: RaiseEvent cyFolderChangeEvent("新建目录 目录名", sDisplayName1, sDisplayName2)
        Case SHCNE_RMDIR: RaiseEvent cyFolderChangeEvent("删除目录 目录名", sDisplayName1, sDisplayName2)
        Case SHCNE_MEDIAINSERTED: RaiseEvent cyFolderChangeEvent("插入可移动存储介质", sDisplayName1, sDisplayName2)
        Case SHCNE_MEDIAREMOVED: RaiseEvent cyFolderChangeEvent("移去可移动存储介质", sDisplayName1, sDisplayName2)
        Case SHCNE_DRIVEREMOVED: RaiseEvent cyFolderChangeEvent("移去驱动器", sDisplayName1, sDisplayName2)
        Case SHCNE_DRIVEADD: RaiseEvent cyFolderChangeEvent("添加驱动器", sDisplayName1, sDisplayName2)
        Case SHCNE_NETSHARE: RaiseEvent cyFolderChangeEvent("改变目录的共享属性", sDisplayName1, sDisplayName2)
        Case SHCNE_UPDATEDIR: RaiseEvent cyFolderChangeEvent("更新目录", sDisplayName1, sDisplayName2)
        Case SHCNE_UPDATEITEM: RaiseEvent cyFolderChangeEvent("更新文件 文件名", sDisplayName1, sDisplayName2)
        Case SHCNE_SERVERDISCONNECT: RaiseEvent cyFolderChangeEvent("断开与服务器的连", sDisplayName1, sDisplayName2)
        Case SHCNE_UPDATEIMAGE: RaiseEvent cyFolderChangeEvent("SHCNE_UPDATEIMAGE", sDisplayName1, sDisplayName2)
        Case SHCNE_DRIVEADDGUI: RaiseEvent cyFolderChangeEvent("SHCNE_DRIVEADDGUI", sDisplayName1, sDisplayName2)
        Case SHCNE_RENAMEFOLDER: RaiseEvent cyFolderChangeEvent("重命名文件夹", sDisplayName1, sDisplayName2)
        Case SHCNE_FREESPACE: RaiseEvent cyFolderChangeEvent("磁盘空间大小改变", sDisplayName1, sDisplayName2)
        Case SHCNE_ASSOCCHANGED: RaiseEvent cyFolderChangeEvent("改变文件关联", sDisplayName1, sDisplayName2)
        
    End Select
    
End Function

窗体代码:

frmMain.frm

Public sOut1 As String, s_flg As Integer
Dim WithEvents MF As cyMonitorFolder
'Download by http://www.NewXing.com
Private Sub Command1_Click()
    Set MF = New cyMonitorFolder
    MF.cySetMonitorFolder Me.hWnd, "d:\a"
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Call SHNotify_Unregister
    Call UnSubClass(hWnd)
End Sub

Private Sub MF_cyFolderChangeEvent(ByVal sEventString As String, ByVal sFile1 As String, ByVal sFile2 As String)
    List1.AddItem Now & vbTab & sEventString & ":" & sFile1 & "=》" & sFile2, 0
End Sub

这里就是调用示例了,使用非常简单,引入前面的2个模块代码之后,在业务代码这里的窗体按钮事件里面启动监控,并设置好需要监控的目录地址,就完事了,

接下来该目录下的任何文件变动都会在类模块的事件里面触发,此示例演示了把变动的信息输出到窗体的 listbox 里面。

本项目用到的tlb文件


阿鬼建议把模块文件合并到类模块里面,有时间可以整理试试:

把回调指向类的成员函数,就可以干掉模块了
还能实现事件,何乐而不为呢

定格建议使用更简单的 api 来实现:

“FindFirstChangeNotification”
“FindNextChangeNotification”

这里我用 ai 拿了一个示例代码,未经测试:
使用 API 函数在 VB6 中实现目录更改监控 – VB6.PRO
https://vb6.pro/vi/677


另外有一个函数也可以实现,不过较为复杂

使用vb6写一个调用 ReadDirectoryChangesW 实现目录更改监控的示例 – VB6.PRO
https://vb6.pro/vi/680

Views: 111

Hi, I’m 邓伟

本来无一物,何处惹尘埃

发表回复