??xml version="1.0" encoding="utf-8" standalone="yes"?>
关于内存映射文g处理
今天看到一文章讲内存映射文g的处理,虽然自己没有处理q如此大的文件系l,但是好奇׃看了下,谁知道自׃后会不会用到或考到q方面的知识。所以就l自?/span> mark 一下,增加点自q印象?/span>
首先Q通过 CreateFile() 函数来创建或打开一个文件内核对象,q个对象标识了磁盘上要用作内存映射文g的文件。(其实是获取文件句柄)
其次Q通过 CreateFileMapping() 函数来ؓ刚才创徏的文件内核对象创Z个文件映内核对象ƈ告诉pȝ文g的尺总及访问文件的方式。(获取文g映射内核对象的句柄)
再次Q通过 MapViewOfFile() 函数文件内核映对象添加到q程中。(获取映射内核对象的指针)
接着Q程序就可以通过指针q行常规的文件读取了Q这里的操作和文g操作一P不做展开?/span>
用完之后Q还得回Ӟ先用 UnmapViewOfFile() 释放映内核对象指针,然后通过 CloseHandle 关闭之前创徏的文件映内核对象句柄和文g内核对象句柄?/span>
以下是我扑ֈ的文章的出处Q?/span> http://newcactus.bokee.com/viewdiary.15316244.html
下面Ua是粘贴别人的作品Q?/span>
VC++
中用内存映文件处理大文g
摘要Q?/span>
本文l出了一U方便实用的解决大文件的d、存储等处理的方法,q结合相关程序代码对具体的实现过E进行了介绍?span lang="EN-US">
引言
文g操作是应用程序最为基本的功能之一Q?span lang="EN-US">Win32 API?span lang="EN-US">MFC均提供有支持文g处理的函数和c,常用的有Win32 API?span lang="EN-US">CreateFile()?span lang="EN-US">WriteFile()?span lang="EN-US">ReadFile()?span lang="EN-US">MFC提供?span lang="EN-US">CFilecȝ。一般来_以上q些函数可以满大多数场合的要求Q但是对于某些特D应用领域所需要的动辄几十GB、几?span lang="EN-US">GB、乃臛_TB的v量存储,再以通常的文件处理方法进行处理显然是行不通的。目前,对于上述q种大文件的操作一般是以内存映文件的方式来加以处理的Q本文下面将针对q种Windows核心~程技术展开讨论?span lang="EN-US">
内存映射文g
内存映射文g与虚拟内存有些类|通过内存映射文g可以保留一个地址I间的区域,同时物理存储器提交l此区域Q只是内存文件映的物理存储器来自一个已l存在于盘上的文gQ而非pȝ的页文gQ而且在对该文件进行操作之前必首先对文gq行映射Q就如同整个文件从盘加蝲到内存。由此可以看出,使用内存映射文g处理存储于磁盘上的文件时Q将不必再对文g执行I/O操作Q这意味着在对文gq行处理时将不必再ؓ文g甌q分配缓存,所有的文g~存操作均由pȝ直接理Q由于取消了文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤Q得内存映文件在处理大数据量的文件时能v到相当重要的作用。另外,实际工程中的pȝ往往需要在多个q程之间׃n数据Q如果数据量,处理Ҏ是灵zd变的Q如果共享数据容量巨大,那么需要借助于内存映文件来q行。实际上Q内存映文件正是解x地多个进E间数据׃n的最有效Ҏ?span lang="EN-US">
内存映射文gq不是简单的文gI/O操作Q实际用CWindows的核心编E技?span lang="EN-US">--内存理。所以,如果惛_内存映射文g有更深刻的认识,必须?span lang="EN-US">Windows操作pȝ的内存管理机制有清楚的认识,内存理的相关知识非常复杂,出了本文的讨论范畴Q在此就不再赘述Q感兴趣的读者可以参阅其他相关书c。下面给Z用内存映文件的一般方法:
首先要通过CreateFile()函数来创建或打开一个文件内核对象,q个对象标识了磁盘上要用作内存映射文g的文件。在?span lang="EN-US">CreateFile()文件映像在物理存储器的位置通告l操作系l后Q只指定了映像文件的路径Q映像的长度q没有指定。ؓ了指定文件映对象需要多大的物理存储I间q需要通过CreateFileMapping()函数来创Z个文件映内核对象以告诉pȝ文g的尺总及访问文件的方式。在创徏了文件映对象后Q还必须为文件数据保留一个地址I间区域Qƈ把文件数据作为映到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过pȝ的管理而将文g映射对象的全部或部分映射到进E地址I间。此Ӟ对内存映文件的使用和处理同通常加蝲到内存中的文件数据的处理方式基本一P在完成了对内存映文件的使用Ӟq要通过一pd的操作完成对其的清除和用过资源的释放。这部分相对比较单,可以通过UnmapViewOfFile()完成从进E的地址I间撤消文g数据的映像、通过CloseHandle()关闭前面创徏的文件映对象和文g对象?span lang="EN-US">
内存映射文g相关函数
在用内存映文件时Q所使用?span lang="EN-US">API函数主要是前面提到q的那几个函敎ͼ下面分别对其q行介绍Q?span lang="EN-US">
HANDLE CreateFile(LPCTSTR lpFileName, |
函数CreateFile()即是在普通的文g操作时也l常用来创徏、打开文gQ在处理内存映射文gӞ该函数来创徏/打开一个文件内核对象,q将其句柄返回,在调用该函数旉要根据是否需要数据读写和文g的共享方式来讄参数dwDesiredAccess?span lang="EN-US">dwShareModeQ错误的参数讄会D相应操作时的p|?span lang="EN-US">
HANDLE CreateFileMapping(HANDLE hFile, |
CreateFileMapping()函数创徏一个文件映内核对象,通过参数hFile指定待映到q程地址I间的文件句柄(该句柄由CreateFile()函数的返回D取)。由于内存映文件的物理存储器实际是存储于磁盘上的一个文Ӟ而不是从pȝ的页文g中分配的内存Q所以系l不会主动ؓ其保留地址I间区域Q也不会自动文件的存储I间映射到该区域Qؓ了让pȝ能够定寚w面采取何U保护属性,需要通过参数flProtect来设定,保护属?span lang="EN-US">PAGE_READONLY?span lang="EN-US">PAGE_READWRITE?span lang="EN-US">PAGE_WRITECOPY分别表示文g映射对象被映后Q可以读取、读写文件数据。在使用PAGE_READONLYӞ必须保CreateFile()采用的是GENERIC_READ参数Q?span lang="EN-US">PAGE_READWRITE则要?span lang="EN-US">CreateFile()采用的是GENERIC_READ|GENERIC_WRITE参数Q至于属?span lang="EN-US">PAGE_WRITECOPY则只需要确?span lang="EN-US">CreateFile()采用?span lang="EN-US">GENERIC_READ?span lang="EN-US">GENERIC_WRITE其中之一卛_?span lang="EN-US">DWORD型的参数dwMaximumSizeHigh?span lang="EN-US">dwMaximumSizeLow也是相当重要的,指定了文件的最大字节数Q由于这两个参数?span lang="EN-US">64位,因此所支持的最大文仉度ؓ16EBQ几乎可以满Q何大数据量文件处理场合的要求?span lang="EN-US">
LPVOID MapViewOfFile(HANDLE hFileMappingObject, |
MapViewOfFile()函数负责把文件数据映到q程的地址I间Q参?span lang="EN-US">hFileMappingObject?span lang="EN-US">CreateFileMapping()q回的文件映像对象句柄。参?span lang="EN-US">dwDesiredAccess则再ơ指定了Ҏ件数据的讉K方式Q而且同样要与CreateFileMapping()函数所讄的保护属性相匚w。虽然这里一再对保护属性进行重复设|看似多余,但却可以使应用程序能更多的对数据的保护属性实行有效控制?span lang="EN-US">MapViewOfFile()函数允许全部或部分映文Ӟ在映时Q需要指定数据文件的偏移地址以及待映的长度。其中,文g的偏Ud址?span lang="EN-US">DWORD型的参数dwFileOffsetHigh?span lang="EN-US">dwFileOffsetLowl成?span lang="EN-US">64位值来指定Q而且必须是操作系l的分配_度的整数倍,对于Windows操作pȝQ分配粒度固定ؓ64KB。当Ӟ也可以通过如下代码来动态获取当前操作系l的分配_度Q?span lang="EN-US">
SYSTEM_INFO sinf; |
参数dwNumberOfBytesToMap指定了数据文件的映射长度Q这里需要特别指出的是,对于Windows 9x操作pȝQ如?span lang="EN-US">MapViewOfFile()无法扑ֈ_大的区域来存放整个文件映对象,返回空|NULLQ;但是?span lang="EN-US">Windows 2000下,MapViewOfFile()只需要ؓ必要的视图找到够大的一个区域即可,而无考虑整个文g映射对象的大?span lang="EN-US">
在完成对映射到进E地址I间区域的文件处理后Q需要通过函数UnmapViewOfFile()完成Ҏ件数据映像的释放Q该函数原型声明如下Q?span lang="EN-US">
BOOL UnmapViewOfFile(LPCVOID lpBaseAddress); |
唯一的参?span lang="EN-US">lpBaseAddress指定了返回区域的基地址Q必d其设定ؓMapViewOfFile()的返回倹{在使用了函?span lang="EN-US">MapViewOfFile()之后Q必要有对应的UnmapViewOfFile()调用Q否则在q程l止之前Q保留的区域无法释放。除此之外,前面q曾?span lang="EN-US">CreateFile()?span lang="EN-US">CreateFileMapping()函数创徏q文件内核对象和文g映射内核对象Q在q程l止之前有必要通过CloseHandle()其释放Q否则将会出现资源泄漏的问题?span lang="EN-US">
除了前面q些必须?span lang="EN-US">API函数之外Q在使用内存映射文g时还要根据情冉|选用其他一些辅助函数。例如,在用内存映文件时Qؓ了提高速度Q系l将文g的数据页面进行高速缓存,而且在处理文件映视图时不立x新文件的盘映像。ؓ解决q个问题可以考虑使用FlushViewOfFile()函数Q该函数强制pȝ修改过的数据部分或全部重新写入盘映像Q从而可以确保所有的数据更新能及时保存到盘?span lang="EN-US">
使用内存映射文g处理大文件应用示?span lang="EN-US">
下面l合一个具体的实例来进一步讲q内存映文件的使用Ҏ。该实例从端口接收数据,q实时将其存放于盘Q由于数据量大(几十GBQ,在此选用内存映射文gq行处理。下面给出的是位于工作线E?span lang="EN-US">MainProc中的部分主要代码Q该U程自程序运行时启动Q当端口有数据到达时会发出事ghEvent[0]Q?span lang="EN-US">WaitForMultipleObjects()函数{待到该事g发生后将接收到的数据保存到磁盘,如果l止接收发Z?span lang="EN-US">hEvent[1]Q事件处理过E将负责完成资源的释攑֒文g的关闭等工作。下面给出此U程处理函数的具体实现过E:
…?br />//
创徏文g内核对象Q其句柄保存?span lang="EN-US">hFile |
在终止事件触发处理过E中如果只简单的执行UnmapViewOfFile()?span lang="EN-US">CloseHandle()函数无法正标识文件的实际大小Q即如果开辟的内存映射文g?span lang="EN-US">30GBQ而接收的数据只有14GBQ那么上q程序执行完后,保存的文仉度仍?span lang="EN-US">30GB。也是_在处理完成后q要再次通过内存映射文g的Ş式将文g恢复到实际大,下面是实现此要求的主要代码:
//
创徏另外一个文件内核对?span lang="EN-US"> |
l论
l实际测试,内存映射文g在处理大数据量文件时表现Z良好的性能Q比通常使用CFilecdReadFile()?span lang="EN-US">WriteFile(){函数的文g处理方式h明显的优ѝ本文所qC码在Windows 98下由Microsoft Visual C++ 6.0~译通过?/span>
枚D串口四法
串口作ؓ最基本的电脑通信 I/O 接口Q其使用虽然?nbsp;PC 上越来越,但是在工业A器领域仍然用的相当普遍,׃W者工作中需要用C口,而且发现枚D串口至今仍未搞得很清楚,为此自己先整理下Q希望大侠和同行们对我不懂和错误的地Ҏ点一下?/p>
1 、查询注册表
查询注册表的Ҏ是网上见到的比较常见的方法,该方法就是用编E方法读取注册表内信息,相当于用户通过在运行框内输?nbsp;”regedit” Q或 regedit32 Q直接打开注册表,查看“ HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM ”Ҏ获取串口信息。以下是源代码:
CString strSerialList[256]; // 临时定义 256 个字W串l,因ؓpȝ最多也?nbsp;256 ?/p>
HKEY hKey;
LPCTSTR data_Set="HARDWARE\\DEVICEMAP\\SERIALCOMM\\";
long ret0 = (::RegOpenKeyEx(HKEY_LOCAL_MACHINE, data_Set, 0, KEY_READ, &hKey));
if(ret0 != ERROR_SUCCESS)
{
return -1;
}
int i = 0;
CHAR Name[25];
UCHAR szPortName[25];
LONG Status;
DWORD dwIndex = 0;
DWORD dwName;
DWORD dwSizeofPortName;
DWORD Type;
dwName = sizeof(Name);
dwSizeofPortName = sizeof(szPortName);
do
{
Status = RegEnumValue(hKey, dwIndex++, Name, &dwName, NULL, &Type,
szPortName, &dwSizeofPortName);
if((Status == ERROR_SUCCESS)||(Status == ERROR_MORE_DATA))
{
strSerialList[i] = CString(szPortName); // 串口字符串保?/p>
i++;// 串口计数
}
} while((Status == ERROR_SUCCESS)||(Status == ERROR_MORE_DATA));
RegCloseKey(hKey);
以上Ҏ同样也可以实现对q口的查询,只要?nbsp;"HARDWARE \\ DEVICEMAP\\ SERIALCOMM\\" ?nbsp;"HARDWARE\\DEVICEMAP\\PARALLEL PORTS\\" 代替p了?/p>
比较Q该Ҏ旉最省,W者在自己电脑上试q,?nbsp;1ms Q少?nbsp;1ms 的我也不知道怎么~程计时Q内卛_完成Q同时也可解?nbsp;usb 转串口设备的问题Q比较实用,唯一~点是,如果用户在装某些软硬件时在注册表中注册了虚拟串口之类的,用此法枚丑־到的该类串口实际上是不能当串口用的?/p>
2 、?nbsp;EnumPort Ҏ
该方法调?nbsp;EnumPort Q) API 函数Q该函数本n是枚D电脑端口用的Q它枚D的ƈ非只有串口,所以必d其所得串口进行分析选择Q以下是源代码:
int m_nSerialPortNum(0);// 串口计数
CString strSerialList[256]; // 临时定义 256 个字W串l?/p>
LPBYTE pBite = NULL;
DWORD pcbNeeded = 0; // bytes received or required
DWORD pcReturned = 0; // number of ports received
m_nSerialPortNum = 0;
// 获取端口信息Q能得到端口信息的大?nbsp;pcbNeeded
EnumPorts(NULL, 2, pBite, 0, &pcbNeeded, &pcReturned);
pBite = new BYTE[pcbNeeded];
// 枚D端口Q能得到端口的具体信?nbsp;pBite 以及端口的的个数 pcReturned
EnumPorts(NULL, 2, pBite, pcbNeeded, &pcbNeeded, &pcReturned);
PORT_INFO_2 *pPort;
pPort = (PORT_INFO_2*)pBite;
for ( i = 0; i < pcReturned; i++)
{
CString str = pPort[i].pPortName;
// 串口信息的具体确?/p>
if (str.Left(3) == "COM")
{
strSerialList[m_nSerialPortNum] = str.Left(strlen(str) - 1);
//CString temp = str.Right(strlen(str) - 3);// 下面两行注释获取串口序号?/p>
//m_nSerialPortNo[m_nSerialPortNum] = atoi(temp.Left(strlen(temp) - 1));
m_nSerialPortNum++;
}
}
以上Ҏ除了串口Q还可以枚D所有的q口和打印机{接口,而且能找到虚拟串口(q些串口有些未用时Q在注册表和g讑֤理器中是不能取得的Q。但是该ҎE微耗时些,W者在自己电脑上试q,大概需要几?nbsp;ms Q主要问题是该方法有?nbsp;usb 串口q不能查刎ͼ所以该Ҏq不可靠?/p>
3 、依ơ打开串口的方?/p>
该方法就是中规中矩的依次打开串口Q看打开是否成功来判断串口的有无Q该Ҏ源代码如下:
int m_nSerialPortNum(0);// 串口?/p>
CString strSerialList[256]; // 临时定义 30 个字W串l?/p>
int nCom = 0;
int count = 0;
HANDLE hCom;
do {
nCom++;
strCom.Format("COM%d", nCom);
hCom = CreateFile(strCom, 0, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(INVALID_HANDLE_VALUE == hCom )
break;
strSerialList[m_nSerialPortNum] = strCom;
m_nSerialPortNum++;
CloseHandle(hCom);
} while(1);
以上Ҏ枚D的都是当前可用的串口Q如果有一个串口当前被占用则其后的串口也将无法枚D得到Q当然以上方法也可以Ҏ调用 for 循环让其枚D打开 256 个串口的Ҏ以避免上q情况,不过该方法比前两U更耗时Q一般查找一个串口就?nbsp;15ms 左右Q,不过可以枚D得到所有当前可打开的串口,当然不能枚D得到一些虚拟串口?/p>
4 、?nbsp;SetupAPI 函数集的Ҏ
此种Ҏ是我所见过最单的ҎQ之所以简单是因ؓ已经有h复杂的代码装h了,我只需像傻子一栯用就可以完成工作了,具体的说明请?a >http://www.codeguru.com/Cpp/W-P/system/hardwareinformation/article.php/c5721/ Q下面给出本用该Ҏ的例子代码:
int m_nSerialPortNum(0);// 串口计数
CString strSerialList[256]; // 临时定义 256 个字W串l?/p>
CArray<SSerInfo,SSerInfo&> asi;
EnumSerialPorts(asi,TRUE);// 参数?nbsp;TRUE 时枚丑ֽ前可以打开的串口,
// 否则枚D所有串?/p>
m_nSerialPortNum = asi.GetSize();
for (int i=0; i<asi.GetSize(); i++)
{
CString str = asi[i].strFrien dlyName;
}
补充说明一下,使用该方法只要在你的E序中,d“ EnumSerial.cpp ”?#8220; EnumSerial.h ”两个文gQƈ且将 Setupapi.lib 包含q你的工E文件中p了,该方法时间上来说可能和第三种Ҏ差不多,但该Ҏ获取的串口完完全全就是硬件设备管理器中的串口?/p>
以上是笔者对枚D串口几种Ҏ的小l,有些没弄明白或含p的地方Q还h正?/p>
如何去除对话框默认的Enter?span lang="EN-US">Esc按键响应
MFC
从简化方便入手,为我们创建的对话框,都增加了一个缺省的
Enter
?/span>
Esc
键响应,前者响应对话框?/span>
IDOK
按钮Q后者响?/span>
IDCANCEL
按钮Q但是有时我们创建的对话框不需要这个简便的响应操作Q奇怪的是我们在去除
IDOK
按钮?/span>
IDCANCEL
按钮后还是实C了禁止如上两个缺省按键的响应。那么该如何实现呢,以下便是解决q程Q以去除
Enter
键响应ؓ例)Q?/span>
首先Q利?/span>
ClassWizard
为对话框?/span>
IDOK
d单击响应函数Q去除该函数中默认的
OnOK
函数响应Q如下所C?/span>
void CDialogDemoDlg::OnOK()
{
CDialog::OnOK();
}
改ؓ
void CDialogDemoDlg::OnOK()
{
//CDialog::OnOK();
}
注:q行如上q步后,昄你不论再怎么?/span>
Enter
键都无法使对话框退ZQ但是也D该对话框q用户用鼠标点?/span>
IDOK
按钮也无法实现对话框默认?/span>
OnOK
函数了。所以这一步只是解决了一个现象问题,本没有真正解决问题。下面我们要恢复q个按钮的功能?/span>
其次Q去除缺省按钮。在资源视图下,?/span>
IDOK
按钮属性栏中的
Default button
属性勾厅R得默?/span>
Enter
键不再l响应该按钮?/span>
W三Q恢?/span>
OnOk
函数的功能。首先在资源视图中,?/span>
IDOK
按钮?/span>
ID
改变?/span>
IDC_OK(
q里用户Ҏ需要可以自p|资源的
ID)
Q其ơؓ其添加单d应函敎ͼq在需要执行结束的代码后添?/span>
CDialog::OnOK()
函数Q如下所C:
void CDialogDemoDlg::OnOk()
{
......//
省其它处理代?/span>
CDialog::OnOK();
}
如上所q过E后Q整?/span>
Enter
键默认响应已l去除,而且也不会媄响对话框正常响应
OnOK
函数的功能,在如上对话框处理后,如果
IDC_OK
按钮?/span>
TabOrder
属性ؓ
1
的话Q按
Enter
键就相当于鼠标单?/span>
IDC_OK,
所以用户必d?/span>
Layout
”的?/span>
Tab Order
”下面重新布|?/span>
IDC_OK
按钮?/span>
TabOrder
序?/span>
也许l心的读者会发现Q有时当不用q行W三步操作时Q直接将W一?/span>
OnOK()
函数中注释的
CDialog::OnOK()
代码重新启用Q也是可以“完成”去?/span>
Enter
键默认响应的Ҏ,何必要进行第三步q么复杂的操作,其实不进行第三步操作Qƈ非真正实C去除
Enter
键响应,比如对话框上有一?/span>
Edit
控gӞ在编?/span>
Edit
l束后,用户按下
Enter
键,E序又马上d?/span>
OnOK
函数了?/span>
好了Q去除对话框
Enter
键缺省响应的q程׃l到q里Q读者可以试着d?/span>
Esc
键的响应?/span>
Q注Q该Ҏ本h也是书上得来Q仅做个人ȝQ?/span>