今天的任务是要保存一个文件。平常看别人怎么写,自己还只是看,没有动手去写过,对各个API相应的参数不是很了解。今天在运用的时候,还真是遇见了一些问题。
我们先来说说问题:
第一个问题:使用WriteFile的时候,我直接将宽字符串写进了文件,文件显示如大家所想,掺杂了很多乱码。但是很有规则。所以我很快就明白了这需要将宽字符串转换成ASCII码。
第二个问题:就是我将文件打开后,又进行了写文件的操作,此时失败。所以对这种情况,还没有想出办法,是由于CreateFile的参数的某些限制么?
由于这两个问题,所以我也好好看了一下SDK文档。
我们先来看一下CreateFile和WriteFile的原型和参数介绍:
HANDLE CreateFile(
LPCTSTR lpFileName , // 文件名
DWORD dwDesiredAccess , // 访问方式
DWORD dwShareMode , // 共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes , // 设为NULL
DWORD dwCreationDisposition , /// 创建方式
DWORD dwFlagsAndAttributes , // 属性
HANDLE hTemplateFile
);
BOOL WriteFile(
HANDLE hFile , // 文件句柄
LPCVOID lpBuffer , // 包含写向文件的数据
DWORD nNumberOfBytesToWrite , // 数据包含的字符串的个数
LPDWORD lpNumberOfBytesWritten ,
LPOVERLAPPED lpOverlapped
);
第一次我写的程序很简单
BOOL WriteOwnFile(TCHAR* pFileName, TCHAR* pBuffer, DWORD dwLen)
{
HANDLE hFile = CreateFile(pFileName,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (INVALID_HANDLE_VALUE != hFile)
{
DWORD dwSize = 0;
WriteFile(hFile, pBuffer, dwLen, &dwSize, NULL );
CloseHandle(hFile);
return TRUE;
}
return FALSE;
}
这样是完成了,但是写出来的文件是乱码。所以没有进行字符的转换,我们需要将pBuffer进行转换。这就要用到了WideCharToMultiByte.如何用呢?
首先我的方法比较笨,我是这么用的:
char* pchBuffer = new char[dwLen+1];
WideCharToMultiByte(CP_ACP, NULL, pBuffer, -1, pchBuffer, dwLen+1, NULL, FALSE );
WriteFile(hFile, pBuffer, dwLen+1, &dwSize, NULL );
Delete[] pchBuffer;
此时注意,我在WriteFile中用了dwLen+1。结果就是在文件的末尾出现了乱码,正好多一个乱码出来。所以WriteFile中nNumberOfBytesToWrite是写的字符串的数目,是不包括’\0’的。
这个方法笨,是因为我们的函数可以缩减为两个参数。是因为如下这么写时,dwLen是所要转换的字符串的个数,此时转换的字符串是包括’\0’的。
DWORD dwLen = WideCharToMultiByte(CP_ACP, NULL, pBuffer, -1, NULL, NULL, NULL, FALSE );
所以我们再来看一下改写以后的代码
BOOL WriteOwnFile(TCHAR* pFileName, TCHAR* pBuffer)
{
HANDLE hFile = CreateFile(pFileName,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (INVALID_HANDLE_VALUE != hFile)
{
DWORD dwSize = 0;
DWORD dwLen = WideCharToMultiByte(CP_ACP, NULL, pBuffer, -1, NULL, NULL, NULL, FALSE );
char* pchBuffer = new char[dwLen];
WideCharToMultiByte(CP_ACP, NULL, pBuffer, -1, pchBuffer, dwLen, NULL, FALSE );
WriteFile(hFile, pBuffer, dwLen+1, &dwSize, NULL );
delete[] pchBuffer;
CloseHandle(hFile);
return TRUE;
}
return FALSE;
}
这样感觉代码好看多了。
对于第二个问题,文件打开的时候文件创建失败,还没有想好办法解决。我在想是不是我的某些认知存在问题,文件打开的时候,是否可以用CreateFile来打开呢?
补充:
程序的读和写都是自己控制的,所以我在写文件的时候,没有必要进行字符串。读文件的时候也按照相应的字符读取即可。所以我的代码使用最初的代码就可以了。
今天完成了一个任务,就是在mobile上如何监控文件的操作。这个SDK中有相应的例子,为FileChangeNotif。
如何实现文件监控?
首先要在窗口注册,这个要用到SHChangeNotifyRegister ,这个函数的主要功能就是列举一个窗口来接收change notifications.
在这个注册的窗口中,响应WM_FILECHANGEINFO 这个消息,来进行我们响应的操作。
如何我们不想监控了,则可以使用SHChangeNotifyDeregister ,来移除相应的注册窗口。
这样我们就可以实现对一个文件夹内文件的生成,删除,改名等等操作的监控。
下面我们再具体来谈谈每一步如何操作。
1 、SHChangeNotifyRegister 的运用
SHChangeNotifyRegiste r的原型为
BOOL WINAPI SHChangeNotifyRegister(
HWND hwnd,
SHCHANGENOTIFYENTRY * pshcne
);
其中,hwnd,为接收change notification的窗口;
pshcne 是一个指向SHCHANGENOTIFYENTRY结构的指针,它用来指明窗口接收的change notification的类型.如果设为NULL,窗口将接收all file system, network 和 media类型的notifications.
SHCHANGENOTIFYENTRY 是什么样的一个结构,我们看一下它的定义
typedef struct tagSHCHANGENOTIFYENTRY {
DWORD dwEventMask;
LPTSTR pszWatchDir;
BOOL fRecursive;
} SHCHANGENOTIFYENTRY;
dwEventMask 指定发生什么时间来发送notification 消息
pszWatchDir 指定监控路径,该值为NULL的情况下,是监控所有的文件。
fRecursive 指定是否只监控指定路径还是监控指定路径及其子文件夹。
知道了这些,我们不妨写一个这样的函数,来启动文件监控。
代码如下:
BOOL StartFileMonitor(HWND hWnd, LPTSTR lpFilePath)
{
SHCHANGENOTIFYENTRY schneNotifyEntry;
schneNotifyEntry.dwEventMask = SHCNE_ALLEVENTS;
schneNotifyEntry.pszWatchDir = lpFilePath;
schneNotifyEntry.fRecursive = TRUE;
return SHChangeNotifyRegister(hWnd, &schneNotifyEntry);
}
2 、如何处理WM_FILECHANGEINFO 消息
WM_FILECHANGEINFO 中的参数lParam,指向FILECHANGENOTIFY ,含有相关的数据。所以我们在收到该消息后,先作的一部操作就是
FILECHANGENOTIFY *lpfcn = (FILECHANGENOTIFY*)lParam;
FILECHANGENOTIFY 的结构为:
typedef struct tagFILECHANGENOTIFY {
DWORD dwRefCount;
FILECHANGEINFO fci;
} FILECHANGENOTIFY;
我们主要用到了其中的fci参数。
FILECHANGEINFO 的结构为:
struct _FILECHANGEINFO {
DWORD cbSize;
LONG wEventId;
ULONG uFlags;
DWORD dwItem1;
DWORD dwItem2;
DWORD dwAttributes;
FILETIME ftModified;
ULONG nFileSize;
} FILECHANGEINFO, *LPFILECHANGEINFO;
dwEventId 与SHCHANGENOTIFYENTRY结构中的dwEventMask 对应。
dwItem1,dwItem 2是事件依赖的值,里面包括了我们需要的文件的完整路径。如果是进行创建文件的操作,则dwItem1 是创建后文件的完整路径,如果是对文件进行重新命名操作的话,则dwItem2 是修改后文件的完整路径。此处对其他参数不做介绍,大家需要的话,可以查看一下。
我们做完相应的操作后,要知道释放,此时要用到SHChangeNotifyFree 。这个用起来就简单很多,如SHChangeNotifyFree (lpfcn)。
下面给大家一小段示例代码,如下
case WM_FILECHANGEINFO:
{
FILECHANGENOTIFY *lpfcn;
FILECHANGEINFO *lpfci;
lpfcn = (FILECHANGENOTIFY *)lParam;
if (NULL == lpfcn)
{
break;
}
// see if the pointer to the file change info structure
lpfci = &(lpfcn->fci);
if (NULL == lpfci)
{
break;
}
else
{
switch (lpfci->wEventId)
{
case SHCNE_RENAME:
{
//……
}
break;
}
}
SHChangeNotifyFree(lpfcn);
}
break;
3 、如何停止文件监控
停止文件监控比较简单,只要使该窗口不接收WM_FILECHANGEINFO 消息即可。使用SHChangeNotifyDeregister(hWnd) 即可。
以上是我今天学习的一些总结,此外需要注意的一个小地方,在mobile上,把一个文件从一个文件夹拷到另一个文件夹,此时响应的事件是 SHCNE_CREATE,二从电脑上拷贝一个文件到mobile上,响应的消息为SHCNE_RENAME。我注意到从电脑上拷贝的话,mobile会 先生成一个Temp文件夹内生成一个临时文件,然后再在我们指定的文件夹内生成一个文件。这个机制我还不是很清楚为什么。