进程通常被定义为一个正在运行的程序的实例,它由两个部分组成:一个是操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计信息的地方。另一个是地址空间,它包含所有的可执行模块或DLL模块的代码和数据。它还包含动态分配的空间。如线程堆栈和堆分配空间。每个进程被赋予它自己的虚拟地址空间,当进程中的一个线程正在运行时,该线程可以访问只属于它的进程的内存。属于其它进程的内存则是隐藏的,并不能被正在运行的线程访问。
两个进程之间进行通讯方式包括:剪贴板Clipboard、窗口WM_COPYDATA消息、共享内存方式、消息管道、邮件槽、Windows套接字、远程过程调用RPC、串行/并行通信、COM/DCOM。
本文介绍共享内存方式实现进程之间进行通讯。FileMapping用于将存在于磁盘的文件放进一个进程的虚拟地址空间,并在该进程的虚拟地址空间中产生一个区域用于“存放”该文件,这个空间就叫做File View,系统并同时产生一个File Mapping Object(存放于物理内存中)用于维持这种映射关系,这样当多个进程需要读写那个文件的数据时,它们的File View其实对应的都是同一个File Mapping Object,这样做可节省内存和保持数据的同步性,并达到数据共享的目的。使用共享内存在处理大数据量数据的快速交换时表现出了良好的性能,在数据可靠性等方面要远远高于发送WM_COPYDATA消息的方式(WM_COPYDATA消息用于小数据量的进程间交换)。这种大容量、高速的数据共享处理方式在设计高速数传通讯类软件中有着很好的使用效果。
1、共享内存方式实现进程之间通讯步骤:
一个进程处理过程:CreateFileMapping()函数创建一个内存映射文件对象,如果创建成功则通过MapViewOfFile()函数将此文件映射对象的视图映射进地址空间,同时得到此映射视图的首址。访问地址进行数据读写。当数据传输结束,即将退出程序时,需要将映射进来的内存文件映射对象视图卸载和资源的释放等处理。这部分工作主要由UnmapViewOfFile()和CloseHandle()等函数完成。
另一个进程处理过程:OpenFileMapping()函数打开一个命名指定的文件映射对象,如果执行成功,继续用MapViewOfFile()函数将此文件映射对象的视图映射到接收应用程序的地址空间并得到其首址。访问地址进行数据读写。当数据传输结束,即将退出程序时,需要将映射进来的内存文件映射对象视图卸载和资源的释放等处理。这部分工作主要由UnmapViewOfFile()和CloseHandle()等函数完成。
关于数据处理完成之后怎样通知另外一个进程去读取,有两种方法:一是消息通知;二是定义一个数据结构头,设置标志位,线程检测标志位,根据标志位状态判断是否处理完成,如果处理完成通过事件来唤醒另外事件进行处理。
HANDLE CreateFileMapping(
HANDLE hFile, //物理文件句柄
LPSECURITY_ATTRIBUTES lpAttributes, //安全设置
DWORD flProtect, //保护设置
DWORD dwMaximumSizeHigh, //高位文件大小
DWORD dwMaximumSizeLow, //低位文件大小
LPCTSTR lpName //共享内存名称
);
1) 物理文件句柄
任何可以获得的物理文件句柄, 如果你需要创建一个物理文件无关的内存映射也无妨, 将它设置成为 0xFFFFFFFF(INVALID_HANDLE_VALUE)就可以了.
如果需要和物理文件关联, 要确保你的物理文件创建的时候的访问模式和"保护设置"匹配, 比如: 物理文件只读, 内存映射需要读写就会发生错误. 推荐你的物理文件使用独占方式创建.
如果使用 INVALID_HANDLE_VALUE, 也需要设置需要申请的内存空间的大小, 无论物理文件句柄参数是否有效, 这样 CreateFileMapping 就可以创建一个和物理文件大小无关的内存空间给你, 甚至超过实际文件大小, 如果你的物理文件有效, 而大小参数为0, 则返回给你的是一个和物理文件大小一样的内存空间地址范围. 返回给你的文件映射地址空间是可以通过复制, 集成或者命名得到, 初始内容为0.
2) 保护设置
就是安全设置, 不过一般设置NULL就可以了, 使用默认的安全配置. 在win2k下如果需要进行限制, 这是针对那些将内存文件映射共享给整个网络上面的应用进程使用是, 可以考虑进行限制.
3) 高位文件大小
弟兄们, 我想目前我们的机器都是32位的东东, 不可能得到超过32位进程所能寻址的私有32位地址空间, 一般还是设置0吧, 我没有也不想尝试将它设置超过0的情况.
4) 低位文件大小
这个还是可以进行设置的, 不过为了让其他共享用户知道你申请的文件映射的相关信息, 我使用的时候是在获得的地址空间头部添加一个结构化描述信息, 记录内存映射的大小, 名称等, 这样实际申请的空间就比输入的增加了一个头信息结构大小了, 我认为这样类似BSTR的方式应该是比较合理的.
5) 共享内存名称
这个就是我今天测试的时候碰壁的祸根, 因为为了对于内存进行互斥访问, 我设置了一个互斥句柄, 而名称我选择和命名共享内存同名, 之下就是因为他们使用共同的namespace导致了错误, 呵呵.
7) 调用CreateFileMapping的时候GetLastError的对应错误
ERROR_FILE_INVALID 如果企图创建一个零长度的文件映射, 应有此报
ERROR_INVALID_HANDLE 如果发现你的命名内存空间和现有的内存映射, 互斥量, 信号量, 临界区同名就麻烦了
ERROR_ALREADY_EXISTS 表示内存空间命名已经存在
8) 相关服务或者平台的命名保留
Terminal Services:
命名可以包含 "Global" 或者 "Local" 前缀在全局或者会话名空间初级文件映射. 其他部分可以包含任何除了()以外的字符, 可以参考 Kernel Object Name Spaces.
Windows 2000 or later:
如果 Terminal Services 没有运行 "Global" 和 "Local" 前缀的特殊含义就被忽略了
LPVOID
WINAPI
MapViewOfFile(
__in HANDLE hFileMappingObject,
__in DWORD dwDesiredAccess,
__in DWORD dwFileOffsetHigh,
__in DWORD dwFileOffsetLow,
__in SIZE_T dwNumberOfBytesToMap
);
hFileMappingObject是共享文件对象。
dwDesiredAccess是文件共享属性。
dwFileOffsetHigh是文件共享区的偏移地址。
dwFileOffsetLow是文件共享区的偏移地址。
dwNumberOfBytesToMap是共享数据长度。
HANDLE WINAPI OpenFileMapping(
__in DWORD dwDesiredAccess,
__in BOOL bInheritHandle,
__in LPCTSTR lpName
);
dwDesiredAccess Long,带有前缀FILE_MAP_XXX的一个常数。参考MapViewOfFile函数的dwDesiredAccess参数的说明
bInheritHandle Long,如这个函数返回的句柄能由当前进程启动的新进程继承,则这个参数为TRUE
lpName String,指定要打开的文件映射对象名称
2、共享内存实现进程间通讯实例:
进程1:
#define SHARE_MEMORY_SIZE 3145728 //3MB
#define WM_EARSE_DATA_WRITE WM_USER+110
#define WM_EARSE_DATA_READ WM_USER+111
typedef struct tagFileInfo
{
TCHAR szFileName[MAX_PATH];//文件名
long nOffset;//偏移量
long nImageSize;//大小
int nImageType;//图像类型
}FileInfo;
typedef struct tagEarseImageData
{
FileInfo szSrc;//源文件信息
FileInfo szDst1;//目标文件1
FileInfo szDst2;//目标文件2
int nStatus;//状态 0,表示发送;1,表示接受
int bSuccess;//成功 1,表示成功;0,表示失败
TCHAR szExtend[4096];//扩展
}EarseImageData;
//定义共享内存区域
HANDLE lhShareMemory1 = CreateFileMapping(HANDLE(0xFFFFFFFF), NULL, PAGE_READWRITE,0, SHARE_MEMORY_SIZE,L"EaresImageSharedMemory");
if (NULL==lhShareMemory1)
{
if (GetLastError()!=ERROR_ALREADY_EXISTS)
{
return 0;
}
}
char* lpBuffer = NULL;
CxImage image1;//CxImage图像处理类
bool bRet = image1.Load(sSrcFn,CXIMAGE_FORMAT_TIF);
long nDataSize = 0;
int nImageType = 6;
lpBuffer = (char*)MapViewOfFile(lhShareMemory1, FILE_MAP_WRITE, 0, 0, SHARE_MEMORY_SIZE);
if(lpBuffer)
{
EarseImageData imageData;
long iOffset = sizeof(EarseImageData);
_tcscpy(imageData.szSrc.szFileName,sSrcFn);
imageData.szSrc.nImageType = nImageType;
imageData.szSrc.nImageSize = nDataSize;
imageData.szSrc.nOffset = iOffset;
imageData.nStatus = 0;
imageData.bSuccess = 0;
memcpy(lpBuffer,&imageData,iOffset);
HWND hFindWnd = ::FindWindow(NULL,L"EarseImage");//查找另外一个进程2的窗口,发送与准备数据通知另外进程进行处理
if(hFindWnd)
{
::SendMessage(hFindWnd,WM_EARSE_DATA_WRITE,0,0);
}
}
//WM_EARSE_DATA_READ消息处理,用于接收进程2处理完的数据
LRESULT CXXXDlg::OnEarseImageRead(WPARAM wParam,LPARAM lParam)
{
HANDLE lhShareMemory;
char* lpcBuffer;
lhShareMemory = OpenFileMapping(FILE_MAP_READ, false,L"EaresImageSharedMemory");
if (lhShareMemory!=NULL)
{
lpcBuffer = (char*)MapViewOfFile(lhShareMemory, FILE_MAP_READ, 0, 0, SHARE_MEMORY_SIZE);
if (lpcBuffer!=NULL)
{
EarseImageData *pData = (EarseImageData*)lpcBuffer;
if(pData)
{
if(pData->bSuccess==1)
{
bool bRet1 = m_img1.Decode((BYTE *)(lpcBuffer+pData->szDst1.nOffset),pData->szDst1.nImageSize,pData->szDst1.nImageType);
bool bRet2 = m_img2.Decode((BYTE *)(lpcBuffer+pData->szDst2.nOffset),pData->szDst2.nImageSize,pData->szDst2.nImageType);
//bRet1 = m_img1.Save(L"D://image1.tif",pData->szDst1.nImageType);
//bRet2 = m_img2.Save(L"D://image2.tif",pData->szDst2.nImageType);
}
}
}
}
return 1;
}
if(lpBuffer!=NULL)
{
UnmapViewOfFile(lpBuffer);
}
if(lhShareMemory1!=NULL)
{
CloseHandle(lhShareMemory1);
}
进程2:
#define SHARE_MEMORY_SIZE 3145728 //3MB
#define WM_EARSE_DATA_WRITE WM_USER+110
#define WM_EARSE_DATA_READ WM_USER+111
typedef struct tagFileInfo
{
TCHAR szFileName[MAX_PATH];//文件名
long nOffset;//偏移量
long nImageSize;//大小
int nImageType;//图像类型
}FileInfo;
typedef struct tagEarseImageData
{
FileInfo szSrc;//源文件信息
FileInfo szDst1;//目标文件1
FileInfo szDst2;//目标文件2
int nStatus;//状态 0,表示发送;1,表示接受
int bSuccess;//成功 1,表示成功;0,表示失败
TCHAR szExtend[4096];//扩展
}EarseImageData;
//WM_EARSE_DATA_WRITE消息处理函数
HANDLE lhShareMemory;
char* lpcBuffer;
lhShareMemory = OpenFileMapping(FILE_MAP_READ|FILE_MAP_WRITE, false,L"EaresImageSharedMemory");
if (lhShareMemory!=NULL)
{
lpcBuffer = (char*)MapViewOfFile(lhShareMemory, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, SHARE_MEMORY_SIZE);
if (lpcBuffer!=NULL)
{ EarseImageData *pData = (EarseImageData *)lpcBuffer;
CxImage image1,cxTmp1,cxTmp2;
bool bRet = image1.Load(pData->szSrc.szFileName,pData->szSrc.nImageType);
if(bRet)
{
CImageProcess g_imageProcess;//CImageProcess为图像处理类
if(g_imageProcess.EraseRegion(&image1,&cxTmp1,&cxTmp2))
{
pData->bSuccess = 1;
long iOffset = sizeof(EarseImageData);
BYTE *lpImageData1= NULL;
BYTE *lpImageData2 = NULL;
int nImageType = pData->szSrc.nImageType;
long nImageSize = 0;
cxTmp1.Encode(lpImageData1,nImageSize,nImageType);
pData->szDst1.nOffset = iOffset;
pData->szDst1.nImageSize = nImageSize;
pData->szDst1.nImageType = nImageType;
memcpy(lpcBuffer+iOffset,lpImageData1,nImageSize);
iOffset = iOffset+nImageSize;
cxTmp2.Encode(lpImageData2,nImageSize,nImageType);
pData->szDst2.nOffset = iOffset;
pData->szDst2.nImageSize = nImageSize;
pData->szDst2.nImageType = nImageType;
memcpy(lpcBuffer+iOffset,lpImageData2,nImageSize);
if(lpImageData1!=NULL)
{
delete []lpImageData1;
lpImageData1 = NULL;
}
if(lpImageData2!=NULL)
{
delete []lpImageData2;
lpImageData2 = NULL;
}
}
else
{
pData->bSuccess = 0;
}
}
else
{
pData->bSuccess = 0;
}
HWND hFindWnd = ::FindWindow(NULL,L"UnionRectTest");//查找进程1的窗口,发送消息通知数据已经处理完成
if(hFindWnd)
{
::SendMessage(hFindWnd,WM_EARSE_DATA_READ,0,0);
}
}
}
if(lpcBuffer!=NULL)
{
UnmapViewOfFile(lpcBuffer);
}
///
3、注意事项
1)、共享名一定要特殊命名(否则就有可能因为与系统级别的对象同名而导致失败。虽然概率不大,但不得不防)。
2)、在CreateFileMapping(...),OpenFileMapping(...),MapViewOfFile(..)函数中,映射的打开方式参数最好是一致的。
3)、每用MapViewOfFile(...)函数映射一次,都必须UnmapViewOfFile(...)一次。
4)、使用共享内存在处理大数据量数据的快速交换时表现出了良好的性能,在数据可靠性等方面要远远高于发送WM_COPYDATA消息的方式;而WM_COPYDATA消息用于小数据量的进程间交换实现更加方便简单。
5)、用MapViewOfFile处理大文件时,如果文件过大,如400M,则无法一次性映射入内存,否则会出现1132错误,即内存不足。原因可能为操作系统无法找到连续的内存。因此需要通过分页的方式,逐页将文件内容映射到内存。