유니코드 커맨드 라인 실행하고 결과값 받아오기
탐구생활/Delphi
소위 이야기하는 도스커맨드 실행하고 결과 받아오기. 김성동님의 코드를 살짝 바꿔 사용.
커맨드라인 파라미터를 유니코드로 넘길 수 있게 하고, 중간 중간에 이루어지는 출력을 받아서 처리할 수 있는 콜백버전을 새로 만듦.
다국어 INI를 읽어 UTF-8 로 뿌려주는 간단한 유틸리티를 만들었는데, 이 유틸리티를 호출하려면 INI 파일의 이름을 유니코드 파라미터로 넘겨야 하기 때문에 필요해짐.
// 유니코드 커맨드라인 실행하고 출력값 얻어오자. .
// 주의! 콘솔로 출력되는 문자열은 안시문자열이다.
// GetConsoleOutputCP(), SetConsoleOutputCP() API 참조.
type
TRunCmdCallback = procedure (const aRetStr: AnsiString; aParam: Pointer);
// 실행결과 문자열을 RunCmdCallback으로 조금씩 받아 조립할 때 사용.
// 콜백의 호출 주기는 상수 BUFFER_SIZE 의 값과 WaitForSingleObject() 의 두번째 인자인 시간값으로 결정된다.
function RunWideCmd(Command: WideString; RunCmdCallback: TRunCmdCallback; Param: Pointer = nil): DWORD; overload;
const
BUFFER_SIZE = 512; // 버퍼 크기가 작으면 RunCmdCallback이 좀 더 자주 일어남
var
hReadPipe : THandle;
hWritePipe : THandle;
SI : TStartUpInfo;
PI : TProcessInformation;
SA : TSecurityAttributes;
SD : TSecurityDescriptor;
BytesRead : DWORD;
Avail, wrResult : DWORD;
RetStr: AnsiString;
begin
{ Dos Application }
if Win32Platform = VER_PLATFORM_WIN32_NT then
begin
InitializeSecurityDescriptor(@SD, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@SD, True, nil, False);
SA.nLength := SizeOf(SA);
SA.lpSecurityDescriptor := @SD;
SA.bInheritHandle := True;
CreatePipe(hReadPipe, hWritePipe, @SA, BUFFER_SIZE);
end else
CreatePipe(hReadPipe, hWritePipe, nil, BUFFER_SIZE);
try
{ STARTUPINFO를 설정한다. }
FillChar(SI, SizeOf(SI), 0);
SI.cb := SizeOf(TStartUpInfo);
SI.wShowWindow := SW_HIDE;
SI.dwFlags := STARTF_USESHOWWINDOW;
{ STARTF_USESTDHANDLES 를 빼면 PIPE로 입출력이 Redirect 되지 않는다. }
SI.dwFlags := SI.dwFlags or STARTF_USESTDHANDLES;
SI.hStdOutput := hWritePipe;
SI.hStdError := hWritePipe;
{ Process를 생성한다. }
if CreateProcessW(nil, PWChar(Command), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, nil, SI, PI) then
begin
Result := 0;
while Result = 0 do
begin
// 대기시간이 길면 반응이 느리다. 짧으면 콜백이 그만큼 자주 일어남. 적당히 조절해 사용.
wrResult := WaitForSingleObject(PI.hProcess, 5);
{ Pipe에 출력된 내용이 있는 지 조사한다. }
if PeekNamedPipe(hReadPipe, nil, 0, nil, @Avail, nil) then
begin
if Avail > 0 then
begin
{ Redirect된 화면 내용을 얻어낸다. }
SetLength(RetStr, Integer(Avail));
ReadFile(hReadPipe, PAnsiChar(RetStr)[0], Avail, BytesRead, nil);
if Assigned(RunCmdCallback) then RunCmdCallback(RetStr, Param);
end;
end;
{ WAIT_TIMEOUT이 아니면 루프를 마친다. }
if wrResult <> WAIT_TIMEOUT then Result := 1;
end;
{ 실행이 끝났다.. }
GetExitCodeProcess(PI.hProcess, Result);
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);
end;
finally
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
end;
end;
// 실행결과 문자열 전체를 한꺼번에 리턴받을 때 사용.
function RunWideCmd(Command : WideString; var RetStr: AnsiString): DWORD; overload;
const
BUFFER_SIZE = 512;
var
hReadPipe : THandle;
hWritePipe : THandle;
SI : TStartUpInfo;
PI : TProcessInformation;
SA : TSecurityAttributes;
SD : TSecurityDescriptor;
BytesRead : DWORD;
Avail, wrResult : DWORD;
LastSize: Integer;
begin
{ Dos Application }
if Win32Platform = VER_PLATFORM_WIN32_NT then
begin
InitializeSecurityDescriptor(@SD, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@SD, True, nil, False);
SA.nLength := SizeOf(SA);
SA.lpSecurityDescriptor := @SD;
SA.bInheritHandle := True;
CreatePipe(hReadPipe, hWritePipe, @SA, BUFFER_SIZE);
end else
CreatePipe(hReadPipe, hWritePipe, nil, BUFFER_SIZE);
try
{ STARTUPINFO를 설정한다. }
FillChar(SI, SizeOf(SI), 0);
SI.cb := SizeOf(TStartUpInfo);
SI.wShowWindow := SW_HIDE;
SI.dwFlags := STARTF_USESHOWWINDOW;
{ STARTF_USESTDHANDLES 를 빼면 PIPE로 입출력이 Redirect 되지 않는다. }
SI.dwFlags := SI.dwFlags or STARTF_USESTDHANDLES;
SI.hStdOutput := hWritePipe;
SI.hStdError := hWritePipe;
{ Process를 생성한다. }
if CreateProcessW(nil, PWideChar(Command), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, nil, SI, PI) then
begin
Result := 0;
while Result = 0 do
begin
wrResult := WaitForSingleObject(PI.hProcess, 5);
{ Pipe에 출력된 내용이 있는 지 조사한다. }
if PeekNamedPipe(hReadPipe, nil, 0, nil, @Avail, nil) then
begin
if Avail > 0 then
begin
{ Redirect된 화면 내용을 얻어낸다. }
LastSize := Length(RetStr);
SetLength(RetStr, LastSize + Integer(Avail));
ReadFile(hReadPipe, PChar(RetStr)[LastSize], Avail, BytesRead, nil);
end;
end;
{ WAIT_TIMEOUT이 아니면 루프를 마친다. }
if wrResult <> WAIT_TIMEOUT then Result := 1;
end;
{ 실행이 끝났다.. }
GetExitCodeProcess(PI.hProcess, Result);
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);
end;
finally
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
end;
end;
두번째 함수는 사용법 간단. 대충 다음과 같이.
procedure TForm1.Button5Click(Sender: TObject);
var
RetStr: AnsiString;
begin
RunWideCmd('cmd /c dir c:\', RetStr);
ListBox1.Items.Text := RetStr;
ListBox1.ItemIndex := ListBox1.Count -1;
end;
첫번째 함수는 실제 콘솔창처럼 변화하는 상태를 표현할 때 써먹기 위해 만들었는데 사용은 약간 귀찮다.
(콜백의 호출 주기는 RunWideCmd 함수 내의 상수 BUFFER_SIZE 의 값과 WaitForSingleObject() 로 전달되는 시간값으로 결정된다.)
var
ugRetStr: AnsiString;
procedure RunCmdCallback (const aRetStr: AnsiString; aParam: Pointer);
begin
ugRetStr := ugRetStr + aRetStr;
TListBox(aParam).Items.BeginUpdate;
TListBox(aParam).Items.Text := ugRetStr;
TListBox(aParam).ItemIndex := TListBox(aParam).Count -1;
TListBox(aParam).Items.EndUpdate;
Application.ProcessMessages;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
ugRetStr := '';
RunWideCmd('cmd /c dir c:\', RunCmdCallback, ListBox1);
end;
덤으로... 이렇게 호출당한 입장에서 유니코드 파라미터를 참고하는 방법은 다음과 같다.
function GetWideParamStr(P: PWideChar; var Param: WideString): PWideChar;
var
i, Len: Integer;
Start, S, Q: PWideChar;
begin
while True do
begin
while (P[0] <> #0) and (P[0] <= ' ') do
P := CharNextW(P);
if (P[0] = '"') and (P[1] = '"') then Inc(P, 2) else Break;
end;
Len := 0;
Start := P;
while P[0] > ' ' do
begin
if P[0] = '"' then
begin
P := CharNextW(P);
while (P[0] <> #0) and (P[0] <> '"') do
begin
Q := CharNextW(P);
Inc(Len, Q - P);
P := Q;
end;
if P[0] <> #0 then
P := CharNextW(P);
end else
begin
Q := CharNextW(P);
Inc(Len, Q - P);
P := Q;
end;
end;
SetLength(Param, Len);
P := Start;
S := Pointer(Param);
i := 0;
while P[0] > ' ' do
begin
if P[0] = '"' then
begin
P := CharNextW(P);
while (P[0] <> #0) and (P[0] <> '"') do
begin
Q := CharNextW(P);
while P < Q do
begin
S[i] := P^;
Inc(P);
Inc(i);
end;
end;
if P[0] <> #0 then P := CharNextW(P);
end else
begin
Q := CharNextW(P);
while P < Q do
begin
S[i] := P^;
Inc(P);
Inc(i);
end;
end;
end;
Result := P;
end;
function WideParamStr(Index: Integer): WideString;
var
P: PWideChar;
begin
Result := '';
if Index = 0 then
begin
SetLength(Result, MAX_PATH);
SetLength(Result, GetModuleFileNameW(0, PWideChar(Result), MAX_PATH));
end else
begin
P := GetCommandLineW;
while True do
begin
P := GetWideParamStr(P, Result);
if (Index = 0) or (Result = '') then Break;
Dec(Index);
end;
end;
end;
위에 정의된 WideParamStr() 함수를 ParamStr() 함수 대신에 사용하면 됨.