|
メニュー
アンテナ
|
[Win32][C] GUI アプリからコンソールアプリを実行するには概要[Delphi] GUI アプリからコンソールアプリを実行するには の C 版。いじょ(ぉ Delphi 版と違ってコールバックを使うので、データをリアルタイムにほしいときも 大丈夫。 コード長いので注意。まずはヘッダファイルから。 RedirectStdIO.h
/*
* RedirectStdIO.h
* コンソールリダイレクション
*/
#if !defined(REDIRECTSTDIO_H)
#define REDIRECTSTDIO_H
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "user32.lib")
// ターゲットアプリの stdin として与えるデータを pBuffer へコピーする。
// out LPVOID pBuffer ターゲットの stdin として与えたいデータをここへ
// コピーするためのバッファ。
// in DWORD dwBufferSize pBuffer のサイズ。
// out DWORD* pdwBytesWritten pBuffer に実際に書き込んだバイト数を返す。
// i/o LPVOID lpUser RedirectStdIO() に渡した lpUser. 用途は任意。
// ret BOOL 続きのデータがあるとき TRUE
typedef BOOL (CALLBACK *fnWriteStdIn)(LPVOID pBuffer, DWORD dwBufferSize,
DWORD* pdwBytesWritten, LPVOID lpUser);
// ターゲットアプリが吐いた stdout や stderr を pBuffer から受け取る
// in LPVOID pBuffer ターゲットアプリが吐いた stdout/stderr のデータ
// in DWORD dwBufferSize pBuffer に実際に入っているバイト数。
// out DWORD* pdwBytesRead pBuffer から読み取ったバイト数を返す。
// i/o LPVOID lpUser RedirectStdIO() に渡した lpUser. 用途は任意。
// ret BOOL もっとデータがほしければ TRUE
typedef BOOL (CALLBACK *fnReadStdOutErr)(LPVOID pBuffer, DWORD dwBufferSize,
DWORD* pdwBytesRead, LPVOID lpUser);
// コンソールアプリの標準入出力を横取りする(元には返さない)
DWORD RedirectStdIO(LPSTR pszCommandLine, // コマンドライン
fnWriteStdIn pfnWriteStdIn, // stdin を与えるコールバック
fnReadStdOutErr pfnReadStdOut,// stdout を受け取る callback
fnReadStdOutErr pfnReadStdErr,// stderr を受け取る callback
DWORD* pdwReturnCode, // コンソールアプリの戻り値
LPVOID lpUser, // callback へ渡す任意の値
DWORD dwBufferSize // 初期バッファサイズ。
// 0 でデフォルト
);
// おまけ:Win32 のファイルハンドルから stdio の FILE* を得る。
// mode は fopen() の mode 引数。
FILE* GetStdioStreamFromWin32Handle(HANDLE hWin32Handle, char* mode);
#endif // !REDIRECTSTDIO_H
RedirectStdIO.cpp
// 公開関数の説明はヘッダにある
#include "RedirectStdIO.h"
#include <fcntl.h>
#include <io.h>
// デフォルトバッファサイズ
#define BUFFER_SIZE 8192
// 入力待ち状態になるのを待つ時間
#define WAIT_FOR_READY 1000
// stdin/stdout/stderr を授受するループの中でのウェイト
#define WAIT_FOR_RUN 1
// パイプハンドルのラッパ
typedef struct tagPipe
{
HANDLE hRead;
HANDLE hWrite;
}Pipe;
// 関数内部で利用するコンテキスト
typedef struct tagRedirStdIOContext
{
Pipe StdIn;
Pipe StdOut;
Pipe StdErr;
HANDLE hStdInWritePipeDup; // stdin を与えるときはこいつに書き込む
fnWriteStdIn pfnWriteStdIn;
fnReadStdOutErr pfnReadStdOut;
fnReadStdOutErr pfnReadStdErr;
LPVOID lpUser;
BYTE* pbyBuffer; // 転送バッファ
DWORD dwBufferSize; // バッファサイズ
}RedirStdIOContext;
// stdin/stdout/stderr をコールバックを呼び出して処理する。
BOOL PumpPipe(RedirStdIOContext* pContext);
// hPipe のパイプへ pfnWriteStdIn から得たデータを書き込む
BOOL WriteToPipe(RedirStdIOContext* pContext, HANDLE hPipe,
fnWriteStdIn pfnWriteStdIn);
// hPipe から得たデータを pfnReadStdOutErr へ渡す
BOOL ReadFromPipe(RedirStdIOContext* pContext, HANDLE hPipe,
fnReadStdOutErr pfnReadStdOutErr);
DWORD RedirectStdIO(LPSTR pszCommandLine,
fnWriteStdIn pfnWriteStdIn,
fnReadStdOutErr pfnReadStdOut,
fnReadStdOutErr pfnReadStdErr,
DWORD* pdwReturnCode,
LPVOID lpUser,
DWORD dwDefaultBufferSize
)
{
RedirStdIOContext context;
ZeroMemory(&context, sizeof(RedirStdIOContext));
context.pfnWriteStdIn = pfnWriteStdIn;
context.pfnReadStdOut = pfnReadStdOut;
context.pfnReadStdErr = pfnReadStdErr;
context.lpUser = lpUser;
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
context.dwBufferSize = dwDefaultBufferSize;
// デフォルトバッファサイズ
if(context.dwBufferSize <= 0) context.dwBufferSize = BUFFER_SIZE;
// パイプ作りまくり
CreatePipe(&context.StdIn.hRead, &context.StdIn.hWrite, &sa,
context.dwBufferSize);
DuplicateHandle(GetCurrentProcess(), context.StdIn.hWrite,
GetCurrentProcess(), &context.hStdInWritePipeDup,
0, FALSE, DUPLICATE_SAME_ACCESS);
CloseHandle(context.StdIn.hWrite);
CreatePipe(&context.StdOut.hRead, &context.StdOut.hWrite, &sa,
context.dwBufferSize);
CreatePipe(&context.StdErr.hRead, &context.StdErr.hWrite, &sa,
context.dwBufferSize);
STARTUPINFO si = { sizeof(STARTUPINFO) };
si.dwFlags = STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdInput = context.StdIn.hRead;
si.hStdOutput = context.StdOut.hWrite;
si.hStdError = context.StdErr.hWrite;
PROCESS_INFORMATION pi;
// れっつ起動
BOOL r = CreateProcess(NULL, pszCommandLine, &sa, NULL, TRUE,
DETACHED_PROCESS | CREATE_NO_WINDOW,
NULL, NULL, &si, &pi);
if(r)
{
// 入力待ちに入るまで待っててやる
WaitForInputIdle(pi.hProcess, WAIT_FOR_READY);
// stdin 不要なら先に閉じてしまう
if(!context.pfnWriteStdIn)
CloseHandle(context.hStdInWritePipeDup);
// 転送用バッファ。
context.pbyBuffer = new BYTE[context.dwBufferSize];
DWORD dwRet;
do
{
// ポンプを動かす
if(!PumpPipe(&context))
break;
dwRet = WaitForSingleObject(pi.hProcess, WAIT_FOR_RUN);
}while(dwRet != WAIT_OBJECT_0); // ターゲットプロセスが生きている間
DWORD dwBytesInStdOut = 0, dwBytesInStdErr = 0;
while(1)
{
BOOL r;
r = PeekNamedPipe(context.StdOut.hRead, NULL, 0, NULL, &dwBytesInStdOut,
NULL);
r = r && PeekNamedPipe(context.StdErr.hRead, NULL, 0, NULL,
&dwBytesInStdErr, NULL);
if(!r || (!dwBytesInStdOut && !dwBytesInStdErr))
break;
PumpPipe(&context); // 書き残し、読み残しはないか?
Sleep(WAIT_FOR_RUN);
}
delete[] context.pbyBuffer;
// 終了コード
if(pdwReturnCode) GetExitCodeProcess(pi.hProcess, pdwReturnCode);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
// 後始末
CloseHandle(context.StdIn.hRead);
CloseHandle(context.StdOut.hRead); CloseHandle(context.StdOut.hWrite);
CloseHandle(context.StdErr.hRead); CloseHandle(context.StdErr.hWrite);
return r;
}
// パイプから読んでコールバックに渡す
BOOL ReadFromPipe(RedirStdIOContext* pContext, HANDLE hPipe,
fnReadStdOutErr pfnReadStdOutErr)
{
DWORD dwBytesAvail = 0, dwBytesRead = 0;
// パイプに何か来てる?
if(PeekNamedPipe(hPipe, NULL, 0, NULL, &dwBytesAvail, NULL))
{
if(!dwBytesAvail) return TRUE; // パイプに何もなし:処理は成功
// 転送バッファサイズよりも大きいデータが届いた
if(dwBytesAvail > pContext->dwBufferSize)
dwBytesAvail = pContext->dwBufferSize;
if(ReadFile(hPipe, pContext->pbyBuffer, dwBytesAvail, &dwBytesRead, NULL))
{
if(pfnReadStdOutErr)
return pfnReadStdOutErr(pContext->pbyBuffer, dwBytesRead, &dwBytesRead,
pContext->lpUser);
return TRUE; // コールバックは設定されていないがバッファから吐き出す必要がある
}
}
return FALSE; // パイプを覗けない:エラー
}
// コールバックからもらってパイプに書く
BOOL WriteToPipe(RedirStdIOContext* pContext, HANDLE hPipe,
fnWriteStdIn pfnWriteStdIn)
{
DWORD dwBytesWritten = 0;
BOOL bCallbackResult = pfnWriteStdIn(pContext->pbyBuffer,
pContext->dwBufferSize, &dwBytesWritten,
pContext->lpUser);
if(!WriteFile(hPipe, pContext->pbyBuffer, dwBytesWritten, &dwBytesWritten,
NULL))
return FALSE;
return bCallbackResult;
}
// ポンプを回す
BOOL PumpPipe(RedirStdIOContext* pContext)
{
BOOL r = TRUE;
if(pContext->pfnWriteStdIn)
{
// stdin を与える
if(!WriteToPipe(pContext, pContext->hStdInWritePipeDup,
pContext->pfnWriteStdIn))
{
// コールバックから FALSE が返ってきた:もう与えるデータはない
CloseHandle(pContext->hStdInWritePipeDup);
pContext->pfnWriteStdIn = NULL;
}
}
// どちらかのコールバックがもうデータはいらんと言うまで
r = r && ReadFromPipe(pContext, pContext->StdOut.hRead,
pContext->pfnReadStdOut);
r = r && ReadFromPipe(pContext, pContext->StdErr.hRead,
pContext->pfnReadStdErr);
return r;
}
FILE* GetStdioStreamFromWin32Handle(HANDLE hWin32Handle, char* mode)
{
int hCrt = _open_osfhandle((long)hWin32Handle, _O_TEXT);
return _fdopen(hCrt, mode);
}
この関数を使ったサンプルアプリは次のとおり。コマンドライン引数で与えられた
プログラムを実行し、標準出力とエラー出力をそれぞれ redir.cpp
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include "RedirectStdIO.h"
// ファイルハンドルを押し込んでおく構造体。
struct STDFILES
{
FILE* fpStdOut;
FILE* fpStdErr;
};
// stdout をファイルに書き出す
BOOL CALLBACK PrintStdOut(LPVOID pBuffer, DWORD dwBufferSize,
DWORD* pdwBytesRead, LPVOID lpUser)
{
*pdwBytesRead = fwrite(pBuffer, 1, dwBufferSize,
((STDFILES*)lpUser)->fpStdOut);
return TRUE;
}
// stderr をファイルに書き出す
BOOL CALLBACK PrintStdErr(LPVOID pBuffer, DWORD dwBufferSize,
DWORD* pdwBytesRead, LPVOID lpUser)
{
*pdwBytesRead = fwrite(pBuffer, 1, dwBufferSize,
((STDFILES*)lpUser)->fpStdErr);
return TRUE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
DWORD dwDummy = 0;
STDFILES stdFiles;
// エラーチェックなし
stdFiles.fpStdOut = fopen("stdout.txt", "wb");
stdFiles.fpStdErr = fopen("stderr.txt", "wb");
RedirectStdIO(lpCmdLine, NULL, PrintStdOut, PrintStdErr, &dwDummy,
&stdFiles, 0);
fclose(stdFiles.fpStdOut);
fclose(stdFiles.fpStdErr);
return dwDummy;
}
応用スレッドクラス化とか? 改版履歴
|