工作中,QA同学在测试我们程序的时候,发现在XP下,我们的A进程无法启动我们的B进程。而在Win7 64bit系统下功能正常。RD同学调试后,发现我们A进程中使用ShellExcute去启动了B进程(转载请指明出于breaksoftware的csdn博客)

HINSTANCE ShellExecute(_In_opt_  HWND hwnd,_In_opt_  LPCTSTR lpOperation,_In_    LPCTSTR lpFile,_In_opt_  LPCTSTR lpParameters,_In_opt_  LPCTSTR lpDirectory,_In_    INT nShowCmd
);

其中不成功的场景是:我们给lpParameters传递了大概32K字节长度的参数。
        我当时就觉得这个是因为ShellExcute中参数长度限制问题。我决定将这个逻辑使用CreateProcess去实现,这样我将会有更多的控制权力。但是最后我们发现问题还是依旧的,因为我们查看MSDN关于CreateProcess的lpCommandLine说明:

lpCommandLine [in, out, optional]
The command line to be executed. The maximum length of this string is 32,768 characters, including the Unicode terminating null character. 

它最长只可以穿32768个字符(而我之后测试结果却是32766)。看来简单的使用CreateProcess还是不能解决我们的问题。
        为了解决这个问题,我们首先分析问题出现的场景:

  1. A进程去启动B进程
  2. A进程启动B进程时要传递一个很长的数据
  3. A进程不关心B进程执行结果和生命周期
  4. B进程不关心A进程的生命周期

遇到这类问题,首先肯定先想到,使用管道(Pipe)或者Socket这类进程间通信手段。这个方法可以解决上述特点中的1、2两个问题。但是管道和Socket给人最直观的映像就是:双方交互式通信。即A要关心B的存在与否,B也要关心A的存在与否。任何一方断了,都会影响另一方的流程。这个和我们上述特点中的3、4是相背的。那么怎么解决呢?我想到了另一个进程间通信的方法——内存映射文件。
        内存映射文件分为两种,一种是“命名”文件,一种是“匿名”内存映射文件。“命名”文件一般用于安全性要求不高的进程间通信,而“匿名”内存映射文件一般是用于安全性较高的进程间通信。我们肯定优先考虑安全性更高的“匿名”内存映射文件。我举一个之前我写得工程的例子解释如何使用“匿名”内存映射文件进行进程间通信的:

  1. A和B进程建立管道连接
  2. A创建一个“匿名”内存映射文件
  3. A打开B进程句柄
  4. A将“匿名”内存映射文件Handle Duplicate给B进程,生成B进程可以使用的HandleB
  5. A将HandleB通过管道传递给进程B
  6. 进程B使用HandleB访问数据

这个流程给出了一个使用匿名管道进行进程间通信的一个必要的条件:B进程的已经存在,并且可以通知B进程去使用Duplicate后的HandleB。
        在我们的场景中,就是不希望使用除了文件映射之外的通信方式。而且,我们要在B进程创建时,就将文件映射传给B进程,所以无法使用“匿名”内存映射文件。
        目前只剩下“命名”内存映射文件一条路可以走了。虽然这种方式存在种种不安全性,但是它是目前场景下唯一可以选择的方向。
        为了不存在“名称”的冲突问题。我选择了随机生成“名称”的方案

VOID CTransmitParam::GenerateFileMappingName()
{time_t t;srand((unsigned)time(&t));WCHAR wchName[MAX_PATH] = {0};wsprintf( wchName, L"%d", rand());m_wstrFileMappingName.clear();m_wstrFileMappingName.append(wchName);
}

虽然每次都是随机的,但是我还是不放心这个“随机”碰撞的概率。于是我在创建内存映射文件时判断了下当前创建的“名字”是否在系统中已经存在。如果存在,我会重新随机生成名字并创建该名字的内存映射文件。

BOOL CTransmitParam::CreateFileMappingEx(DWORD dwNewBufferSize)
{BOOL bSuc = FALSE;int nMaxLoopCount = 32;do {m_hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, dwNewBufferSize, m_wstrFileMappingName.c_str());if ( NULL == m_hFileMapping ) {break;}if ( ERROR_ALREADY_EXISTS == ::GetLastError() ) {::CloseHandle(m_hFileMapping);m_hFileMapping = NULL;if ( 0 >= --nMaxLoopCount ) {break;}else {GenerateFileMappingName();continue;}}else {bSuc = TRUE;break;}}while (TRUE);return TRUE;
}

待内存映射文件创建成功后,我们往该“文件”中写入数据,其数据格式是:前sizeof(DWORD)保存的是要传递给子进程的数据长度,其后跟着数据内容。

struct StData {DWORD dwBufferSize; // 从BufferFirst开始的数据长度BYTE BufferFirst;
};

具体的数据填充代码是

BOOL CTransmitParam::PackData( LPVOID lpMem,DWORD dwNewBufferSize,LPCBYTE lpBuffer, DWORD dwBufferSize )
{BOOL bSuc = FALSE;do {LPBYTE lpFilePointer = (LPBYTE)lpMem;OVERLAPPED op;memset(&op, 0, sizeof(op));DWORD dwRead = 0;DWORD dwBufferSizeSize = sizeof(dwNewBufferSize);errno_t e = memcpy_s( lpFilePointer, dwNewBufferSize, &dwNewBufferSize, dwBufferSizeSize);if ( 0 != e ) {std::cerr<<"Memcpy_s Failed.The error code is"<<e<<std::endl;break;}lpFilePointer += sizeof(dwNewBufferSize);e = memcpy_s( lpFilePointer, dwNewBufferSize - dwBufferSizeSize, lpBuffer, dwBufferSize );if ( 0 != e ) {std::cerr<<"Memcpy_s Failed.The error code is"<<e<<std::endl;break;}bSuc = TRUE;} while (0);return bSuc;
}

下一步就是我们要使用挂起的方式创建子进程B。之所以要使用挂起方式创建,是因为我们要获取其进程的句柄,并且使用该进程句柄去Duplicate出内存映射文件句柄HandleB。之所以要这么做,所因为,我们要在此时让该内存映射文件和子进程B的生命周期相关联。因为从父进程角度来说,我们CreateFileMapping后,要进行对应的CloseHandle,从而不会造成资源泄露。如果我们不让父进程创建的内存映射文件和子进程B相关联,在父进程CloseHandle后,内存映射文件的引用计数将降为0,从而被释放掉。此时,子进程可能还没有时机去读取到内存映射文件。

BOOL CTransmitParam::CreateProcess_TransmitParam( LPCWSTR lpChildProcssPath )
{BOOL bSuc = FALSE;do {STARTUPINFO st;memset(&st, 0, sizeof(st));st.cb = sizeof(st);PROCESS_INFORMATION pi;memset(&pi, 0, sizeof(pi));std::wstring wstrCmd = GenerateCommandLine();BOOL bCreateSuc = CreateProcess( lpChildProcssPath, (LPWSTR) wstrCmd.c_str(), NULL, NULL, FALSE, /*CREATE_NO_WINDOW |*/ CREATE_SUSPENDED, NULL, NULL, &st, &pi );if ( FALSE == bCreateSuc ) {std::cerr<<"CreateProcess Error.The error code is"<<::GetLastError()<<std::endl;break;}HANDLE hTargetHandle = NULL;if ( FALSE == DuplicateHandle( GetCurrentProcess(), m_hFileMapping, pi.hProcess, &hTargetHandle, DUPLICATE_SAME_ACCESS, FALSE, DUPLICATE_SAME_ACCESS ) ) {std::cerr<<"DuplicateHandle Failed.The error code is"<<::GetLastError()<<std::endl;break;}if ( NULL != pi.hThread ) {::ResumeThread( pi.hThread );}CloseHandle(pi.hThread);CloseHandle(pi.hProcess);bSuc = TRUE;} while (0);return bSuc;
}

在父进程CloseHandle后,父进程的逻辑就此走完。我们再看下子进程的数据接收过程。
        子进程接收一个以“FM”为Key的参数,该参数中保存了“命名”内存映射文件的名字,通过该名字,我们可以获取父进程传送过来的数据内容。

BOOL CTransmitParam::UnPackData(LPVOID lpMem)
{BOOL bSuc = FALSE;do {m_dwRecvBufferLength = 0;errno_t e = memcpy_s( &m_dwRecvBufferLength, sizeof(m_dwRecvBufferLength), lpMem, sizeof(DWORD));if ( 0 != e ) {std::cerr<<"Memcpy_s Failed.The error code is"<<e<<std::endl;break;}if ( 0 == m_dwRecvBufferLength ) {std::cerr<<"FileMapping's size is 0.\n"<<::GetLastError()<<std::endl;break;}m_lpRecvBuffer = new BYTE[m_dwRecvBufferLength];memset( m_lpRecvBuffer, 0, sizeof(m_lpRecvBuffer));e = memcpy_s( m_lpRecvBuffer, m_dwRecvBufferLength, (LPBYTE)lpMem + sizeof(DWORD), m_dwRecvBufferLength );if ( 0 != e ) {std::cerr<<"Memcpy_s Failed.The error code is"<<e<<std::endl;break;}bSuc = TRUE;} while (0);return bSuc;
}BOOL CTransmitParam::GetRecvBuffer(const std::wstring& wstrFileMappingName)
{if ( NULL != m_lpRecvBuffer ) {return TRUE;}BOOL bSuc = FALSE;do {    HANDLE hFileMapping = OpenFileMapping( FILE_MAP_READ, FALSE, wstrFileMappingName.c_str());if ( NULL == hFileMapping ) {std::cerr<<"OpenFileMapping Failed.The error code is"<<::GetLastError()<<std::endl;break;}LPVOID lpMem = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);if ( NULL == lpMem ) {std::cerr<<"MapViewOfFile Failed.The error code is"<<::GetLastError()<<std::endl;break;}if ( FALSE == UnPackData(lpMem) ) {break;}UnmapViewOfFile(lpMem);CloseHandle(hFileMapping);bSuc = TRUE;} while (0);return bSuc;
}

工程下载地址

一种解决启动进程传递参数过长的方法相关推荐

  1. EXPRESS项目PM2启动NODE_ENV传参数不生效问题解决方法

    EXPRESS项目PM2启动NODE_ENV传参数不生效问题解决方法 参考文章: (1)EXPRESS项目PM2启动NODE_ENV传参数不生效问题解决方法 (2)https://www.cnblog ...

  2. 解决Springboot GET请求参数过长的情况

    项目场景: 使用Spring Boot进行项目开发,解决Springboot GET请求参数过长的情况 问题描述 报错信息:Springboot GET请求参数过长抛出异常:Request heade ...

  3. 参数无效_Shell 脚本启动如何传递参数

    我们在日常的脚本开发中,经常会碰到当脚本需要输入参数,然后脚本依据传递进来的参数作为依据,判断执行接下来的脚本逻辑.今天就介绍一下给Shell脚本传递参数进行交互的几种方式. 命令行参数 向脚本传递参 ...

  4. 转:Web页面通过URL地址传递参数常见问题及检测方法

    Web页面即我们在浏览器中所看到的网页,在Web应用程序中,其页面往往需要进行动态切换和数据交互,页面间的数据常规传递方法有多种,本文主要介绍Web页面处理程序中常见的URL地址参数传递方法,包括概述 ...

  5. 异构计算机 桌面,一种解决异构操作系统的复合桌面虚拟化架构及方法_2

    有多个容器虚拟机,在每一个容器虚拟机上运行有应用程序虚拟机. [0020]进一步的为更好的实现本发明所述结构,特别采用下述设置方式:还包括虚拟机模板库,所述虚拟机模板库分别与物理机集群和集群头节点相连 ...

  6. H5页面跳转传递参数过长

    最近做H5页面的时候,用href跳转的时候,遇到一个问题,url+parm太长,导致跳转页面接收的参数被截断. <a href="url"></a> 解决方 ...

  7. 详解10种解决启动故障的方法

    开机自检时出现问题后会出现各种各样的英文短句,短句中包含了非常重要的信息,读懂这些信息可以自己解决一些小问题,可是这些英文难倒了一部分朋友,下面是一些常见的BIOS短句的解释,大家可以参考一下. 1. ...

  8. 易语言大漠多线程启动线程传递参数自定义数据

    线程传参自定义数据 • 1.自定义数据类型 • 2.传递自定义数据类型的指针 • 3.把数据内存指针转换为新数据内存指针 • 4.赋给新的自定义数据变量 源码: .子程序 子程序1 .参数 a, 整数 ...

  9. C# 向Main函数传递参数(程序启动时传递参数)

    代码 static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleText ...

最新文章

  1. 基带信号传输之信道均衡
  2. wordpress启动初始化过程
  3. [Java基础]抽象类基础
  4. CSharp设计模式读书笔记(22):策略模式(学习难度:★☆☆☆☆,使用频率:★★★★☆)...
  5. 新松机器人发行价_知识创造财富,“机器人第一股”背后的院士是怎样炼成的?...
  6. MySQL + MyBatis 批量插入时存在则忽略或更新记录
  7. 【C语言】三种方式不使用分号输出HelloWorld
  8. jsp mysql超市管理系统_jsp超市管理系统 基于jsp+servlet+MySQL的超市管理系统(带数据库) - 下载 - 搜珍网...
  9. Java 8 API Stream让List操作更便捷
  10. 人脸识别**检测技术讨论:基于背景人脸相对运动的**判断方法
  11. python全栈构图_Python全栈-magedu-2018-笔记5
  12. 【算法学习笔记】81.动态规划 分类讨论 SJTU OJ 1075 括号匹配升级
  13. CUHK Occlusion Dataset(行人检测数据集)转换为YOLO+VOC数据集
  14. 进销存+php模板,PHP进销存源码 仓库管理系统 WEB进销存 php+mysql 网络版进销存
  15. 字节跳动笔试难题 扑克牌的移动
  16. 56ip数字标牌软件
  17. 在html中制作多彩照片墙,60个照片墙布置方案 记录浪漫时刻
  18. vivox27微信无法连接服务器,vivo X27微信拍照模糊怎么办?简单一个操作轻松解决...
  19. 魔法少女小圆计算机音乐,求魔法少女小圆一些背景音乐的名字
  20. 中国跨境电商现状及发展前景

热门文章

  1. apache 安装后默认主页无法打开_CAD教程:CAD软件打开图纸后钢筋符号无法读取的解决办法...
  2. 竞赛|数据竞赛Top解决方案开源整理
  3. OpenCV Hough Line变换
  4. Linux那些事儿 之 戏说USB(10)模型,又见模型
  5. kset_create_and_add
  6. windows下opencv安装及配置(vs2010环境)
  7. 数据库实验:数据库和表、查询、视图与安全性、存储过程及游标、触发器、综合实验-水果商店进阶
  8. Windows7无法自动将IP协议堆栈绑定到网络适配器的办法
  9. VSCode设置折叠左侧资源管理器所有文件夹的快捷键Alt+X、切换左侧活动栏显示隐藏快捷键Alt+Q
  10. OpenResty中遇到Can't locate Time/HiRes.pm in @INC问题的解决方法