Delphi Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
델파이 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
FreePascal/Lazarus
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
델마당
볼랜드포럼 광고 모집

델파이 강좌/문서
Delphi Programming Tutorial&Documents
[137] 델파이의 유니코드 지원 Part III: 기존 코드의 수정
박지훈.임프 [cbuilder] 13384 읽음    2008-09-25 17:45
작성자 : 닉 하지스(Nick Hodges)

요약 : 이 아티클에서는 여러분의 코드가 델파이 2009에서 동작하기 위해 필요한 것들을 설명합니다.

이 시리즈의 파트 I 에서 알아봤던 대로, 델파이 2009는 기본적으로 UTF-16 기반의 스트링을 사용하기 때문에 기존의 코드에서 특정 코드 부분들은 수정이 필요할 수도 있습니다. 일반적으로 기존 코드의 거의 대부분은 델파이 2009에서 그대로 잘 동작할 것입니다. 이 아티클에서 곧 보게 되겠지만, 필요한 코드 수정의 대부분은 상당히 명확하고 약간은 난해합니다. 하지만, 일부 특정 코드들은 재검토하고 UnicodeString과 제대로 동작하도록 보장하기 위해 수정을 해야 할 수 있습니다.

예를 들어, 스트링을 조작하거나 스트링 포인터 연산을 하는 코드는 유니코드 호환성을 위해 검사해야 합니다. 더 구체적으로 말하자면, 다음과 같은 코드들은 반드시 재검토하고 코드에 남아있지 않도록 해야 합니다.

  • SizeOf(Char)를 1로 간주한 코드
  • 스트링의 Length가 스트링의 바이트 크기와 같다고 간주한 코드
  • 영구 저장장치에 스트링을 쓰거나 읽는 코드, 스트링을 데이터 버퍼로 이용하는 코드

    영구 저장장치에 쓰거나 읽는 코드는 정확한 바이트 수만큼 읽거나 쓰여지는지 확인해야 합니다. 1바이트는 더 이상 1 문자를 의미하지 않기 때문입니다.

    일반적으로, 코드 작업이 필요한 경우 수정 작업은 단순하고 최소한의 노력으로 할 수 있습니다.


    수정이 필요 없는 경우들



    이 섹션에서는 이전처럼 계속 잘 동작할 것이고 새 UnicodeString과 제대로 동작하기 위해 어떤 수정도 필요하지 않은 코드들을 살펴봅니다. 델파이 2009에서 VCL과 RTL 전체가 개발자들이 예상하는 대로 동작하도록 수정되었으며, 대단히 드문 예외를 제외하면 모두 마찬가지입니다. 예를 들어, TStringList는 이제 완벽하게 유니코드를 지원하면서도 기존의 모든 TStringList 코드가 이전처럼 잘 동작합니다. 하지만, TStringList는 특히 유니코드와 잘 동작하도록 개선되었으므로 원한다면 새로운 기능들을 활용할 수 있습니다. (물론 반드시 필요한 것은 아닙니다)

    일반적인 스트링 타입 사용



    일반적으로, 스트링 타입을 사용하는 코드는 이전과 동일하게 동작할 것입니다. 아래에서 설명할 예외를 제외하면, 스트링 변수를 AnsiString 타입으로 다시 선언할 필요는 없습니다. 스트링 선언은 저장소 버퍼나 스트링을 데이터 버퍼로 사용하는 코드의 경우에만 AnsiString으로 수정되어야 합니다.

    런타임 라이브러리(RTL)



    런타임 라이브러리에서 추가된 기능들은 파트 II 에서 폭넓게 살펴봤습니다.

    파트 II에서는 RTL에 추가된 새로운 유닛 하나를 언급하지 않았었는데, 그것은 AnsiString.pas입니다. 이 유닛은 AnsiString을 사용하려 하거나 사용해야 할 코드에서 하위 호환성을 위해 존재합니다.

    런타임 라이브러리 코드는 예상하는 대로 동작하며 일반적으로 수정이 필요하지 않습니다. 수정이 필요한 부분들은 아래에서 설명합니다.

    VCL



    VCL 전체가 유니코드를 지원합니다. 모든 기존의 VCL 컴포넌트들은 다른 처리가 없더라도 그대로 이전과 동일하게 동작합니다. 여러분 코드에서 VCL을 사용하는 많은 부분들은 유니코드를 지원하면서 동시에 하위 호환성을 가집니다. 우리는 VCL이 유니코드에 대비하고 동시에 하위 호환성을 가지도록 많은 작업을 했습니다. 특정 스트링 조작을 하지 않는 보통의 VCL 코드들은 이전과 똑같이 동작할 것입니다.

    스트링 인덱스



    스트링 인덱스는 이전과 완전히 동일하게 동작하며, 스트링에 인덱스를 쓰는 코드는 수정할 필요가 없습니다.
    var
      S: string;
      C: Char;
    begin
      S := 'This is a string';
      C := S[1];  // C는 'T' 값을 가집니다. 물론 C는 WideChar입니다.
    end;
    


    스트링의 Length/Copy/Delete/SizeOf



    Copy는 수정 없이 이전처럼 동작합니다. Delete와 SysUtils 기반의 스트링 조작 함수들도 마찬가지입니다.

    Length(스트링) 함수 호출도 언제나처럼 넘겨준 스트링의 엘레먼트 개수를 리턴합니다.

    모든 스트링에 대한 SizeOf 함수 호출은 4를 리턴합니다. 모든 스트링 선언은 레퍼런스이며, 포인터의 사이즈가 4이기 때문입니다.

    모든 스트링에 대한 Length 호출은 스트링의 엘레먼트 개수를 리턴합니다.

    다음의 코드를 살펴봅시다.
    var
      S: string;
    begin
      S:= 'abcdefghijklmnopqrstuvwxyz가나';
      WriteLn('Length = ', Length(S));
      WriteLn('SizeOf = ', SizeOf(S));
      WriteLn('TotalBytes = ', Length(S) * SizeOf(S[1]));
      ReadLn;
    end.
    

    위 코드의 결과는 다음과 같습니다.
    Length = 28
    SizeOf = 4
    TotalBytes = 56
    


    PChar의 포인터 연산



    PChar의 포인터 연산은 이전과 같이 동작합니다. 컴파일러가 PChar의 크기를 알고 있으므로, 다음과 같은 코드는 예상대로 동작합니다.
    var
      p: PChar;
      MyString: string;
    begin
      ...
      p := @MyString[1];
      Inc(p);
      ...
    end;
    

    이 코드는 델파이의 이전 버전들과 똑같이 동작하지만, 물론 타입들은 다릅니다. PChar는 이제 PWideChar이고 MyString은 이제 UnicodeString입니다.

    ShortString



    ShortString은 기능과 선언 모두 변경 사항이 없으며, 이전과 같이 동작합니다.

    ShortString 선언은 지정한 개수의 AnsiChar 버퍼를 할당합니다. 다음 코드를 살펴봅시다.
    var
      S: string[32];
    begin
      S:= 'abcdefghijklmnopqrstuvwxyz가나';
      WriteLn('Length = ', Length(S));
      WriteLn('SizeOf = ', SizeOf(S));
      WriteLn('TotalBytes = ', Length(S) * SizeOf(S[1]));
      ReadLn;
    end.
    

    위 코드의 결과는 다음과 같습니다.
    Length = 30
    SizeOf = 33
    TotalBytes = 30
    


    전체 바이트 수가 30이라는 것을 보면 이 스트링 변수가 AnsiChar를 가지고 있다는 것을 알 수 있습니다.

    추가로, 다음의 코드를 살펴봅시다.
    type
    TMyRecord = record
      String1: string[20];
      String2: string[15];
    end;
    

    이 레코드는 AnsiChar 데이터들을 가진 두 개의 ANSI 스트링을 가진 레코드가 되어 이전과 똑같이 메모리에 배치됩니다. 여러분이 ShortString을 가진 레코드로 File of Rec을 사용하는 경우에도 위의 코드는 이전과 동일하게 동작하며, 그런 레코드로 파일을 읽고 쓰는 작업을 하는 코드는 수정할 필요 없이 이전과 같이 동작합니다. 하지만 Char는 이제 WideChar라는 것을 잊지 마십시오. 따라서 다음과 같이 이 레코드를 파일로부터 불러오는 코드가 있고 그래서 다음과 같이 호출했었다면,
    var
      MyRec: TMyRecord;
      SomeChar: Char;
    begin
      // 여기에 MyRec의 데이터를 파일로부터 불러오는 코드...
      SomeChar := MyRec.String1[3];
      ...
    end;
    

    String1[3]로부터 나오는 Char 타입의 SomeChar의 타입이 이전에는 실제로는 AnsiChar였지만 이제 WideChar가 된다는 것을 기억해야 합니다. 이 코드가 이전과 같이 동작하게 하려면, SomeChar의 선언을 다음과 같이 바꿔야 합니다.
    var
      MyRec: TMyRecord;
      SomeChar: AnsiChar; // shortstring의 인덱스 해당 값을 AnsiChar로 선언합니다
    begin
      // 여기에 MyRec의 데이터를 파일로부터 불러오는 코드...
      SomeChar := MyRec.String1[3];  
      ...
    end;
    



    재검토 대상인 경우들



    이 섹션에서는 유니코드 호환을 위해서는 재검토해야 할 여러 코드들을 설명합니다. Char는 이제 WideChar이기 때문에, 문자 배열이나 스트링에서 바이트 크기를 추정하는 것은 맞지 않을 수 있습니다. 다음은 새로운 UnicodeString 타입과 호환되기 위해서는 검토되어야 하는 여러 특정한 코드들입니다.

    SaveToFile/LoadFromFile



    SaveToFile 및 LoadFromFile 호출은 이전과 같이 읽고 쓰는 동작을 하므로 전반적으로는 잘 동작합니다. 하지만, 이 함수들을 사용할 때 유니코드 데이터를 다루려고 한다면 새로운 오버로드된 버전의 함수들을 사용할 것을 고려해보십시오.

    예를 들어, TStrings에는 이제 다음과 같은 오버로드된 함수들이 포함되어 있습니다.
    procedure SaveToFile(const FileName: string); overload; virtual;
    procedure SaveToFile(const FileName: string; Encoding: TEncoding); overload; virtual;
    

    위의 두 번째 메소드는 데이터가 어떻게 파일에 쓰여질 지를 결정하는 인코딩 파라미터를 가진 새로운 함수입니다. (TEncoding 타입에 대한 설명을 위해서는 파트 II를 읽어보십시오) 위의 첫번째 메소드를 호출하면 스트링 데이터는 이전과 똑같이 ANSI 데이터로 저장됩니다. 따라서, 여러분의 기존의 코드는 이전과 동일하게 동작합니다.

    하지만, 유니코드 스트링 데이터가 들어있다면, 두번째 오버로드 함수를 사용하여 특정 TEncoding 타입을 넘겨줘야 합니다. 그렇게 하지 않으면 스트링은 ANSI 데이터로 쓰여지게 되고, 데이터 손실이 일어날 수 있습니다.

    따라서, 여기서 가장 좋은 방법은 기존의 SaveToFile 및 LoadFromFile 호출들을 재검토하고 데이터를 어떻게 저장할 것인지를 지정하는 두번째 파라미터를 추가하는 것입니다. 물론 유니코드 스트링을 사용할 일이 없다고 생각한다면 그대로 놔둬도 상관없습니다.

    Chr 함수의 사용



    integer 값으로부터 Char 값을 만들어내는 기존의 코드는 아마도 Chr 함수를 사용했을 것입니다. Chr 함수의 사용 중에 특정한 방식으로 호출하면 다음과 같은 에러가 날 수 있습니다.
    [DCC Error] PasParser.pas(169): E2010 Incompatible types: 'AnsiChar' and 'Char'
    

    Chr 함수의 결과를 AnsiChar에 대입하는 코드라면, Chr 함수를 AnsiChar 캐스팅으로 바꿈으로써 이 에러를 간단히 없앨 수 있습니다.

    그러니까, 아래의 코드는,
    MyChar := chr(i);
    

    아래와 같이 변경할 수 있습니다.
    MyChar := AnsiChar(i);
    


    문자들의 set



    아마도 컴파일러가 가장 자주 문제를 발견하게 될 부분은 set 안의 문자들일 것입니다. 과거에는 문자가 한 바이트였기 때문에 문자를 set으로 다루는 것이 아무런 문제가 되지 않았습니다. 하지만 지금은 Char가 WideChar로 선언되었기 때문에 set으로 다룰 수가 없게 되었습니다. 따라서, 여러분이 다음과 같은 코드를 가지고 있고,
    procedure TDemoForm.Button1Click(Sender: TObject);
    var
      C: Char;
    begin
      C := Edit1.Text[1];
    
      if C in ['a'..'z', 'A'..'Z'] then
      begin
        Label1.Caption := 'It is there';
    end;
    end;
    

    이 코드를 컴파일하면, 아래와 같은 컴파일러 경고를 받게 됩니다.
    [DCC Warning] Unit1.pas(40): W1050 WideChar reduced to byte char in set expressions. Consider using 'CharInSet' function in 'SysUtils' unit.
    

    원한다면 이런 코드를 그대로 둘 수도 있습니다. 컴파일러는 여러분이 뭘 하려고 하는지 ‘알기’ 때문에, 정확한 코드를 만들어냅니다. 하지만, 이 경고를 없애려고 한다면 새로 추가된 함수인 CharInSet을 사용할 수 있습니다.
    if CharInSet(C, ['a'..'z', 'A'..'Z']) then
    begin
      Label1.Caption := 'It is there';
    end;
    

    CharInSet 함수는 Boolean 값을 리턴하며, 컴파일러 경고 없이 컴파일됩니다.

    스트링을 데이터 버퍼로 이용한 경우



    string을 데이터 버퍼로 이용하는 경우가 흔합니다. 이런 코드가 흔한 이유는 스트링 조작은 보통 상당히 쉽기 때문입니다. 하지만 이제 string은 UnicodeString이기 때문에 이런 기존의 코드들은 대부분 수정이 필요합니다.

    string을 데이터 버퍼로 사용하는 코드를 처리하는 방법에는 몇 가지가 있습니다. 첫번째 방법은 데이터 버퍼로 사용되던 변수를 단순히 string 대신 AnsiString으로 선언하는 것입니다. 코드에서 버퍼의 바이트들을 조작하기 위해 Char를 사용했다면 그 변수들을 AnsiChar로 선언하면 됩니다. 이 방법을 선택하게 되면 기존의 모든 코드는 이전처럼 동작할 수 있게 되지만, 스트링 버퍼를 액세스하는 모든 변수들을 명시적으로 ANSI 타입으로 선언했는지 조심해야 합니다.

    두번째 방법은 이런 상황을 처리하는 더 좋은 방법으로, 기존 코드를 수정하여 스트링 타입에서 바이트 배열, 혹은 TBytes로 바꾸는 것입니다. TBytes는 이런 목적을 위해 설계된 것으로, 이전의 string 타입을 사용하는 것과 비슷하게 동작합니다.

    버퍼에 대한 SizeOf 호출



    문자 배열에 SizeOf을 사용한 코드도 재검토할 필요가 있습니다. 다음의 코드를 살펴봅시다.
    procedure TDemoForm.Button1Click(Sender: TObject);
    var
      P: array[0..16] of Char;
    begin
      StrPCopy(P, 'This is a string');
      Memo1.Lines.Add('P의 길이는 ' + IntToStr(Length(P)) + '입니다');
      Memo1.Lines.Add('P의 크기는 ' + IntToStr(SizeOf(P)) + '입니다');
    end;
    

    이 코드는 Memo1에 다음과 같은 결과를 보여줄 것입니다.
    P의 길이는 17입니다
    P의 크기는 34입니다
    

    위의 코드에서, Length는 지정된 스트링의 문자 개수를 리턴하지만(null 종료 문자 포함), SizeOf는 배열에 사용된 총 바이트 수를 리턴하므로 이 경우에는 34가 됩니다. 즉, 1문자당 2바이트입니다. 델파이의 이전 버전들에서는 이 코드는 두 경우 모두 17을 리턴했었습니다.

    FillChar의 사용



    FillChar 호출은 스트링 혹은 문자와 함께 사용되었을 경우 재검토해야 합니다. 다음의 코드를 살펴봅시다.
    var
      Count: Integer;
      Buffer: array[0..255] of Char;
    begin
      // 기존 코드 - string = UnicodeString인 경우 잘못된 코드임
      Count := Length(Buffer);
      FillChar(Buffer, Count, 0);
      
      // 유니코드를 위해 수정 ? 아래 두 가지 모두 맞는 코드임
      Count := SizeOf(Buffer);                // <<-- 버퍼 크기를 바이트로 지정
      Count := Length(Buffer) * SizeOf(Char); // <<-- 버퍼 크기를 바이트로 지정
      FillChar(Buffer, Count, 0);
    end;
    

    Length는 문자 단위의 크기를 리턴하지만 FillChar는 Count가 바이트 단위일 것이라고 간주합니다. 이 경우에는 Length 대신 SizeOf가 사용되어야 합니다. (혹은 Length의 값에 Char의 크기를 곱해줘야 합니다)

    그리고, FillChar의 세번째 인자인 Char 타입의 기본 사이즈가 2가 되었으므로, FillChar는 이전처럼 1바이트의 값으로 채우는 것이 아니라 2바이트의 값으로 채운다는 점을 주의하십시오.

    다음 코드를 살펴봅시다.
    var
      Buf: array[0..32] of Char;
    begin
      FillChar(Buf, Length(Buf), #9);
    end;
    

    이 코드는 배열을 코드포인트 $09로 채우는 것이 아니라 $0909로 채우게 됩니다. 원하는 결과를 얻기 위해서는 다음과
    같이 수정해야 합니다.
    var
      Buf: array[0..32] of Char;
    begin
      ..
      StrPCopy(Buf, StringOfChar(#9, Length(Buf)));
      ..
    end;
    


    문자 상수



    다음의 코드는,
    if Edit1.Text[1] = #128 then
    

    대부분의 ANSI 코드 페이지에서 유로화 기호(€)로 인식되어 True 조건이 됩니다. 하지만, 델파이 2009에서는 False가 되는데, 그 이유는 ANSI 코드 페이지에서 #128 값이 유로화 기호이지만 유니코드에서는 제어 문자이기 때문입니다. 유니코드에서 유로화 기호는 #$20AC입니다.

    기존의 코드에서 #128~#255 문자들을 사용한 부분들은 델파이 2009에서는 실제 문자로 바꿔줘야 합니다.
    if Edit1.Text[1] = '€' then
    

    위 코드는 ANSI에서의 #128과 동일하게 동작하면서 동시에 델파이 2009에서도 제대로 동작합니다.

    Move 호출



    Move 호출에 스트링이나 문자 배열이 사용된 경우 재검토가 필요합니다. 다음 코드를 살펴봅시다.
    var
      Count: Integer;
      Buf1, Buf2: array[0..255] of Char;
    begin
      // 기존의 코드 - string = UnicodeString인 경우 잘못된 코드임
      Count := Length(Buf1);
      Move(Buf1, Buf2, Count);
      
      // 유니코드에서도 맞는 코드
      Count := SizeOf(Buf1);                // <<-- 버퍼 사이즈를 바이트로 지정
      Count := Length(Buf1) * SizeOf(Char); // <<-- 버퍼 사이즈를 바이트로 지정
      Move(Buf1, Buf2, Count);
    end;
    

    Length는 문자 단위의 길이를 리턴하지만, Move는 Count 파라미터가 바이트 단위일 거라고 간주합니다. 이런 경우에는 Length 대신 SizeOf를 사용해야 합니다. (혹은 Length의 값에 Char의 크기를 곱해줘야 합니다)

    TStream의 Read/ReadBuffer 메소드



    TStream.Read/ReadBuffer 호출에 스트링이나 문자 배열이 사용된 경우 재검토가 필요합니다. 다음 코드를 살펴봅시다.
    var
      S: string;
      L: Integer;
      Stream: TStream;
      Temp: AnsiString;
    begin
      // 기존 코드 - string = UnicodeString인 경우 잘못된 코드임
      Stream.Read(L, SizeOf(Integer));
      SetLength(S, L);
      Stream.Read(Pointer(S)^, L);
       
      // 유니코드에서도 맞는 코드
      Stream.Read(L, SizeOf(Integer));
      SetLength(S, L);
      Stream.Read(Pointer(S)^, L * SizeOf(Char));  // <<-- 버퍼 사이즈를 바이트로 지정
      
      // ANSI 데이터에서 맞는 코드
      Stream.Read(L, SizeOf(Integer));
      SetLength(Temp, L);              // <<-- 임시 AnsiString 사용
      Stream.Read(Pointer(Temp)^, L * SizeOf(AnsiChar));  // <<-- 버퍼 사이즈를 바이트로 지정
      S := Temp;                       // <<-- 스트링을 유니코드로 넓힘
    end;
    

    노트: 이 해결책은 읽혀지는 데이터의 포맷에 의존적입니다. 스트림의 텍스트를 정확히게 인코딩하기 위해서는 파트 II에서 설명했던 새로운 TEncoding 클래스를 참고하십시오.

    Write/WriteBuffer



    Read/ReadBuffer와 마찬가지로, TStream.Write/WriteBuffer 호출도 스트링이나 문자 배열이 사용된 경우 재검토가 필요합니다. 다음 코드를 살펴봅시다.
    var
      S: string;
      Stream: TStream;
      Temp: AnsiString;
    begin
      // 기존 코드 - string = UnicodeString인 경우 잘못된 코드임
      Stream.Write(Pointer(S)^, Length(S));
      
      // 유니코드에서도 맞는 코드
      Stream.Write(Pointer(S)^, Length(S) * SizeOf(Char)); // <<-- 버퍼 사이즈를 바이트로 지정
      
      // ANSI 데이터에서 맞는 코드
      Temp := S;          // <<-- Use temporary AnsiString
      Stream.Write(Pointer(Temp)^, Length(Temp) * SizeOf(AnsiChar));// <<-- 버퍼 사이즈를 바이트로 지정
    end;
    

    노트: 이 해결책은 읽혀지는 데이터의 포맷에 의존적입니다. 스트림의 텍스트를 정확히게 인코딩하기 위해서는 파트 II에서 설명했던 새로운 TEncoding 클래스를 참고하십시오.

    LeadBytes



    다음과 같은 코드는,
    if Str[I] in LeadBytes then
    

    IsLeadChar 함수로 바꾸십시오.
    if IsLeadChar(Str[I]) then
    


    TMemoryStream



    TMemoryStream이 텍스트 파일을 저장하는 데에 사용되는 경우, BOM (Byte Order Mark)을 파일의 시작 부분에 써넣는 것이 좋습니다. 아래 코드는 BOM을 파일에 써넣는 예제입니다.
    var
      BOM: TBytes;
    begin
      ...
      BOM := TEncoding.UTF8.GetPreamble;
      Write(BOM[0], Length(BOM));
    

    모든 파일 쓰기 코드는 유니코드 스트링을 UTF8로 변환 작업이 필요합니다.
    var
      Temp: Utf8String;
    begin
      ...
      Temp := Utf8Encode(Str); // <-- Str은 파일에 쓰여질 스트링입니다
      Write(Pointer(Temp)^, Length(Temp));
      //Write(Pointer(Str)^, Length(Str)); <-- 이 라인은 스트링을 파일에 쓰기 위한 기존의 코드입니다
    end;
    


    TStringStream



    TStringStream은 이번 버전에서 새로운 타입인 TByteStream에서 상속을 받습니다. TByteStream은 Bytes라는 속성을 가지고 있는데, 이 속성을 통해 TStringStream의 바이트들을 직접 액세스할 수 있습니다. TStringStream은 거의 이전과 동일하게 동작하지만, 가지고 있는 스트링은 유니코드 기반 스트링으로 바뀌었습니다.

    MultiByteToWideChar
    MultiByteToWideChar 호출은 쉽게 없앨 수 있으며, 단순한 대입문으로 바꾸면 됩니다. MultiByteToWideChar를 사용했을 때의 예제입니다.
    procedure TWideCharStrList.AddString(const S: string);
    var
      Size, D: Integer;
    begin
      Size := SizeOf(S);
      D := (Size + 1) * SizeOf(WideChar);
      FList[FUsed] := AllocMem(D);
      MultiByteToWideChar(0, 0, PChar(S), Size, FList[FUsed], D);
      Inc(FUsed);
    end;
    

    위 코드를 유니코드로 바꾸면, ANSI와 유니코드 양쪽 모두 컴파일이 됩니다.
    procedure TWideCharStrList.AddString(const S: string);
    var
      L, D: Integer;
    begin
      FList[FUsed] := StrNew(PWideChar(S));
      Inc(FUsed);
    end;
    


    SysUtils.AppendStr



    이 메소드는 사용하지 말 것을 권하며(deprecated), 따라서 AnsiString 버전만 존재하고 오버로드된 UnicodeString 버전은 추가되지 않았습니다.

    이 메소드의 호출은,
    AppendStr(String1, String2);
    

    다음과 같이 대체하십시오.
    String1 := String1 + String2;
    

    더 좋은 방법은 문자열을 연결하기 위해 새로 추가된 TStringBuilder 클래스를 사용하는 것입니다.

    GetProcAddress



    GetProcAddress 호출은 항상 PAnsiChar를 사용해야 합니다. (SDK에 W 접미사가 붙은 함수가 존재하지 않습니다) 예를 들면,
    procedure CallLibraryProc(const LibraryName, ProcName: string);
    var
      Handle: THandle;
      RegisterProc: function: HResult stdcall;
    begin
      Handle := LoadOleControlLibrary(LibraryName, True);
      @RegisterProc := GetProcAddress(Handle, PAnsiChar(AnsiString(ProcName)));
    end;
    

    노트: Windows.pas에 이 변환을 하는 오버로드된 메소드가 있습니다.

    포인터 연산 목적으로 문자 포인터가 아닌 포인터를 PChar() 캐스팅을 하는 경우
    이전의 버전들에서는, 포인터 연산이 지원되지 않는 데이터 타입 포인터가 많았습니다. 이 때문에, 단지 포인터 연산을 위해서 문자가 아닌 여러 종류의 포인터를 PChar로 캐스팅하는 경우가 잦았습니다. 델파이 2009에서는, 컴파일러 디렉티브로 지정하여 포인터 연산을 가능하게 할 수 있으며, PByte 타입의 경우 특별히 허용하고 있습니다.

    따라서, 여러분의 코드가 기존에 다음과 같이 포인터 데이터를 포인터 연산을 하기 위해 PChar로 캐스팅을 했다면,
    function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer;
    begin
      if (Node = FRoot) or (Node = nil) then
        Result := nil
      else
        Result := PChar(Node) + FInternalDataOffset;
    end;
    

    PChar 대신 PByte를 사용하도록 수정해야 합니다.
    function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer;
    begin
      if (Node = FRoot) or (Node = nil) then
        Result := nil
      else
        Result := PByte(Node) + FInternalDataOffset;
    end;
    

    위의 코드에서 Node는 실제로는 문자 데이터가 아닙니다. 단지 Node 다음의 특정 바이트 수만큼의 데이터를 액세스하기 위한 포인터 연산을 하기 위해 PChar로 캐스팅하고 있는 것입니다. 이 코드는 이전 버전에서는 SizeOf(Char) = Sizeof(Byte)였기 때문에 잘 동작했습니다. 이제는 달라졌으며, 이 코드가 제대로 동작하려면 PChar 대신 PByte를 사용하도록 수정해야 합니다. 이런 수정을 하지 않으면 Result는 잘못된 데이터 위치를 가리킬 것입니다.

    variant open array 파라미터



    variant open array 파라미터를 다루기 위해 TVarRec을 사용한 코드가 있다면, UnicodeString도 다룰 수 있도록 코드를 수정해야 합니다. UnicodeString의 경우를 위한 vtUnicodeString 타입이 새로 선언되었습니다. UnicodeString 데이터는 vUnicodeString에 저장됩니다. DesignIntf.pas에 있는 다음의 코드를 살펴보면, UnicodeString 타입을 처리하기 위해 새 코드가 추가되어야 하는 경우를 볼 수 있습니다.
    procedure RegisterPropertiesInCategory(const CategoryName: string;
      const Filters: array of const); overload;
    var
      I: Integer;
    begin
      if Assigned(RegisterPropertyInCategoryProc) then
        for I := Low(Filters) to High(Filters) do
          with Filters[I] do
            case vType of
              vtPointer:
                RegisterPropertyInCategoryProc(CategoryName, nil,
                  PTypeInfo(vPointer), );
              vtClass:
                RegisterPropertyInCategoryProc(CategoryName, vClass, nil, );
              vtAnsiString:
                RegisterPropertyInCategoryProc(CategoryName, nil, nil,
                  string(vAnsiString));
              vtUnicodeString:
                RegisterPropertyInCategoryProc(CategoryName, nil, nil,
                  string(vUnicodeString));
            else
              raise Exception.CreateResFmt(@sInvalidFilter, [I, vType]);
            end;
    end;
    


    CreateProcessW



    CreateProcess (CreateProcessW)의 유니코드 버전은 ANSI 버전과 사소한 차이가 있습니다. MSDN의 lpCommandLine 파라미터에 대한 설명을 인용하면,

    “이 함수의 유니코드 버전인 CreateProcessW는 스트링의 내용을 변경시킬 수 있습니다. 따라서, 이 파라미터는 읽기 전용의 메모리로의 포인터여서는 안됩니다. (const 변수 혹은 스트링 상수) 이 파라미터가 상수 스트링이면, 함수 호출이 액세스 바이올레이션(Access Violation)을 발생시킬 수 있습니다.”

    이 때문에, CreateProcess를 호출하는 기존의 일부 코드는 델파이 2009에서 컴파일 했을 때 액세스 바이올레이션을 발생시킬 수 있습니다.

    다음은 문제 발생 가능성이 있는 예입니다.

    스트링 상수가 파라미터인 경우
    CreateProcess(nil, 'foo.exe', nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo);
    

    상수 표현식이 파라미터인 경우
    const
      cMyExe = 'foo.exe';
    begin
      CreateProcess(nil, cMyExe, nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo);
    end;
    

    레퍼런스 카운트 -1인 스트링이 파라미터인 경우
    const
      cMyExe = 'foo.exe';
    var
      sMyExe: string;
    begin
      sMyExe := cMyExe;
      CreateProcess(nil, PChar(sMyExe), nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo);
    end;
    



    검색해봐야 할 코드 패턴들



    다음은 기존의 코드가 제대로 유니코드를 지원하기 위해 텍스트 검색을 해볼 수 있는 코드 패턴들의 리스트입니다.

  • “of Char” 텍스트와 “of AnsiChar” 텍스트를 검색하여 버퍼가 유니코드에 맞게 사용되었는지 확인합니다.
  • “string[” 텍스트를 검색하여 스트링 인덱스의 문자가 대입되는 변수가 Char(즉 WideChar)가 아닌 AnsiChar 타입 변수로 지정되도록 수정합니다.
  • “AnsiString”, “AnsiChar”, “PAnsiChar”를 명시적으로 지정한 부분을 찾아 그럴 필요가 있고 제대로 되어 있는지 확인합니다.
  • “ShortString”을 명시적으로 지정한 부분을 찾아 그럴 필요가 있고 제대로 되어 있는지 확인합니다.
  • “Length(” 텍스트를 검색하여 Length가 SizeOf와 동일한 의미로 사용되지는 않았는지 확인합니다.
  • “Copy(” , “Seek(” , “Pointer(” , “AllocMem(”, “GetMem(” 텍스트를 검색하여 스트링 혹은 문자 배열에 대해 제대로 동작하는지 확인합니다.

    이 리스트는 UnicodeString 타입을 지원하기 위해 잠재적으로 수정될 필요가 있을 수 있는 코드들입니다.


    결론



    여기까지 여러분이 유니코드 세계에서 여러분의 코드가 제대로 동작하기 위한 코드들을 정리해봤습니다. 일반적으로, 기존의 대부분의 코드는 제대로 동작할 것입니다. 컴파일 과정에서 발생하는 경고들의 대부분은 쉽게 해결할 수 있습니다. 재검토를 해봐야 하는 코드 패턴들의 대부분은 일반적이지 않은 코드들이므로, 그런 코드가 없다면 기존의 코드는 수정 없이도 잘 동작할 것입니다.


    원문 : http://dn.codegear.com/article/38693
  • 양용성.우석아빠 [ysyang]   2008-09-27 15:30 X
    글 잘 읽었습니다. 수고 많으셨습니다
    강형철 [nicey2k]   2008-10-17 17:59 X
    아주 잘 읽었습니다. 감사합니다.
    변영희 [trueeyes]   2008-10-19 11:05 X
    아주 많은 도움이 될것 같습니다. 감사합니다.
    이용남 [tachyon5]   2010-09-10 11:59 X
    많은 도움이 되었습니다. 감사합니다.

    + -

    관련 글 리스트
    137 델파이의 유니코드 지원 Part III: 기존 코드의 수정 박지훈.임프 13384 2008-09-25
    Google
    Copyright © 1999-2015, borlandforum.com. All right reserved.