起動プロセスの伝達パラメータが長すぎることを解決する方法
8801 ワード
仕事の中で、QAさんは私たちのプログラムをテストしたとき、XPの下で、私たちのAプロセスは私たちのBプロセスを起動できないことを発見しました.Win 7 64 bitシステムでは機能が正常です.RDさんがデバッグした後、私たちのAプロセスでShellExcuteを使ってBプロセスを開始したことを発見しました(breaksoftwareからのcsdnブログを転載してください)
それはShellExcuteでパラメータ長制限の問題だと思っていました.私はこの論理をCreateProcessを使って実現することにしました.そうすれば、私はより多くの制御権力を持つことになります.しかし、最後に問題は依然として残っていることに気づきました.MSDNのCreateProcessに関するlpCommandLineの説明を見たからです.
この問題を解決するために、まず問題が発生したシーンを分析します. AプロセスBプロセス を起動する AプロセスがBプロセスを開始するときに長いデータを渡す AプロセスはBプロセスの実行結果とライフサイクル に関心を持たない. BプロセスAプロセスのライフサイクル に関心がない
このような問題に遭遇すると,まずパイプ(Pipe)やSocketのようなプロセス間通信手段を用いることを考えなければならない.この方法は上記の特徴のうち1,2の2つの問題を解決することができる.しかし、パイプとSocketの最も直感的なイメージは、双方のインタラクティブな通信です.すなわち、AはBの存在に関心を持ち、BもAの存在に関心を持つ.どちらか一方が切断されると、他方のプロセスに影響します.これは私たちの上記の特徴の3、4とは逆です.では、どうやって解決しますか?メモリマッピングファイルという別のプロセス間通信方法を考えました.
メモリマッピングファイルには、名前付きファイルと匿名メモリマッピングファイルの2種類があります.ネーミング・ファイルは、一般的にセキュリティ要件の低いプロセス間通信に使用されますが、匿名メモリ・マッピング・ファイルは、一般的にセキュリティが高いプロセス間通信に使用されます.セキュリティの高い匿名メモリマッピングファイルを優先するに違いありません.以前に書いたプロジェクトの例を挙げて、匿名メモリマッピングファイルを使用してプロセス間通信を行う方法を説明します. AとBプロセスはパイプ接続 を確立する. A匿名メモリマッピングファイル を作成 A Bプロセスハンドル を開く. A「匿名」メモリマッピングファイルHandle DuplicateをBプロセスに渡し、Bプロセスで使用可能なHandleB を生成する. Aは、プロセスB にHandleBをパイプを介して渡す.プロセスBは、HandleBを使用してデータ にアクセスする.
このフローは、匿名パイプを使用してプロセス間通信を行うために必要な条件を与える:Bプロセスは既に存在し、Duplicate後のHandleBを使用するようにBプロセスに通知することができる.
私たちのシーンでは、ファイルマッピング以外の通信方式は使用したくない.また、Bプロセスの作成時にファイルマッピングをBプロセスに渡すため、匿名メモリマッピングファイルは使用できません.
名前付きメモリマッピングファイルが1つしか残っていません.この方式には様々な不安全性があるが、現在のシーンで唯一選択できる方向である.
名前の競合が発生しないようにします.ランダムに「名前」を生成するシナリオを選択しました
サブプロセスは、「FM」をキーとするパラメータを受信します.このパラメータには、「ネーミング」メモリマッピングファイルの名前が保存されています.この名前を使用すると、親プロセスから転送されたデータの内容を取得できます.
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に約32 Kバイトのパラメータを渡したことです.それは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を簡単に使うと、私たちの問題を解決できないようです.この問題を解決するために、まず問題が発生したシーンを分析します.
このような問題に遭遇すると,まずパイプ(Pipe)やSocketのようなプロセス間通信手段を用いることを考えなければならない.この方法は上記の特徴のうち1,2の2つの問題を解決することができる.しかし、パイプとSocketの最も直感的なイメージは、双方のインタラクティブな通信です.すなわち、AはBの存在に関心を持ち、BもAの存在に関心を持つ.どちらか一方が切断されると、他方のプロセスに影響します.これは私たちの上記の特徴の3、4とは逆です.では、どうやって解決しますか?メモリマッピングファイルという別のプロセス間通信方法を考えました.
メモリマッピングファイルには、名前付きファイルと匿名メモリマッピングファイルの2種類があります.ネーミング・ファイルは、一般的にセキュリティ要件の低いプロセス間通信に使用されますが、匿名メモリ・マッピング・ファイルは、一般的にセキュリティが高いプロセス間通信に使用されます.セキュリティの高い匿名メモリマッピングファイルを優先するに違いありません.以前に書いたプロジェクトの例を挙げて、匿名メモリマッピングファイルを使用してプロセス間通信を行う方法を説明します.
このフローは、匿名パイプを使用してプロセス間通信を行うために必要な条件を与える:Bプロセスは既に存在し、Duplicate後のHandleBを使用するようにBプロセスに通知することができる.
私たちのシーンでは、ファイルマッピング以外の通信方式は使用したくない.また、Bプロセスの作成時にファイルマッピングをBプロセスに渡すため、匿名メモリマッピングファイルは使用できません.
名前付きメモリマッピングファイルが1つしか残っていません.この方式には様々な不安全性があるが、現在のシーンで唯一選択できる方向である.
名前の競合が発生しないようにします.ランダムに「名前」を生成するシナリオを選択しました
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を作成することです.保留で作成する理由は、プロセスのハンドルを取得し、そのプロセスハンドルを使用してメモリマッピングファイルのハンドルHandleBをDuplicateから取り出すためです.なぜなら、メモリマッピングファイルをサブプロセス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」をキーとするパラメータを受信します.このパラメータには、「ネーミング」メモリマッピングファイルの名前が保存されています.この名前を使用すると、親プロセスから転送されたデータの内容を取得できます.
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.
"<<::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;
}
エンジニアリングダウンロードアドレス