에서 알아봤던 대로, 델파이 2009는 기본적으로 UTF-16 기반의 스트링을 사용하기 때문에 기존의 코드에서 특정 코드 부분들은 수정이 필요할 수도 있습니다. 일반적으로 기존 코드의 거의 대부분은 델파이 2009에서 그대로 잘 동작할 것입니다. 이 아티클에서 곧 보게 되겠지만, 필요한 코드 수정의 대부분은 상당히 명확하고 약간은 난해합니다. 하지만, 일부 특정 코드들은 재검토하고 UnicodeString과 제대로 동작하도록 보장하기 위해 수정을 해야 할 수 있습니다.
예를 들어, 스트링을 조작하거나 스트링 포인터 연산을 하는 코드는 유니코드 호환성을 위해 검사해야 합니다. 더 구체적으로 말하자면, 다음과 같은 코드들은 반드시 재검토하고 코드에 남아있지 않도록 해야 합니다.
영구 저장장치에 스트링을 쓰거나 읽는 코드, 스트링을 데이터 버퍼로 이용하는 코드
영구 저장장치에 쓰거나 읽는 코드는 정확한 바이트 수만큼 읽거나 쓰여지는지 확인해야 합니다. 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;
검색해봐야 할 코드 패턴들
다음은 기존의 코드가 제대로 유니코드를 지원하기 위해 텍스트 검색을 해볼 수 있는 코드 패턴들의 리스트입니다.