1. Popenのインプリメント popenでも_popenでもないよ
C++ Builder CEで使えるPopenを作ります。コマンドラインなプログラムで使えるpopenないし_popenというパイプ関数はありますが、それらは標準の入出力をつなぐ(pipe)関数であり、例えばVCLでは使えません。これが使えると外部プログラムの実行結果が得られるので便利ですから作っておきましょう。古典的なwin32 apiを使うプログラムになります。具体的には、CreatePipeして、CreateProcessしてWaitForSingleObjectしてReadFileして結果を返せばOKです。
まずpopen.hからこれはプロトタイプのみのヘッダーです。
String Popen(const String command,const String arguments,DWORD& rc);
次にpopen.cppです。
#include <iostream>
#include <fstream>
#include <string>
#include <memory>
#include <array>
#include <Windows.h>
String Popen(const String command, const String arguments,DWORD& rc)
{
SECURITY_ATTRIBUTES sa;
HANDLE read, write;
std::string rtn;
const String cmd_str = command + " " + arguments + "\0";
std::shared_ptr<char> cmd(new char[cmd_str.Length() + 1], std::default_delete<char[]>());
std::copy(cmd_str.begin(), cmd_str.end(), cmd.get());
cmd.get()[cmd_str.Length()] = '\0';
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
if (!CreatePipe(&read, &write, &sa, 0))
{
std::cerr << "CreatePipe is Error!" << std::endl;
return String("");
}
STARTUPINFOA si = {};
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si.hStdOutput = write;
si.hStdError = write;
if (si.hStdOutput == INVALID_HANDLE_VALUE || si.hStdError == INVALID_HANDLE_VALUE)
{
std::cerr << "GetStdHandle is Error!" << std::endl;
return String("");
}
PROCESS_INFORMATION pinfo = {};
if (!CreateProcessA(NULL, cmd.get(), NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pinfo))
{
std::cerr << "CreateProcess is Error!" << std::endl;
return String("");
}
HANDLE child = pinfo.hProcess;
if (!CloseHandle(pinfo.hThread))
{
std::cerr << "CloseHandle(hThread) is Error!" << std::endl;
return String("");
}
DWORD r = WaitForSingleObject(child, INFINITE);
switch (r)
{
case WAIT_FAILED:
std::cerr << "WaitResult:WAIT_FAILED" << std::endl;
return String("");
case WAIT_ABANDONED:
std::cerr << "WaitResult:WAIT_ABANDONED" << std::endl;
return String("");
case WAIT_OBJECT_0:
GetExitCodeProcess(child,&rc);
break;
case WAIT_TIMEOUT:
std::cerr << "WaitResult:WAIT_TIMEOUT" << std::endl;
return String("");
default:
std::cerr << "WaitResult:" << r << std::endl;
return String("");
}
if (!CloseHandle(write))
{
std::cerr << "CloseHandle(write) is Error!" << std::endl;
return String("");
}
write = NULL;
std::array<unsigned char, 1024> buf = { 0 };
DWORD rlen = 0;
while (ReadFile(read, buf.data(), buf.size(), &rlen, NULL))
{
std::copy(buf.begin(), buf.begin() + rlen, std::back_inserter(rtn));
}
if (!CloseHandle(read))
{
std::cerr << "CloseHandle(read) is Error!" << std::endl;
return String("");
}
read = NULL;
return rtn.c_str();
}
これはどこかで見つけたコードなんですが、出典が不明です。ご存じの方がいましたら、是非コメントお願いします。acknowledgementしたいと思います。std::な世界はなるべく関数内に押し込めて、外からはString=UnicodeStringで駆動できるようにしました。返り値もStringです。
使い方ですが、VCLプログラムのフォームにただTMemo等を下記のように置いて、
Unit1.cppは、
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "popen.h"
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
String command = "ping";
String args = "www.google.com";
DWORD rc;
String rtn;
rtn = Popen(command, args,rc);
Memo1->Lines->Add(rtn);
}
//---------------------------------------------------------------------------
等としておいて、
プロジェクトにはpopen.hとpopen.cppを加えてbuildして実行すると、
が得られます。実行しようとするcommand(外部プログラム)はpathが通ったところにあるか、ちゃんとfull pathで表現しないと起動できないです。この関数を元にしてIDE内部からGit command群を実行するプログラム群を作れますが、それはおいおい紹介します。
コメント
[…] 他に前記事C++ Builder CE 外部プログラムの利用で紹介したpopen.hとpopen.cppをプロジェクトに追加しておきます。結果のプロジェクトの様子は、 […]
[…] 以前の記事C++ Builder CE 外部プログラムの利用で掲載したpopen.cppですが、プロジェクトの設定によってはコンパイルエラーが出ることがあります。要は、 […]