삽질하는플머

php-xmlrpc 에서 시간함수 문제

탐구생활/WEB 관련

상황은 이렇다. 


델파이로 만든 XMLRPC 클라이언트에서 "시간값"을 인자로 PHP로 만든 XMLRPC 서버의 함수를 실행하고

이 값을 MySQL의 타임스탬프 필드에 저장하자. 


xml-rpc 의 시간표시는 iso 8601 에 따라 다음과 같이 표시된다.

  • 2011-08-24T00:00:00

xmlrpc.inc 에는 이 형식과 유닉스 타임스탬프 사이의 변환을 위해 iso8601_encode, iso8601_decode 함수가 정의되어있다. 
이렇게 변환한 유닉스 타임스탬프값을 MySQL에 집어넣기 위해 MySQL 내장함수인 FROM_UNIXTIME() 을 사용해 봤는데
클라에서 전송한 시간보다 +9 시간 더 나오는 문제가 발생. iso8681_decode의 두번째 인자에 1을 주어 UTC로 풀어도 마찬가지.

RPC 테스트중인 윈도의 PHP는 현재시간이 UTC로 설정되어있다. 그런데 시스템시간은 한국. 그래서 생기는 문제인 듯. 
실제로 다음과 같은 코드로 타임존을 설정하면 제대로 처리된다.


date_default_timezone_set("Asia/Seoul");


NAS, 또는 우분투의 PHP에서 echo date_default_timezone_get(); 을 찍어보면 Asia/Seoul 이 잘 출력되는 것을 보아 윈도용 WAMP의 PHP에서 시스템의 설정을 읽지 못하는 문제로 보인다. 그렇다고 코드에 저걸 무조건 박아넣기도 골룸.


date로 문자열 꾸미기도 시도해 봄. 


1 $tt = iso8601_decode($ExpireDate);
2 $ts = date("Y-m-d G:i:s", $tt);

하지만 iso8601_decode 내부에서 gmmkttime, 또는 mktime 을 실행시키면서 타임존 문제가 생김. 


뭐가 어쨌건 시스템의 날짜 설정과 무관하게 델파이에서 전송한 시간값을 MySQL에 입력하기만 하면 되므로...
iso8601_decode 의 앞부분을 흉내내 이렇게 만들어 봄. 

1 function iso8601_time_to_mysql($idate) {
2     if (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs)) {
3         return sprintf("%s-%s-%s %s:%s:%s", $regs[1], $regs[2], $regs[3], $regs[4], $regs[5], $regs[6]);
4     } else
5         return NULL;
6 }    


요는, php의 시간변환 함수를 사용하지 말자는 것. 
비슷한 요령으로 역변환도 만들어 둠. 

1 function mysql_time_to_iso8601($idate) {
2     if (preg_match('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs)) {
3         return sprintf("%s%s%sT%s:%s:%s", $regs[1], $regs[2], $regs[3], $regs[4], $regs[5], $regs[6]);
4     } else
5         return NULL;
6 }


이제 델파이에서 의도한 시간값이 PHP를 거쳐 MySQL로 제대로 꽂힌다. .



여담으로, date('Z') 로 얻어낸 값에는 현재 설정된 타임존과 UTC 사이의 분단위 시간차이가 들어있다.
GetTimeZoneInformation() 으로 얻어낸 TimeZhoeBias 값과 동일. 그냥 까먹을까봐 적어둠. 




안드로이드 + 프리파스칼 테스트

여가생활/안드로이드

프리파스칼은 라자루스라는 걸출한 IDE를 가지고 있지만, 뭔가 깨작일때는 그래도 터보델파이가 제일 편하다. 


델파이로 간단한 OpenGL ES 런처를 만들고, 안드로이드에서 사용할 JNI 라이브러리를 윈도용 DLL로 빌드해 이 런처로 불러 확인하는 방법으로 개발과 디버깅 모두에 터보델파이를 사용해 보았다. iOS와 달리 안드로이드는 APK에 포함시킨 "데이터파일"이 디바이스에 설치되지 않고 묶인 상태 그대로 남아있기 때문에 이 부분을 윈도에서 그럴듯하게 흉내내주려면 고민이 조금 필요하네... 


암튼 간만에 도서관에 짱박혀 삽질한 결과물... 




DLL 소스를 NDK + FPC 로 빌드한 뒤 폰에 올려본 모습. 






빌드한 APK는 여기.


MindBall.apk



화면을 클릭하면 아래 흐르는 판넬로 공(?)이 떨어진다.

그냥 떨어지면 재미 없으니... 화면 왼쪽을 클릭하면 빨간색 판넬로, 오른쪽은 파란색 판넬로, 가운데는 녹색 판넬로... 


점심내기할 때 써먹으면 좋다. 
수년전 마누라와 만원내기했던 추억이 새록새록...

델파이로 깨작여보는 OpenGL ES 어플

여가생활/안드로이드

후배랑 술먹다가 한때 잘나가던 안드로이드폰 "HTC HTC_X515E" 하나를 갈취. 

취미삼아 FPC로 OpenGL을 돌려보며 즐거워하는 중이다. 


하지만, 코드 조금 고칠때마다 기계나 에뮬로 보내 확인하는건 별로 맘에 안듬.
게다가 맥에 만들어둔 FPC환경을 써먹느라 안드 SDK와 NDK도 맥에 깔았더니 더더욱 귀찮...

원래 걸으면 눕고싶고 누우면 자버리는게 프로그래머란 족속들 아닌가. 

파스칼 코드는 윈도에서 델파이로 깨작거리는게 아무래도 손에 익고 편하다. 


imtec, malideveloper 등에서 제공하는 OpenGL ES 에뮬레이터를 이용하면 

윈도환경에서도 OpenGL ES를 사용할 수 있다. 

아래 사이트로 가서 윈도용 OpenGL ES 2.0 / 1.1 에뮬레이터를 다운받자. 


http://malideveloper.arm.com/develop-for-mali/tools/opengl-es-2-0-emulator/


"C:\Program Files (x86)\ARM\Mali Developer Tools\OpenGL ES Emulator v1.3.0\bin" 폴더로 가서 DLL 파일들을 복사해둔다. 

델파이/FPC 용으로 번역해본 헤더와 초간단 예제는 여기에. 
(OpenGL ES 2.0 프로그래밍 가이드 챕터 1,2의 예제를 포팅)

GLES_WORK.7z



압축을 풀고 위에서 복사한 DLL 파일들을 BIN 폴더에 붙여준다. 

(OpenGL ES 에뮬레이터가 설치되었다면 DLL에 경로가 잡혀있으므로 굳이 복사할 필요는 없지만...)

인코딩에 UTF-8을 사용하므로 델파이 7 이하에서 쓰려면 ansi모드로 변환이 필요하다. 귀찮더라도 알아서 할 것. 

(내가 불친절한게 아님... 네이버의 nForge가 소스코드의 인코딩을 UTF-8만 지원... )


gl1ext, gl2ext 는 배포하지 않는다. 예전에 2.0 헤더를 바꾸면서 애플의 확장도 끼워넣었는데... 
애플과 관련된건 아무래도 뭔가 위반하는것 같은 찝찝함이 있어서... 라이센스 확인될 때 까지 당분간 보류... 

델파이 새 버전. RadStudio XE2 발표회 후기

탐구생활/Delphi
2011년 8월 12일, 델파이 새버전 RadStudio XE2 의 발표회가 있었다. 
소문만 무성하던 파이어몽키의 모습이 궁금하기도 했고, 페북에 링크된 개발자님들도 많이들 참가하신다고 해서 월~목 까지 폭풍야근, 금요일 업무를 제끼고 간만에 코엑스로 나들이를 했다. 조금 늦은 후기지만, 기억이 더 엷어지기 전에 그 날의 느낌들을 끄적여두자. 


행보관도 찿기 힘든 구석자리... 파릇파릇 린님, 푹 익은 현호님과 함께 짱박힘. 



발표회는 오전 10시부터 오후 5시까지 진행되었고 각 섹션은 다음과 같다. 

첫번째 시간은 XE2 에 대한 소개. 
두번째, 세번째 시간은 기대했던 파이어몽키에 대한 내용. 
네번째 시간은 64비트 환경에서의 프로그래밍에 대한 소개.
다섯번째 시간은 라이브바인딩이라는 새로운 데이터베이스 접근방법에 대한 소개.
여섯번째 시간은 데이터스냅에 대한 이야기.
일곱번째 시간은 XE2에서 제공하는 모바일솔루션 짚어보기. 


발표자는 엠베카데로 아태지역 수석 전도사 고든-리. 전도사라고 하니까 왠지 종교집단 느낌이... 
암튼 이 분 혼자서 네번째 섹션 하나 빼고 하루종일 발표와 데모를 진행하는 괴물같은 체력을 내뿜었다. 


뒷풀이 장소에서. 맨 왼쪽이 데브기어 대표님, 그 옆이 강사였던 강철체력 고든 리 님.  



사실 내가 사용해 본 가장 최신의, 그리고 가장 애용하는 버전의 델파이는 터보델파이(2006) 이다.
게임이 밥벌이인데다 요새는 PHP 코드를 더 많이 만지고 있고, 한때 이슈였던 유니코드 역시 델파이5 가 현역이던 2000년 초반에 미친듯 삽질을 했었기에, 적어도 내 입장에서는 업그레이드에 대한 별다른 이유를 찾지 못하고 있었다. 때문에 발표회에 참가하기 전 까지는 "가봐야 뭐 별 거 있겠어~" 정도가 솔직한 심정이었는데... "이런 XXX!! 어썸!!! 생각보다 괜찮네!!!!"

이 날 사용되었던 XE2는 베타 8버전으로서 시연중 몇몇 상황, 특히 파이어몽키 기반의 어플들에서 오류가 발생하기도 하였다. 
하지만 충분히 매력적인 물건이고 "지금까지 잊고 있던 업그레이드에 욕망을 한꺼번에 불러일으키는 물건이다."
물론 그 이유는 파이어몽키.  

잠깐 옆으로 새서, 아이폰 앱을 취미삼아 만지작거리면서 (물론 파스칼로) 가장 신기했던 점은 OpenGL로 뿌려지는 화면 위에 라벨이나 에디트같은 다른 코코아 컨트롤을 올려도 그들 사이에 Z-Order 나 각각의 투명도가 그대로 유지된다는 것이었다. 윈도에서 3D 가속을 받는 화면위에 윈도 컨트롤들을 올려본 개발자라면 이 말에 동의할텐데... 핸들이 없는 라벨등의 경우는 말할것도 없고 핸들을 가진 에디트같은 컨트롤들도 창모드 말고 풀스크린에서는 아예 표시도 되지 않는다. 게다가 배경은 칙칙한 윈도 기본색이 되고. 이 때문에 웹브라우저 컨트롤을 게임화면에 임베딩하기 위해 눈물을 머금고 성능을 포기하면서 창모드를 고수하는 경우도 많이 있었다. 웹킷을 3D 환경에 랜더링하는 Awesomium 같은 제품이 (예전엔 오픈소스였는데...) 관심을 끄는 것도 이런 열악한 환경 때문. 

너무 멀리갔다. 아무튼 이렇게 iOS에서 OpenGL화면과 UIKit의 컨트롤들이 자연스럽게 어우러지는 것은 이미 iOS의 화면표시 자체가 GPU의 가속을 받고 있고 UIKit도 그 기반에서 만들어졌기 때문인데, 이 부럽기 그지없는 환경을 드디어 우리 윈도 프로그래머들도 누리게 되었다. 파이어몽키는 버튼이나 에디트같은 UI 요소들이 OS가 제공하는 리소스에 의존하지 않고, 화면에 그려질 때는 GPU 가속을 받도록 완전히 새로 만들어진 UI 프레임워크이기 때문이다. 


저 컨트롤들은 핸들이 없어. 윈도프로시저도 없어. 스파이로 안잡혀.




반투명에 회전도 자유로워~


게다가 놀라운것은 이 프레임워크를 멀티플랫폼의 전초기지로 사용했다는 점인데...

파스칼을 사용하며 멀티플랫폼을 지원하는 라자루스의 경우 LCL(에반게리온 떠올리는 당신은 덕후!)이라는 레이어를 두고 여기서 Windows, QT, GTK 등 각 UI세트에 해당하는 위젯셋을 구현하는 방식인데, 때문에 각 플랫폼에서 공통적으로 지원하는 기능 이상을 만들기 쉽지 않다. 아무리 QT가 발전해 3D 신세경을 보여줘도 윈32가 이를 지원하지 않는 이상 LCL에서 이런 멋진 결과를 공통적으로 구현하기는 어렵다는 이야기.

엠베카데로가 KSDev의 VGScene를 인수하면서 만들어낸 해법은 조금 더 스케일이 컸다. 아예 UI 플랫폼을 다시 만들고 이것을 다른 OS에서도 동작시키는 것. 사실 각 OS별로 출력은 그렇다고 해도 입력할 때 글자조합이나 커서이동 처리가 모두 제각각이라 꿈꾸기 쉽지 않은 길일텐데... 아무튼 그 길로 갔다. 윈도 시스템과 찰떡처럼 결합된 VCL은 그냥 윈도 전용으로 남겨두고 멀티플랫폼은 아예 새로 만든 파이어몽키를 기반으로 함으로서 기존 델파이 개발자는 자신에게 익숙한 환경에서 만든 제품을 다른 OS에서도 동일한 UX로 제공하는 것이 가능해졌다. 

물론 첫 버전인만큼 기대만큼의 완성도에는 미치지 않는다. 윈도에서 IME 조합문자 표시는 베타10에 와서야 구현되었고 그나마 경계부분 커서 처리나 일본 IME에서의 조합중 커서이동 미구현, 생뚱맞은 후보창 위치 등 아직은 오류투성이이다. 거기에 아랍어, 태국어등 출력조합 문자열에서의 커서 처리도 VGScene 시절의 문제가 그대로 남아있다. 하지만 생각해보라. 이 문제들이 영원히 해결되지 않을까? 허접 프로그래머인 나조차도 윈도 3D 환경에서 다국어처리에 몇 달 안걸렸는데... (그것도 업무 외 시간에 아들놈 피해 도서관과 게임방을 전전하면서...)

출력은 이미 쓸만한 수준이고 입력마저 깔끔해진다면... 게다가 이 UX가 모든 OS에서 보장된다면... 어떤 세상이 열릴까... 
파이어몽키에 대한 PT 첫머리에 "파이어몽키는 게임엔진이 아니다" 라고 했지만, 이 물건이야말로 게임 UI 엔진에 목마른 많은 게임 개발자들에게 복음이 될 수 있다. XE2 프로버전의 경우 요즘 각광받는 스케일폼의 1/10 정도의 가격이 될 것이며 IDE에서 직접 컨트롤을 배치하고 무엇보다 액션스크립트가 아닌 C++, 또는 파스칼로 구동코드를 만들 수 있기 때문이다. 어디 게임 뿐일까. 플래시를 능가하는 RIA에 빠른 실행속도. 지하철 천정에 매달려 열차 도착을 알리는 윈도 XP들이 자유 운영체제인 리눅스로 바뀔 날도 멀지 않은 듯 하다. 


일단 내 감상문은 여기까지. 
XE2의 다른 기능들에 대해서는 솔직히 남에게 떠들만큼 알지도 못하고 쓸 일도 별로 없으니 출시 이후 쏟아질 리뷰들을 기대하자.
그나저나... 파이어몽키같은 물건이 일반화되면... 메시지 갈구리질이나 문자출력 API 걷어내는 기법은 박물관에나 가야겠군~~ 
 

유니코드 커맨드 라인 실행하고 결과값 받아오기

탐구생활/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() 함수 대신에 사용하면 됨.