Windows C++ 内存映射文件注意事项
内核映射创建的整体步骤
参考博客
步骤一:调用CreateFile()函数,以适当的方式创建或打开一个文件核心对象;
步骤二:把CreateFile()函数返回的文件句柄作为参数,传给CreateFileMapping()函数,由CreateFileMapping()函数创建一个文件映射核心对象的适当属性;
步骤三:创建了文件映射核心对象后,调用MapViewOfFile()函数,告诉系统把文件的哪一部分映射到进程的地址空间中,以何种方式映射;
步骤四:利用MapViewOfFile()函数返回的指针来使用文件数据;
步骤五:操作完毕后,调用UnmapViewOfFile()函数,告诉系统撤销对文件映射核心对象的映射;
步骤六:使用CloseHandle()函数关闭文件映射核心对象;
步骤七:使用CloseHandle()函数关闭文件核心对象;
注意事项:
CreateFile()函数原型
成功返回一个非INVALID_HANDLE_VALUE的值
CreateFile(
__in LPCWSTR lpFileName,
__in DWORD dwDesiredAccess,
__in DWORD dwShareMode,
__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
__in DWORD dwCreationDisposition,
__in DWORD dwFlagsAndAttributes,
__in_opt HANDLE hTemplateFile
);
参数说明
第一个参数:指向文件名的指针 ( LPCWSTR 类型必须使用有效的LPCWSTR 不能直接调用(LPCWSTR )char* 特别是对于新手容易犯错误)
//char*fileName转WCHAR
WCHAR wszClassName[256];
memset(wszClassName, 0, sizeof(wszClassName));
MultiByteToWideChar(CP_ACP, 0,
fileName,//char*类型。
strlen(hyFileName) + 1,
wszClassName,
sizeof(wszClassName) / sizeof(wszClassName[0]));
第二个参数:打开文件的合适方式,包含
#define GENERIC_READ (0x80000000L)
#define GENERIC_WRITE (0x40000000L)
#define GENERIC_EXECUTE (0x20000000L)
#define GENERIC_ALL (0x10000000L)
一般是使用GENERIC_READ|GENERIC_WRITE
第三个参数 : 共享模式 一般写0。
第四个参数:指向安全属性的指针。一般是NULL
第五个参数:如何创建 。一般有:
CREATE_ALWAYS//会将之前存在的文件覆盖
OPEN_ALWAYS//总是打开 文件不存在就创建
OPEN_EXISTING //只有存在的时候打开
第六个参数:文件属性 一般是FILE_ATTRIBUTE_NORMAL
第七个参数:用于复制文件句柄 一般是NULL
参考博客
CreateFileMapping(hyFileHandle,
NULL,
PAGE_READWRITE,
0,
102410241024,
L"fd看看shjpsk");
HANDLE CreateFileMapping(
HANDLE hFile, //物理文件句柄
LPSECURITY_ATTRIBUTES lpAttributes, //安全设置
DWORD flProtect, //保护设置
DWORD dwMaximumSizeHigh, //高位文件大小 一般是0根据自己的程序时32位还是64位
DWORD dwMaximumSizeLow, //低位文件大小这个在写文件的时候需要使用。对于写文件最好设置大点例如1024 * 1024* 1024
LPCTSTR lpName //共享内存名称创建共享内存名字的时候切忌使用global/这个global必须使用管理员权限才能创建成功。
);
-
物理文件句柄
任何可以获得的物理文件句柄, 如果你需要创建一个物理文件无关的内存映射也无妨, 将它设置成为 0xFFFFFFFF(INVALID_HANDLE_VALUE)就可以了.如果需要和物理文件关联, 要确保你的物理文件创建的时候的访问模式和"保护设置"匹配, 比如: 物理文件只读, 内存映射需要读写就会发生错误. 推荐你的物理文件使用独占方式创建.
如果使用 INVALID_HANDLE_VALUE, 也需要设置需要申请的内存空间的大小, 无论物理文件句柄参数是否有效, 这样 CreateFileMapping 就可以创建一个和物理文件大小无关的内存空间给你, 甚至超过实际文件大小, 如果你的物理文件有效, 而大小参数为0, 则返回给你的是一个和物理文件大小一样的内存空间地址范围. 返回给你的文件映射地址空间是可以通过复制, 集成或者命名得到, 初始内容为0.
-
保护设置
就是安全设置, 不过一般设置NULL就可以了, 使用默认的安全配置. 在win2k下如果需要进行限制, 这是针对那些将内存文件映射共享给整个网络上面的应用进程使用是, 可以考虑进行限制. -
高位文件大小
弟兄们, 我想目前我们的机器都是32位的东东, 不可能得到超过32位进程所能寻址的私有32位地址空间, 一般还是设置0吧, 我没有也不想尝试将它设置超过0的情况. -
低位文件大小
这个还是可以进行设置的, 不过为了让其他共享用户知道你申请的文件映射的相关信息, 我使用的时候是在获得的地址空间头部添加一个结构化描述信息, 记录内存映射的大小, 名称等, 这样实际申请的空间就比输入的增加了一个头信息结构大小了, 我认为这样类似BSTR的方式应该是比较合理的. -
共享内存名称
这个就是我今天测试的时候碰壁的祸根, 因为为了对于内存进行互斥访问, 我设置了一个互斥句柄, 而名称我选择和命名共享内存同名, 之下就是因为他们使用共同的namespace导致了错误, 呵呵. -
调用CreateFileMapping的时候GetLastError的对应错误
ERROR_FILE_INVALID 如果企图创建一个零长度的文件映射, 应有此报
ERROR_INVALID_HANDLE 如果发现你的命名内存空间和现有的内存映射, 互斥量, 信号量, 临界区同名就麻烦了
ERROR_ALREADY_EXISTS 表示内存空间命名已经存在 -
相关服务或者平台的命名保留
Terminal Services:
命名可以包含 “Global” 或者 “Local” 前缀在全局或者会话名空间初级文件映射. 其他部分可以包含任何除了()以外的字符, 可以参考 Kernel Object Name Spaces.
创建共享内存名字的时候切忌使用global/这个global必须使用管理员权限才能创建成功。
LPVOID
WINAPI
MapViewOfFile(
__in HANDLE hFileMappingObject,//CreateFileMapping返回的有效句柄
__in DWORD dwDesiredAccess,//文件数据的访问方式
__in DWORD dwFileOffsetHigh,//默认0具体可以自己研究下
__in DWORD dwFileOffsetLow,/默认0具体可以自己研究下
__in SIZE_T dwNumberOfBytesToMap/默认0具体可以自己研究下
);
成功返回一个非NULL的值
在对文件操作的时候可以直接对MapViewOfFile进行操作。
当频繁往文件里写一个字符串的时候可以使用strcat()和memcpy()函数,但是实验证明,strcat追加大的字符串的时候非常消耗资源,memcpy的时候 也非常消耗资源,
strcpy(des,sour);//出现比较大的字符串的时候,往往效率特别低
memcpy(des + strlen(des), data, strlen(data));//出现比较大的字符串的时候,往往效率也是特别低的。
怀疑使用strlen会比较慢。第一步实验:
//类的成员变量里增加一个UINT64 u64WriteAddress;的内存地址,在调用MapViewOfFile的时候直接获取文件的长度。
//继续使用memcpy函数
write(const char* data)
{
int dataLength = strlen(data);
memcpy(des+u64WriteAddress,data,dataLength);
u64WriteAddress+=dataLength;
}
for (int j = 0; j < 90000; ++j)
{
for (int i = 0; i < 300; ++i)
{
file.write("qwertyuiopasdfghjklzxcvbnmwsxedcrfv");
file.write("\n");
}
}
//测试结果非常完美.写1G的数据在VS调试环境下不到4秒果然与strlen函数有关。
整个测试源码如下,只贴主要逻辑
//mian.cpp
char* fileName = "Test.txt";
MyFile file(fileName);
int returnv = file.open(fileName);
if(returnv == 0)
{
for (int j = 0; j < 90000; ++j)
{
for (int i = 0; i < 300; ++i)
{
file.write("qwertyuiopasdfghjklzxcvbnmwsxedcrfv");
file.write("\n");
}
}
file.close();
std::cout<<"写成功"<<endl;
}
else
{
std::cout<<"写失败----"<<returnv<<endl;
}
//MyFile.cpp
int open(const char* fileName)
{
WCHAR wszClassName[256];
memset(wszClassName, 0, sizeof(wszClassName));
MultiByteToWideChar(CP_ACP, 0, fileName, strlen(fileName) + 1, wszClassName,
sizeof(wszClassName) / sizeof(wszClassName[0]));
HANDLE fileHandle = CreateFile(wszClassName, GENERIC_READ | GENERIC_WRITE,0,
NULL, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
if(fileHandle == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
HANDLE mapingFileH = CreateFileMapping(fileHandle,
NULL,
PAGE_READWRITE,
0,
1024*1024*1024,
L"gg");
if(mapingFileH == NULL)
{
close();
return GetLastError();
}
mapingH = (PSTR)MapViewOfFile( mapingFileH, FILE_MAP_WRITE,0,0, 0);
if(!mapingH)
{
close();
return GetLastError();
}
u64WriteAddress = strlen(mapingH);
return 0;
}
void close()
{
UINT64 _size = 0;
if(apingH!= NULL)
{
_size = strlen(mapingH);
mapingH[strlen(mapingH)] = '\0';
UnmapViewOfFile(mapingH);
}
mapingH = NULL;
if(mapingFileH!= NULL)
{
CloseHandle(mapingFileH);
}
mapingFileH = NULL;
if(INVALID_HANDLE_VALUE != fileHandle)
{
SetFilePointer(fileHandle, _size, NULL, FILE_BEGIN);//
SetEndOfFile(fileHandle);
CloseHandle(fileHandle);
}
fileHandle = INVALID_HANDLE_VALUE;
}
int write(const char* data)
{
if (!data)return 2;
//WaitForSingleObject(g_Mutex, INFINITE);//等待互斥量
if (!mapingH)
{
//ReleaseMutex(g_Mutex);
return 3;
}
UINT64 _dataLen = strlen(data);
//strlen(mapingH);
memcpy(mapingH + u64WriteByteAddress, data, _dataLen);
u64WriteByteAddress += _dataLen;
//strcat(mapingH,data);
//ReleaseMutex(g_Mutex);
return 0;
}
最后使用SetFilePointer(FileHandle, WriteByte, NULL, FILE_BEGIN);来保存文件,这里的WriteByte是实际文件大小。