为了解决这个问题我足足花了7天时间,最初我尝试了内存文件映射的方法,效果并不理想,经常出现内存不能访问的问题。之后我使用读写文件的形式,两个进程对文件的读写,如何检测数据的跟新以及文件访问的冲突让我头疼不已。说的Socket方法,也让我感到极其繁琐,不敢尝试。俗话说的好“柳暗花明又一村”,就在我无可奈何的时候,贵杰师弟给我出了使用管道的方式来进行进程间通信的方法。大牛就是大牛,在贵杰师弟的帮助下,终于解决了这个通信的问题。如果我这个应用能按期完成,我将把源码公开,大家一起学习。好了现在说说使用管道通信的具体内容吧。 在MSDN上有对管道(Pipes)的详细解释,管道有两种:匿名管道( Anonymous pipes)主要用于父进程和子进程之间的通信,命名管道(Named pipes)主要用来在两个无关的进程间通信,这两个进程也可以位于不同的计算机上面。在我的案例里面我使用了命名管道。首先我用VS2012写了一个控制台程序作为骨骼数据的生产者,暂且用SkeletonOut表示这个程序,用VS2008写了一个使用骨骼数据的MFC程序,暂且记为SkeletonIn,在SkeletonIn中定义一个消息函数OnKinect,只要出发这个事件,SkeletonIn便会打开应用程序SkeletonOut,让后SkeletonOut开始捕获人体骨骼数据,并将其源源不断的传送给SkeletonIn进程。
基于以上的构思,开始使用管道来进行通信。
首先,在SkeletonIn程序的OnKinect函数中写入如下代码:
void SkeletonIn::OnKinect() {
//创建命名管道
//对于管道名微软有严格的命名要求,具体详见MSDN
HANDLE hNamedPipe = CreateNamedPipe(_T(\"\\\\\\\\.\\\\pipe\\\\namedPipe\"),// 管道名 PIPE_ACCESS_DUPLEX | FILE_FLAG_WRITE_THROUGH, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 2, 1024, 1024,
NMPWAIT_WAIT_FOREVER, NULL);
//判断管道是否创建成功
if (INVALID_HANDLE_VALUE == hNamedPipe){ AfxMessageBox(_T(\"CreateNamedPipe failed.\")); return ; }
//打开SkeletonOut应用程序
WinExec(\"E:\\\\Kinect_Human\\\\Kinect Human Version2\\\\SkeletonOut.exe\
//等待连接管道,这是一个阻塞函数,会一直等待程序连接管道 if(!ConnectNamedPipe(hNamedPipe,NULL)) {
AfxMessageBox(_T(\"Cannot connet Named Pipe\")); CloseHandle(hNamedPipe); return; }
//读取数据
SkeletonData* pReadBuf = new SkeletonData; //将pReadBuf的内容都初始化为0
memset(pReadBuf,0,sizeof(SkeletonData));
DWORD dwRead = 0;//用于记录读入管道的数据量
//这里的ReadFile也是一个阻塞函数,会一直等待管道的数据更新,一旦管道中有新的数据立马读入,否则一直等待
if(!ReadFile(hNamedPipe,pReadBuf,sizeof(SkeletonData),&dwRead,NULL)) {
if(NULL!=pReadBuf) {
delete []pReadBuf; pReadBuf = NULL; }
AfxMessageBox(_T(\"Read File Failed.\")); }
//释放资源、关闭连接、关闭句柄 DisconnectNamedPipe(hNamedPipe);
CloseHandle(hNamedPipe);
} //这样SkeletonIn中就创建了一个管道通过这个管道可以冲SkeletonOut中获取骨骼数据
在SkeletonOut中添加如下代码,为了简便起见SkeletonOut我是使用控制台程序写的 CStringstrPipeName = _T(\"\\\\\\\\.\\\\pipe\\\\namedPipe\");\\\\管道名
//等待连接命名管道
cout<<\"Waif for connecting Pipe...\"< cout<<\"命名管道不存在!\"; return -1; } //打开命名管道 HANDLE hNamedPipe = CreateFile(strPipeName, GENRIC_READ|GENRIC_WRITE,0,NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); //判断是否打开命名管道成功 if(INVALID_HANDLE_VALUE == hNamedPipe) { cout<<\"Creat Pipe Failed:\"< DWORD dwWrite = 0; if(!WriteFile(hNamedPipe,&SkeletonPoints,sizeof(SkeletonData),&dwWrite,NULL)) { cout<<\"写入数据失败!\"< CloseHandle(hNamedPipe); 至此我们就完成了在SkeletonIn和SkeletonOut之间的数据传递,SkeletonOut实时捕获到的人体骨骼数据保存在SkeletonPoints结构中,而SkeletonOut通过使用命名管道将其传送到SkeletonIn中的pReadBuf中去。但是这样的只能传送Kinect捕获的第一个骨骼数据。而我们需要的是Kinect实时捕获的所有骨骼数据。为此我尝试在其中加了了While死循环,但是在操作管道的时候提示失败。贵杰表示这里需要使用线程来读取从SkeletonOut中读入数据,使用线程来监视数据的更新。这种方法常见于对硬件输出数据的读写。为此在SkeletonOut和SkeletonIn中需要对代码进行修改。 SkeletonIn中的代码修改比较方便,只需将代码: //等待连接命名管道 cout<<\"Waif for connecting Pipe...\"< cout<<\"命名管道不存在!\"; return -1; } //打开命名管道 HANDLE hNamedPipe = CreateFile(strPipeName, GENRIC_READ|GENRIC_WRITE,0,NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); //判断是否打开命名管道成功 if(INVALID_HANDLE_VALUE == hNamedPipe) { cout<<\"Creat Pipe Failed:\"< 而在SkeletonOut的代码书写则相对会麻烦一点,在修改代码之间,这里写一个今天贵杰教我的一个调试技巧。骨骼数据每秒钟刷新30次,要看我们传送数据是否成功,最好将每次传送过来的数据显示出来,这样传送成功与否一看便知。但是MFC中不能像控制台那样将数据输出在一个黑窗口上,但是我们可以将数据输出到一个文件中去,如下所示: ofstreamos(\"data.txt\"); cout.rdbuf(os.rdbuf()); /***********一些操作*****************/ CoutSkeleton(*pReadBuf);//输出数据 os.flush(); os.close(); 这里CoutSkeleton函数依然使用<<操作符对数据进行输出操作,不过因为 cout.rdbuf(os.rdbuf());函数的执行,cout<<的输出操作不再是将数据输出到控制台窗口上,而是将其输出到data.txt文件中。这将为我们程序的调试带来不小的便利性。 接下来我们需要创建一个线程专门用于从管道中读数据。这里需要注意的是线程函数是回调函数,因此它必须是静态成员函数或是在类外部声明的全局函数。下面的代码用于定义线程函数 DWORD WINAPI MyThreadProc (LPVOID lpParam) { HANDLE hNamedPipe = (HANDLE)lpParam; // 等待连接 if (!ConnectNamedPipe(hNamedPipe, NULL)){ AfxMessageBox(_T(\"connectNamedPide\")); CloseHandle(hNamedPipe); return -1; } // 输出!!!!!!!!!!! ofstreamos(\"data.txt\"); cout.rdbuf(os.rdbuf()); SkeletonData* pReadBuf = new SkeletonData; memset(pReadBuf, 0, sizeof(SkeletonData)); while(TRUE){ // 读数据 ------ DWORD dwRead = 0; if (!ReadFile(hNamedPipe, pReadBuf, sizeof(SkeletonData), &dwRead, NULL)){ if (NULL != pReadBuf){ delete [] pReadBuf; pReadBuf = NULL; break; } AfxMessageBox(_T(\"ReadFile failed.\")); } CoutSkeleton(*pReadBuf); } os.flush(); os.close(); // 释放资源关闭连接 关闭句柄 if (NULL != pReadBuf){ delete [] pReadBuf; pReadBuf = NULL; } DisconnectNamedPipe(hNamedPipe); CloseHandle(hNamedPipe); // -------------------------------------------------------------------------------------------- return 0; } 这里的参数pParam内传递的值不再是一个指针。具体可以查询MSDN。 当然这里我们只是定义课线程函数,我们还需要在OnKinect()函数中创建线程。改写之后的OnKinec函数如下: voidSkeletonIn::OnKinec() { // TODO: 在此添加命令处理程序代码 // ------------------------ 管道 --------------------------- // 创建管道 HANDLE hNamedPipe = CreateNamedPipe(_T(\"\\\\\\\\.\\\\pipe\\\\namedPipe\"),// 管道名 PIPE_ACCESS_DUPLEX | FILE_FLAG_WRITE_THROUGH, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 2, 1024, 1024, NMPWAIT_WAIT_FOREVER, NULL); if (INVALID_HANDLE_VALUE == hNamedPipe){ AfxMessageBox(_T(\"CreateNamedPipe failed.\")); return ; } // CreateProcess(); WinExec(\"E:\\\\Kinect_Human\\\\Kinect Human Version2\\\\KinectSkeletonOut.exe\ //创建线程 DWORD MyThredID = 0; CreateThread(0, 0, MyThreadProc, hNamedPipe, 0, &MyThredID); } 至此,完成进程SkeletonIn和进程SkeletonOut之间的通信。这个问题困扰了我7天时间,现在终于结果了,这里非常感谢贵杰为我改写代码、调试代码,也非常感谢谭军为我提供的读写文件的解决方案,为我提供的Socket通信解决方案,唐学涛老师为我提供的MSMQ解决方案,以及蕾姐帮我咨询MFC高手。 总之以后写程序尽量写MFC程序,不要再用控制台程序了,使用MFC程序会为后期的维护带来不小的便利。还有就是自己经常将一个函数写几百行的习惯也得改改了,这不利于他人读代码,也不利于自己维护代码。 因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- jqkq.cn 版权所有 赣ICP备2024042794号-4
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务