最近在用java写监视文件系统的东东,特对C++和Java下的不同实现方法做一小结。
1.Java环境下
很多人都说用文件轮询HashTable,然后如何如何比较,这种方法效率极为低下,还会造成无谓的磁盘读写。好了JDK 7中提供了java.nio.file大家可以通过 WatchService 来实现对文件的事件的监听。千万记得在JDK 7下哈,现在的JDK7的预览版发布到 jdk-7-ea-bin-b84-windows-i586-18_feb_2010.exe了。有兴趣的哥们可以把这个程序拿下去学习学习。
该段代码来自于 Sun(准确的该说是Oracle了) http://java./docs/books/tutorial/essential/io/notification.html
/*
* Copyright 2008-2009 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Sun Microsystems nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKind.*;
import static java.nio.file.LinkOption.*;
import java.nio.file.attribute.*;
import java.io.*;
import java.util.*;
/**
* Example to watch a directory (or tree) for changes to files.
*/
public class WatchDir {
private final WatchService watcher;
private final Map<WatchKey,Path> keys;
private final boolean recursive;
private boolean trace = false;
@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event;
}
/**
* Register the given directory with the WatchService
*/
private void register(Path dir) throws IOException {
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
if (trace) {
FileRef prev = keys.get(key);
if (prev == null) {
System.out.format("register: %s\n", dir);
} else {
if (!dir.equals(prev)) {
System.out.format("update: %s -> %s\n", prev, dir);
}
}
}
keys.put(key, dir);
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private void registerAll(final Path start) throws IOException {
// register directory and sub-directories
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir) {
try {
register(dir);
} catch (IOException x) {
throw new IOError(x);
}
return FileVisitResult.CONTINUE;
}
});
}
/**
* Creates a WatchService and registers the given directory
*/
WatchDir(Path dir, boolean recursive) throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<WatchKey,Path>();
this.recursive = recursive;
if (recursive) {
System.out.format("Scanning %s ...\n", dir);
registerAll(dir);
System.out.println("Done.");
} else {
register(dir);
}
// enable trace after initial registration
this.trace = true;
}
/**
* Process all events for keys queued to the watcher
*/
void processEvents() {
for (;;) {
// wait for key to be signalled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
Path dir = keys.get(key);
if (dir == null) {
System.err.println("WatchKey not recognized!!");
continue;
}
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (kind == OVERFLOW) {
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> ev = cast(event);
Path name = ev.context();
Path child = dir.resolve(name);
// print out event
System.out.format("%s: %s\n", event.kind().name(), child);
// if directory is created, and watching recursively, then
// register it and its sub-directories
if (recursive && (kind == ENTRY_CREATE)) {
try {
if (Attributes.readBasicFileAttributes(child, NOFOLLOW_LINKS).isDirectory()) {
registerAll(child);
}
} catch (IOException x) {
// ignore to keep sample readbale
}
}
}
// reset key and remove from set if directory no longer accessible
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
// all directories are inaccessible
if (keys.isEmpty()) {
break;
}
}
}
}
static void usage() {
System.err.println("usage: java WatchDir [-r] dir");
System.exit(-1);
}
public static void main(String[] args) throws IOException {
// parse arguments
if (args.length == 0 || args.length > 2)
usage();
boolean recursive = false;
int dirArg = 0;
if (args[0].equals("-r")) {
if (args.length < 2)
usage();
recursive = true;
dirArg++;
}
// register directory and process its events
Path dir = Paths.get(args[dirArg]);
new WatchDir(dir, recursive).processEvents();
}
}
2.C++环境下
其实从操作系统层面能够监听到文件操作事件是最好的了。
在windows环境下呢可以通过用拷贝钩子实现对文件夹的监控。
可参考网页 http:///article.asp?i=601&d=0z20f4
ICopyHook是一个用于创建拷贝钩子处理程序COM接口,它决定一个文件夹或者打印机对象是否可以被移动,拷贝,重命名或删除。Shell在执行这些操作之前,会调用ICopyHook接口的CopyCallback方法对它们进行验证。CopyCallback返回一个int值指示Shell是否应该继续执行这个操作。返回值IDYES表示继续,而返回值IDNO和IDCANCEL则表示终止。
一个文件夹对象可以安装多个拷贝钩子处理程序。如果出现这种情况,Shell会依次调用每个处理程序。只有当每个处理程序都返回IDYES时,Shell才真正执行用户请求的操作。
拷贝钩子处理程序的作用是在上述四种操作执行前对它们进行验证,但是Shell并不会把操作的结果通知给拷贝钩子处理程序。而windows提供的API函数FindFirstChangeNotification和FindNextChangeNotification却可以实现这个功能。因此,只有把这种两种方法结合起来,才能对一个文件夹的状态进行完全的监控。
拷贝钩子处理程序实现并不困难,首先创建一个作为进程内组件的COM对象,它只需要暴露一个ICopyHook接口(当然还有IUnknown)。然后用regsrv32.exe注册这个COM组件。最后一步是向Shell注册你的这个拷贝钩子处理程序,方法是在注册表HKEY_CLASSES_ROOT\Directory\Shellex\CopyHookHandlers下创建一个名称任意的sub key,在此sub key中创建一个类型为REG_SZ的项并将你的COM对象的CLSID作为它的默认值就可以了。
下面就是一个拷贝钩子的实现程序(注:以下代码经老妖改动并添加了详细操作过程,在BCB6中成功编译并通过测试)
2.1. 从ICopyHook接口创建TCopyHook,从IClassFactory接口创建TClassFactory:
// TCopyHook.h
// TCopyHook类实现了ICopyHook接口,TClassFactory实现了IClassFactory接口
//---------------------------------------------------------------------------
#define NO_WIN32_LEAN_AND_MEAN
#include <shlobj.h>
//---------------------------------------------------------------------------
class TCopyHook: public ICopyHook
{
public:
TCopyHook():m_refcnt(0) {}
STDMETHODIMP QueryInterface(REFIID iid,void **ppvObject);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP_(UINT) CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
LPCTSTR pszDestFile, DWORD dwDestAttribs);
private:
int m_refcnt;
};
//---------------------------------------------------------------------------
class TClassFactory : public IClassFactory
{
public:
TClassFactory():m_refcnt(0) {}
STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
STDMETHODIMP LockServer(BOOL fLock);
private:
int m_refcnt;
};
// TCopyHook.cpp
// TCopyHook对象和TClassFactory对象的实现文件
#include <stdio.h>
#include "TCopyHook.h"
//---------------------------------------------------------------------------
extern LONG nLocks; // 对象计数,用于DllCanUnloadNow
ULONG __stdcall TCopyHook::AddRef()
{
if(m_refcnt == 0)
nLocks++;
m_refcnt++;
return m_refcnt;
}
//---------------------------------------------------------------------------
ULONG __stdcall TCopyHook::Release()
{
int nNewCnt = --m_refcnt;
if(nNewCnt <= 0)
{
nLocks--;
delete this;
}
return nNewCnt;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TCopyHook::QueryInterface(REFIID dwIID, void **ppvObject)
{
if(dwIID == IID_IUnknown)
*ppvObject = static_cast<IUnknown*>(this);
else
if(dwIID == IID_IShellCopyHook)
*ppvObject = static_cast<ICopyHook*>(this);
else
return E_NOINTERFACE;
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
return S_OK;
}
//---------------------------------------------------------------------------
// 这就是CopyCallback方法,拷贝钩子的所有功能由它实现。参数的具体值参看MSDN
UINT __stdcall TCopyHook::CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
LPCTSTR pszDestFile, DWORD dwDestAttribs)
{
char szMessage[MAX_PATH+14];
sprintf(szMessage, "对%s进行的操作,是否继续?", pszSrcFile);
return MessageBox(NULL, szMessage, "确认", MB_YESNO | MB_ICONEXCLAMATION);
}
//---------------------------------------------------------------------------
ULONG __stdcall TClassFactory::AddRef()
{
if(m_refcnt==0)
nLocks++;
m_refcnt++;
return m_refcnt;
}
//---------------------------------------------------------------------------
ULONG __stdcall TClassFactory::Release()
{
int nNewCnt = --m_refcnt;
if(nNewCnt <= 0)
{
nLocks--;
delete this;
}
return nNewCnt;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TClassFactory::QueryInterface(REFIID dwIID, void **ppvObject)
{
if(dwIID == IID_IUnknown)
*ppvObject = static_cast<IUnknown*>(this);
else
if(dwIID == IID_IClassFactory)
*ppvObject = static_cast<IClassFactory*>(this);
else
return E_NOINTERFACE;
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
return S_OK;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TClassFactory::CreateInstance(IUnknown* pUnkownOuter,
REFIID riid, void** ppvObj)
{
if(pUnkownOuter != NULL)
return CLASS_E_NOAGGREGATION;
TCopyHook *pObj = new TCopyHook;
pObj->AddRef();
HRESULT hr = pObj->QueryInterface(riid, ppvObj);
pObj->Release();
return hr;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TClassFactory::LockServer(BOOL fLock)
{
if(fLock)
nLocks++;
else
nLocks--;
return S_OK;
}
2.2. 在BCB中New-->ActiveX-->ActiveX Library,然后添加相应代码。
//----------------------------------------------------------------
// 原作: webber84
// 本文转自 C++Builder研究 - http://www./article.asp?i=601&d=0z20f4
// 修改: ccrun(老妖)(www.)
// 欢迎访问C++ Builder 研究:http://www.
// 为防不负责任的转载者,此处加上原作者及修改者信息,请见谅。
//----------------------------------------------------------------
以下是修改后的Project1.cpp,大家可以直接copy过去。:
//$$---- axlib proj source ---- (stAXLibProjectSource)
#define NO_WIN32_LEAN_AND_MEAN
#include <vcl.h>
#pragma hdrstop
#include <atl\atlvcl.h>
#include <objbase.h>
#include <olectl.h>
#include "TCopyHook.h"
#pragma package(smart_init)
TComModule Project1Module;
TComModule &_Module = Project1Module;
// 这是要添加到注册表中的项,注意如果你要使用这段代码,应该用UUIDGEN.exe生成一
// 个新的CLSID。
const char* szRegTable[][3]=
{
{"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}", 0, "CopyHook"},
{"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}\\InProcServer32", 0, (const char*)-1},
{"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}\\InProcServer32", "ThreadingModel", "Apartment"},
{"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}\\ProgID", 0, "webber84.CopyHook.1"},
{"webber84.CopyHook.1", 0, "CopyHook"},
{"webber84.CopyHook.1\\CLSID", 0, "{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}"}
};
HMODULE hInstance = NULL;
LONG nLocks = 0;
//---------------------------------------------------------------------------
// The ATL Object map holds an array of _ATL_OBJMAP_ENTRY structures that
// described the objects of your OLE server. The MAP is handed to your
// project's CComModule-derived _Module object via the Init method.
BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()
//---------------------------------------------------------------------------
// Entry point of your Server invoked by Windows for processes or threads are
// initialized or terminated.
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
if(reason == DLL_PROCESS_ATTACH)
hInstance = (HMODULE)hinst;
return TRUE;
}
//---------------------------------------------------------------------------
// _Module.Term is typically invoked from the DLL_PROCESS_DETACH of your
// DllEntryPoint. However, this may result in an incorrect shutdown sequence.
// Instead an Exit routine is setup to invoke the cleanup routine
// CComModule::Term.
void ModuleTerm(void)
{
_Module.Term();
}
#pragma exit ModuleTerm 63
//---------------------------------------------------------------------------
// Entry point of your Server invoked to inquire whether the DLL is no
// longer in use and should be unloaded.
STDAPI __export DllCanUnloadNow(void)
{
return nLocks == 0? S_OK: S_FALSE;
}
//---------------------------------------------------------------------------
// Entry point of your Server allowing OLE to retrieve a class object from
// your Server
STDAPI __export DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
HRESULT hr = E_OUTOFMEMORY;
*ppv = NULL;
TClassFactory *pClassFactory = new TClassFactory;
if(pClassFactory != NULL)
hr = pClassFactory->QueryInterface(riid, ppv);
return hr;
}
//---------------------------------------------------------------------------
// Entry point of your Server invoked to instruct the server to create
// registry entries for all classes supported by the module
STDAPI __export DllRegisterServer(void)
{
HRESULT hr = S_OK;
int nItems = sizeof(szRegTable) / sizeof(szRegTable[0]);
char szDllPath[MAX_PATH];
GetModuleFileName(hInstance, szDllPath, MAX_PATH);
for(int i=0; i<nItems && SUCCEEDED(hr); i++)
{
const char *szKeyName = szRegTable[i][0];
const char *szValueName = szRegTable[i][1];
const char *szValue = szRegTable[i][2];
if(szValue == (const char*) - 1)
szValue = szDllPath;
HKEY hKey;
LONG lReturn = RegCreateKey(HKEY_CLASSES_ROOT, szKeyName, &hKey);
if(lReturn == ERROR_SUCCESS)
{
RegSetValueEx(hKey, szValueName, 0, REG_SZ,
(const BYTE*)szValue, strlen(szValue)+1);
RegCloseKey(hKey);
}
if(lReturn != ERROR_SUCCESS)
{
hr = SELFREG_E_CLASS;
DllUnregisterServer();
}
}
return hr;
}
//---------------------------------------------------------------------------
// Entry point of your Server invoked to instruct the server to remove
// all registry entries created through DllRegisterServer.
STDAPI __export DllUnregisterServer(void)
{
HRESULT hr = S_OK;
LONG lReturn = 0;
int nItems = sizeof(szRegTable) / sizeof(szRegTable[0]);
for(int i=nItems-1; i>=0; i--)
{
const char *szKeyName = szRegTable[i][0];
if((i == nItems-1) || stricmp(szRegTable[i+1][0], szKeyName) != 0)
lReturn = RegDeleteKey(HKEY_CLASSES_ROOT, szKeyName);
if(lReturn != ERROR_SUCCESS)
hr = SELFREG_E_CLASS;
}
return hr;
}
//---------------------------------------------------------------------------
2.3. 在BCB的IDE环境中,选择菜单的Project-->Add to Project-->找到刚才创建的TCopyHook.cpp-->OK
编译工程。如果没有错误,将生成Project1.dll。
2.4. 修改注册表:
在HKEY_CLASSES_ROOT\Directory\shellex\CopyHookHandlers\下新建一个项,命名为Test,更改其默认值为{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735},老妖注:这里的默认值应该和project1.cpp中的相同注册项目相同。
2.5. 注册COM组件:
运行regsvr32.exe 路径\project.dll,点击确定后不要理会再弹出的错误窗口。重新启动计算机,试着copy一个文件夹,当paste的时候,效果出来了,嘿嘿。自己试试吧。
源代码下载:
http://www./cctools/MyCopyHook.rar
在Linux环境下呢可以通过用inotify 监视文件系统的变化。
可参考网页 http://blog.csdn.net/rstevens/archive/2008/05/12/2438567.aspx
可以用 Linux 2.6 kenel 提供的 inotify 机制来监控文件系统的变化,例如增加文件,删除文件,修改文件等
下面是一个例子程序,可用来监控一个目录,如果在此目录下进行创建文件、删除文件、修改文件的操作,会有提示信息打印到屏幕上
#include <sys/inotify.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define BUF_LEN 4096
int main(int argc, char **argv)
...{
if(argc < 2) ...{
printf("Usage: %s [directory] ", argv[0]);
return -1;
}
int fd;
fd = inotify_init();
if(fd == -1) ...{
printf("failed in inotify_init, %d ", errno);
return -1;
}
int wd1, wd2;
ADD_AGAIN:
wd1 = inotify_add_watch(fd, argv[1], IN_DELETE|IN_MODIFY|IN_CREATE|IN_IGNORED);
if(wd1 == -1) ...{
perror("inotify_add_watch");
return -1;
} else
printf("new watch fd %d ", wd1);
/**//*
wd2 = inotify_add_watch(fd, "/etc", IN_ACCESS|IN_MODIFY);
if(wd2 == -1) {
perror("inotify_add_watch");
return -1;
}
*/
char buf[BUF_LEN] __attribute__ ((aligned(4)));
ssize_t len, i = 0;
unsigned int queue_len;
int ret;
while (1) ...{
i = 0;
ret = ioctl(fd, FIONREAD, &queue_len);
if(ret < 0) ...{
perror("ioctl");
}
len = read(fd, buf, BUF_LEN);
while(i < len) ...{
struct inotify_event *event = (struct inotify_event*)&buf[i];
printf("wd=%d mask=%d cookie=%d len=%d dir=%s ",
event->wd, event->mask, event->cookie, event->len, (event->mask & IN_ISDIR) ? "yes" : "no");
if(event->len)
printf("name=%s ", event->name);
if(event->mask & IN_IGNORED) ...{
printf("ignored, add again ");
if(0 != inotify_rm_watch(fd, event->wd))
perror("inotify_rm_watch");
goto ADD_AGAIN;
}
i += sizeof(struct inotify_event) + event->len;
}
}
return 0;
}