`
20386053
  • 浏览: 461481 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

工作中,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;
}
工程下载地址

分享到:
评论

相关推荐

    获取其他进程启动参数方法

    一种常见的方法是使用Windows API函数,如`CreateToolhelp32Snapshot`和`Process32Next`来遍历所有进程,然后获取目标进程的进程ID。之后,可以调用`OpenProcess`来打开该进程,接着使用`QueryFullProcessImageName`...

    wpf相互调用传递参数

    在Windows Presentation Foundation (WPF) 中,开发人员经常需要创建可独立运行的exe应用程序,并且在不同的上下文中互相调用,同时传递参数。这在多种场景下都非常实用,例如一个主程序启动另一个辅助程序,或者在...

    易语言CreateThread启动线程传递多个类型参数.rar

    综上所述,"易语言CreateThread启动线程传递多个类型参数.rar"这个资源包提供了一个解决在易语言中使用`CreateThread`创建多线程并传递多种类型参数的方法。通过学习和应用这个示例,开发者能够更好地理解和掌握...

    易语言多线程传递文本参数两种方法源码

    易语言中,全局变量可以在程序的不同线程间共享数据,因此可以用来传递参数。这种方式简单直接,但需要注意线程安全问题。当多个线程同时读写同一全局变量时,可能会引发数据冲突,需要通过同步机制(如锁)来确保...

    exe程序之间传递参数(wince)

    在Windows CE(Wince)操作系统环境下,开发独立的EXE应用程序时,往往需要在不同的程序之间传递参数,以便实现数据共享或协调不同功能模块的工作。本文将深入探讨如何在两个独立的EXE程序之间传递参数,特别是涉及...

    使用ShellExecuteEx调用控制台程序(exe)并传入多个参数

    `ShellExecuteEx`是Windows API中的一个函数,位于shellapi.h头文件中,它提供了一种灵活的方式来启动应用程序、打开文档、发送电子邮件等。相比简单的`ShellExecute`函数,`ShellExecuteEx`提供了更丰富的信息和...

    vb.net多线程通过Deletgrate委托调用传递参数.rar

    Delegate是.NET框架中的一种类型安全的函数指针,它允许我们将方法作为参数传递给其他方法,同时也支持事件处理。 首先,理解委托的概念。委托在VB.NET中相当于一个事件处理程序的签名,它定义了一个方法集合的...

    VC向Vb传递命令行参数.rar_传递参数

    标题“VC向Vb传递命令行参数.rar_传递参数”指出,这个压缩包包含了一个关于如何在VC中创建程序,并将命令行参数传递给VB应用程序的示例。这个过程对于需要交互或整合两个不同编程环境的应用程序来说很有用。 首先...

    delphi多线程传递参数及同步二

    在编程领域,多线程是实现并发执行任务的重要机制,特别是在...理解这些基本概念和技巧对于编写高效、稳定的多线程应用程序至关重要。在实际项目中,还需要考虑异常处理、资源管理等问题,以确保程序的健壮性和安全性。

    进程间参数传递

    在实际开发过程中,经常会遇到需要在一个进程中传递参数到另一个进程的情况,这种需求通常可以通过多种技术手段来实现。本篇文章将详细介绍如何在C#环境中通过不同的方法实现进程间的参数传递,并给出具体的示例代码...

    编写可传递参数的COM组件

    它定义了一种标准的方式,使得组件可以被其他应用程序透明地调用,而无需知道组件的具体实现。COM组件通常以DLL(动态链接库)的形式存在,可以是本地执行或通过Internet进行远程调用。 2. **接口设计**:在C++中,...

    Android编程实现启动另外的APP及传递参数的方法

    在Android编程中,有时我们需要从一个应用程序(APP)启动另一个应用程序,并且可能还需要在启动时传递参数。这个过程可以通过Intent对象来实现。以下是对标题和描述中提到的知识点的详细说明: 1. **启动其他APP的...

    winfrom Windows服务监控exe进程,启动exe窗体应用程序

    在Windows操作系统中,WinForm是一种基于.NET Framework的用户界面开发工具,用于构建桌面应用程序。而Windows服务则是在没有用户交互的情况下长时间运行的程序,通常用于后台任务和系统级功能。本话题关注的是如何...

    Android 开发Activity基础 启动和跳转并传递参数

    在Android应用开发中,Activity是构成应用程序的基本单元,它代表了一个可视化的用户界面。了解Activity的基础,特别是如何启动、跳转以及传递参数,是每个Android开发者必须掌握的关键技能。 一、Activity的基础...

    Cocoa App获取启动参数

    本文将深入探讨如何在Cocoa App中获取启动参数,以及如何通过另一个Cocoa应用程序传递这些参数。 首先,我们要理解什么是启动参数。在命令行环境中,程序启动时可以接受一系列的参数,这些参数在程序执行前就已经...

    anctivity间传递参数

    有两种主要方式传递参数: - **不传递参数**: 当不需要传递数据时,只需创建一个Intent并指定目标Activity: ```java startActivity(new Intent(MainActivity.this, OneActivity.class)); ``` - **传递参数...

    C#中只启动一个进程

    在C#编程中,有时我们需要确保某个应用程序只能运行一个实例,以防止多个实例同时运行导致资源浪费或数据冲突。这个需求通常在开发桌面应用时出现,例如为了保持数据库的唯一性或者避免用户意外打开多个窗口。标题...

    易语言启动线程传自定义数据类型参数源码

    通过阅读和学习这个源码,开发者可以更好地理解和掌握在易语言中启动线程并传递自定义数据类型参数的方法,这对于开发复杂的多线程程序非常有帮助。 在实际应用中,需要注意的是,线程的并发执行可能导致数据竞争和...

    Androidstudio实现页面跳转和传递参数

    Intent是一种用来表达应用程序之间意图的类,可以理解为一个消息对象,它告诉系统你要执行什么操作。在实现页面跳转时,我们通常创建一个新的Intent实例,并指定目标Activity(即要跳转到的页面)。以下是一个简单的...

    Xamarin.Forms应用程序导航系统的帮助程序:传递参数、恢复_C#_下载.zip

    这个资源包“Xamarin.Forms应用程序导航系统的帮助程序:传递参数、恢复”是针对C#开发者设计的,旨在提供一种更高效和方便的方式来管理页面间的导航,同时处理参数的传递和数据恢复。以下是对这个主题的详细讲解: ...

Global site tag (gtag.js) - Google Analytics