携帯版
5090 (isweb) + 79520 (Vector) + Counter by XREA.COM


メニュー
アンテナ
Autch.net > 小ネタ > GUI アプリからコンソールアプリを実行するには Last-Modified: 2008-09-10 16:40:05 (JST)

[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);
}

この関数を使ったサンプルアプリは次のとおり。コマンドライン引数で与えられた プログラムを実行し、標準出力とエラー出力をそれぞれ stdout.txtstderr.txt に保存する。redir.exe cmd.exe /? のようにして実行する。

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;
}

応用

スレッドクラス化とか?

改版履歴

2005/10/10
  • 初版。

Autch.net > 小ネタ > GUI アプリからコンソールアプリを実行するには Last-Modified: 2008-09-10 16:40:05 (JST)
Valid XHTML 1.0! Valid CSS! Made with Cascading Style Sheets Powered by PHP Powered by Smarty
転載・引用・リンク・アンリンク自由。一切のコンテンツは無保証。
Copyright © 2000 - 2008, Autch.net. "gray_forest" theme designed by OCEAN-NET.