지난번 프로젝트에 자바랑 데이터 교환할 일이 있었는데 UInt64 형을 넘겨받았다.


Little Endian으로 바꿔야 하는데 msdn 뒤져보니 htonll 함수가 winsock2에 있는데 Windows 8부터 지원이라고 써있네.


그때 급해서 간단하게 만들어 쓰다가 Visual Studio 2013 Community 깔아서 winsock2.h 찾아보니 소스로 구현이 되어있네.


나중에 쓸일이 있을까 해서 관련 소스 포팅...






msdn에 ntohll 함수 return 값이 설명이 잘못되어 있는 걸 찾았다.


https://msdn.microsoft.com/ko-kr/library/windows/desktop/jj710202%28v=vs.85%29.aspx


return이 u_long인데 winsock2.h에는 unsigned __int64로 되어있다.



유닛 소스는 여기에


https://github.com/yypbd/yypbd-Delphi-Libs/blob/master/lib/Winsock2Addon.pas



1. ServerContainerUnit

  a. ServerContainer1 Name

    제어판 서비스 속성의 서비스 이름 변경

    

  b. ServerContainer1 DisplayName

    제어판 서비스 속성의 표시 이름 변경


  c. 서비스 Description 추가 - AfterInstall 이벤트에 추가

    제어판 서비스 속성의 설명 변경




2. ServerMethodsUnit

  a. ServerMethods1 Name 변경

    클라이언트가 서비스에 접속할때 이용하는 TDSProviderConnection 컴포넌트의 ServerClassName에 설정할 이름





ShowMessage, MessageDlg를 쓰면 버튼, 캡션이 영문으로 나온다.


  ShowMessage( 'asdf' );

  MessageDlg( '가나다라', mtInformation, mbYesNo, 0 );






예전에는 메시지창을 만들어서 쓰곤 했었는데 귀...귀찬....


MessageDlg의 인자로 Winapi의 MessageBox를 쓰도록 Wrapping하려고 했으나...

Winapi의 MB_ 항목들에는 YesToAll, NoToAll, Abort 등 버튼이 없다.

MessageBox로 Wrapping하다가 델파이에 있는 기능이 더 많은데?라는 생각에 Stop!!


문자열을 바꿔버리면 될까나? 라는 생각이 들어서 Dialogs 유닛 확인~

Dialogs 유닛을 보니 Consts유닛에 있는 resourcestring 문자열들을 이용하고 있다.



작업


우선 Consts 유닛을 다른 이름으로 저장한 후에 수정했다.

우선은 메시지 창에서 사용하는 부분 정도만 수정


  SMsgDlgWarning = '경고';

  SMsgDlgError = '오류';

  SMsgDlgInformation = '알림';

  SMsgDlgConfirm = '확인';

  SMsgDlgYes = '예(&Y)';

  SMsgDlgNo = '아니오(&N)';

  SMsgDlgOK = '확인';

  SMsgDlgCancel = '취소';

  SMsgDlgHelp = '도움말(&H)';

  SMsgDlgHelpNone = '도움말 없음';

  SMsgDlgHelpHelp = '도움말';

  SMsgDlgAbort = '취소(&A)';

  SMsgDlgRetry = '재시도(&R)';

  SMsgDlgIgnore = '무시(&I)';

  SMsgDlgAll = '전부(&A)';

  SMsgDlgNoToAll = '전부 아니오(&o)';

  SMsgDlgYesToAll = '전부 예(&A)';

  SMsgDlgClose = '닫기(&C)';


그러고는 저장한 파일을 프로젝트에 추가~

(꼭 추가하지 않더라도 같은 경로에 넣어도 된다.)



위의 소스를 다시 돌려보자




최종 결과물




오홋!!! 됐다~

파일 저장해놨다가 프로젝트 할때 추가하면 될듯하다.

메시지창 말고 다른 메시지도 한글로 바꿔서 적용하면 국내 프로그램 만들 때 편하게 쓸 수 있을 것 같다.



-------------------------------------------------------------------------------------------


ps. 테스트를 해보니 따로 저장한 파일을 library path에 추가되어 있는 경로에 넣어도 된다.

 그러면 프로젝트에 넣지 않아도 한글로 표시된다.


그러므로 난 앞으로 메시지는 모두 한글로 보여줄래!! 라고 한다면 Consts유닛을 델파이의 라이브러리 경로에 추가되어 있는 폴더에 넣으면 되고

필요한 경우에만 쓰고자 한다면 Consts 유닛을 따로 보관하다가 필요한 경우 프로젝트 경로에 복사해 넣으면 되겠다.


c로 된 헤더 포팅해보다가 ...을 발견했다.

예를 들자면 printf가 있겠지?

printf




헛??? array of const로 포팅해야 하나 하고 보다가 varargs 라는 걸 찾았다.

루아 dll에 비슷한 함수가 있어서 가져와봤다.

헤더에는 이렇게 선언되어 있다.

lua_pushfstring


포팅해보자면




혹시나 델파이 내에서 함수도 저렇게 만들어서 쓸 수 있나 했는데 안된다 -_-;
c쪽 헤더 포팅할때나 쓸 수 있는듯
구현할때는 array of const 를 쓰자~




MSDN에 있는 What's New in the Windows API 보다가 VHD 관련한 API가 보여서 포팅해봤다.

VHD 관련 API는 Windows 7 이상에서만 동작한다.
서버는 2008 R2 이상부터

포팅한 헤더 이름은 VirtDisk.h
헤더의 SDK는 v7.0의 것을 이용했다.
MSDN의 예제 링크는 여기
MSDN에 관련 VHD 페이지는 VHD Reference를 참고하면 되겠다.


Example


아아아~ 귀차니즘으로 인한 콘솔보다 못한 UI라니 ㅠ.ㅜ

1. Create

예제에서는 C:\aaa.vhd의 경로에 파일을 생성한다.
현재 예제에서는 사이즈가 256mb로 지정되어 있다.



2. Attach & Detach

VHD를 시스템에 Attach시킨다.
딸깍하는 Windows 효과음이 나면서 처음이라면 가상드라이브 관련 드라이버도 설치가 진행된다.
누르고 자시고 할 것도 없이 그냥 진행된다 -_-;
탐색기에서는 변화가 없으니 제어판의 디스크 관리 항목을 열어보자.


디스크 1이라는 항목이 추가되어 있다.
디스크 1이라고 씌여진 부분에서 마우스 우클릭 후 디스크 초기화를 하면 온라인으로 상태가 변화된다.
256 MB가 씌여진 부분에서 우클릭 후 새 단순볼륨를 누르고 포맷을 진행하면 드라이브가 추가된다.



포맷이 완료되면 위 그림처럼 드라이브가 생성되고 파일을 읽고 쓸 수 있게 된다.
물론 탐색기에서 드라이브처럼 이용할 수 있다.
Detach하면 드라이브가 제거되며 다시 Attach하면 위에서 설정된 드라이브로 바로 마운트된다.


3. 기타 API

CompactVirtualDisk와 MergeVirtualDisk는 예제에서도 실행이 잘 안되서 확인을 못했고 나머지 api는 Attach 혹은 Detach 여부에 따라 동작이 가능했다.



PS

1. MINENUMSIZE 까먹어서 테스트 하다가 삽질 -_-;
http://docwiki.embarcadero.com/RADStudio/en/Delphi_Compiler_Directives_(List)_Index 
컴파일러 지시자 링크

2. MSDN보니 Windows 8에서 API가 몇개(AddVirtualDiskParent 등) 추가됐다 -_-;;;
 어..어이~ 마소~ Windows7도 얼른 지원해주라구.


예전 버전들에서는 압축 관련 작업할때

1. 7zip dll
http://www.progdigy.com/?page_id=13 

2. TSevenZipVCL
http://www.rg-software.de/rg/index.php?option=com_content&task=view&id=29&Itemid=51 

3. SynZip
http://synopse.info/forum/viewtopic.php?pid=163 

JCL에도 있다는데 이것저것 깔리는게 많으니 패쓰~





Delphi XE2버전에는 Zip라이브러리가 포함되어 있다.

사용예를 보자면

1. uses추가
  uses에 System.Zip을 추가한다.


2. 압축하기


3. 압축풀기




압축이나 해제시 스트림이나 바이트 배열도 지원해서 사용하기는 편리한데 압축/해제 Progress를 Notify할 수 있는 프로퍼티가 없다.

소스가 다 있으니 프로퍼티 추가해서 써도 같기도 하고 -_-;;

뭐 다음 버전에 추가되리라 믿어야지.

우선은 큰 파일 말고 작은 파일 처리할때 써야 할 듯 하다.

추가로 Encryption 관련 부분도 없다 -_-;;;

MSDN Shell관련 API를 찾다가 PathMakeUniqueName 함수를 찾았다.

지난번 프로젝트에서 파일을 새로 만들때 유니크한 파일명을 구하려고 생쑈했던 기억이 나서 살펴봤다.


[MSDN PathMakeUniqueName]
http://msdn.microsoft.com/en-us/library/windows/desktop/bb776476(v=vs.85).aspx 
상세 파라미터나 리턴값은 여기서~


[Delphi]



[C++]



[테스트]


경로에 메일서식.txt 파일 있는 경우
D:\메일\메일서식 (1).txt 리턴

경로에 메일서식.txt, 메일서식 (1).txt가 있는 경우
D:\메일\메일서식 (2).txt 리턴

경로에 메일서식.txt, 메일서식 (1).txt, 메일서식 (3).txt가 있는 경우
D:\메일\메일서식 (2).txt 리턴


[ETC]
PathMakeUniqueName 함수는 인자중 Template를 이용한 방법이고 실제 존재하는 파일을 기반으로 유니크한 파일명을 얻어내는 PathYetAnotherMakeUniqueName 함수도 있다.

http://msdn.microsoft.com/en-us/library/windows/desktop/bb776479(v=vs.85).aspx 



C#으로 작성된 xls파일을 생성하는 라이브러리가 있길래 Delphi로 포팅해봤다.

라이브러리 & 예제


 
C# 소스 출처http://www.codeproject.com/KB/office/biffcsharp.aspx

BIFF 포맷 문서 : http://sc.openoffice.org/excelfileformat.pdf


C# 소스 작성자 이름이 익숙하길래 검색해보니 예전에 잘 이용하던 psvDialogs를 만든 사람이네.

그런데 이제 C#만 개발한다고... 아 안타깝다... ㅠ.ㅜ


라이브러리를 사용하는 예제 코드는 아래와 같다.




생성된 파일을 엑셀에서 열어보면 아래와 같다.

 

 


ListView에서 Theme를 켜고 GroupView를 True로 세팅하면 위와같이 Group으로 볼수 있다.

Group의 순서를 바꾸려면 아래와 같이 작업한다.

2010버전에서는 Group 정렬이 따로 빠져나와있지 않아서 CommCtrl을 참조했다.


1. 정렬시 전달할 사용자 데이터 구조체를 선언

type

  PGroupSortData = ^TGroupSortData;

  TGroupSortData = record

    ListView: TListview;

    ColumnIndex: Integer;

    Ascend: Boolean;

  end;



2. 정렬시 호출할 콜백 함수 구현

function ListView_GroupSort(Group1_ID: Integer; Group2_ID: Integer; pvData: Pointer): Integer; stdcall;

var

  GroupSortData: PGroupSortData;

  Str1, Str2: string;

begin

  GroupSortData := PGroupSortData(pvData);


  Str1 := GroupSortData.ListView.Groups.Items[Group1_ID].Header;

  Str2 := GroupSortData.ListView.Groups.Items[Group2_ID].Header;


  Result := CompareText( Str1, Str2 );

  if not GroupSortData.Ascend then

    Result := -Result;

end;


그룹 헤더값을 비교해서 결과값을 리턴한다.



3. uses에 CommCtrl 선언



4. 정렬 실행

var

  GroupSortData: TGroupSortData;

begin

  GroupSortData.ListView := ListView1;

  GroupSortData.Ascend := FAscend;

  GroupSortData.ColumnIndex := Column.Index;


  ListView_SortGroups( ListView1.Handle, ListView_GroupSort, @GroupSortData );


  FAscend := not FAscend;
end; 


LVM_SORTGROUPS 메시지를 쏘는 것으로 CommCtrl.pas를 참조한다.

VC의 CommCtrl.h에 있는 매크로와 동일하다.


5. exe demo & source


지난해에 약간의 Java 코드를 델파이로 포팅하던 도중에 StringBuilder를 String에다가 더하는 코드로 바꾸느라 손꾸락이 아팠었다.
우연찮게 SysUtils 유닛을 살펴보다보니 TStringBuilder 클래스가 있네 -_-;

1. 추가
Append, AppendLine, AppendFormat으로 추가한다.
Append에는 다양한 타입을 쓸수 있으니 간편하다.

2.  삽입
Append와 마찬가지로 다양한 타입을 지원하는 Insert가 있다.

3. 삭제
전체를 지우는 Clear와 구간을 삭제할 수 있는 Remove가 있다.

4. 변경
Replace함수를 이용한다.

5. 최종 결과물
ToString을 쓰면 String으로 리턴해준다.


var

  Str: TStringBuilder;

begin

  Str := TStringBuilder.Create;


  try

    Str.Append( 100 );

    Str.Append( sLineBreak );

    Str.AppendLine( '가나다라' );


    ShowMessage( Str.ToString );

  finally

    Str.Free;

  end;


아... 신규 버전들 나오면 추가된 내용을 한번 쭉 살펴봐야 하거늘 ㅠ.ㅜ
괜한 삽질로 고생만 했네.


ps. 성능 이슈
String을 쓰는 것과 TStringBuilder를 쓰는 것에 대해 비교를 해놓은 링크가 있다.

http://blog.marcocantu.com/blog/not_so_fast_tstringbuilder.html

http://www.monien.net/blog/index.php/2008/10/delphi-2009-tstringbuilder
 


흠.. 그런데 성능 이슈라면 TMemoryStream을 쓰는게 제일 낫지 않나...

TStringBuilder는 쉽게 문자열을 구성할때 쓰는게 좋을듯 싶은데.

Delphi 2009 에서 Generic이 추가되면서 유용한 클래스들이 추가되었다.
특히나 특정 Key와 쌍을 이루는 값을 저장할때 TDictionary는 참 유용하게 쓰인다.
일반 데이터 형을 보관할 때는 상관이 없으나 Class를 보관할때는 메모리 해제에 주의해야 한다.

uses
  Generics.Collections;

type
  TTempKey = class
  end;

  TTempValue = class
  end;

1. TDictionary
procedure TForm2.Button1Click(Sender: TObject);
var
  Dic: TDictionary<Integer,TTempValue>;
  Key: Integer;
begin
  Dic := TDictionary<Integer,TTempValue>.Create;
  try
    Dic.Add( 1, TTempValue.Create );

    for Key in Dic.Keys do
      Dic.Items[Key].Free;
  finally
    Dic.Free;
  end;
end;

Key와 매칭되는 Value를 저장할 수 있다.
여기서는 Key의 형을 Integer, Value의 형은 TTempValue 클래스로 설정했다.

Dic만 해제하면 TDictonary의 Values에 저장된 TTempValue 인스턴스가 붕 떠서 Memory Leak이 발생한다.
반드시 Dic를 해제하기 전에 Dic의 아이템들을 해제해야 한다.


2. TObjectDictionary

예전부터 Contnrs 유닛에 있던 TObjectList와 비슷하다.
생성시에 OwnsObject를 True로 설정해서 자동으로 아이템의 메모리를 해제했던 기억이 있을 것이다.

(1) Key와 Value 가 모두 Class형인 경우

procedure TForm2.Button2Click(Sender: TObject);
var
  Dic: TObjectDictionary<TTempKey,TTempValue>;
begin
  Dic := TObjectDictionary<TTempKey,TTempValue>.Create( [doOwnsKeys, doOwnsValues] );

  try
    Dic.Add( TTempKey.Create, TTempValue.Create );
  finally
    Dic.Free;
  end;
end;

인스턴스 생성시에 생성자의 인자를 보면 TDictionaryOwnerships라는 집합형을 넘긴다.
TObjectList의 OwnsObject를 생각하면 된다.
Key와 Value가 모두 클래스 형이므로 둘다 소유하는 것으로 설정했다.


(2) Value만 Class형인 경우

procedure TForm2.Button3Click(Sender: TObject);
var
  Dic: TObjectDictionary<Integer,TTempValue>;
begin
  Dic := TObjectDictionary<Integer,TTempValue>.Create( [doOwnsValues] );

  try
    Dic.Add( 1, TTempValue.Create );
  finally
    Dic.Free;
  end;
end;

Value만 클래스 형인 경우 Key를 소유하라고 doOwnsKeys를 설정하면 익셉션이 발생한다.
익셉션 클래스는 EInvalidCast, 메시지는 Invalid class typecast.

문제점 발견

프로그램에 스킨을 입히거나 특정 용도가 있는 경우 Border를 bsNone으로 하여 상단 Caption을 숨길 때가 있다.
일반 폼이라면 정상동작하나 bsNone인 폼이라면 폼 최대화 기능 구현을 위해 WindowState를 wsMaximized로 세팅하면 작업표시줄까지 덮어버린다.
그래서 보통은 아래와 같이 작업표시줄을 제외한 작업영역을 구해서 그 크기만큼 폼의 크기를 세팅하고 한다.

// 폼을 최대화하고 Align을 설정하거나
begin
  Form.WindowState := wsMaximized;
  Form.Align := alClient;
end;

// 혹은 작업 영역을 구해서 폼의 크기를 맞춰준다.
var
  WorkRect: TRect;
begin
  Form.WindowState := wsMaximized;        

  SystemParametersInfo( SPI_GETWORKAREA, 0, @WorkRect, 0 );
  Form.SetBounds( WorkRect.Left, WorkRect.Top, WorkRect.Right - WorkRect.Left, WorkRect.Bottom - WorkRect.Top );
 end;


문제는 듀얼모니터인 경우이다.
프로그램을 두번째 스크린으로 옮기고 최대화를 실행하면 폼이 보이지 않게 되어버린다.
어디로 날라간 것일까 -_-;

폼이 다른 모니터에 있는 경우 위치를 제대로 얻어오지 못한다.


해결방법

Forms에 있는 Screen 전역 객체를 이용하여 폼이 위치한 모니터를 얻어내고 작업 영역을 구하면 된다.

1. 폼이 위치한 곳의 TMonitor 객체를 얻어내보자
var
  Monitor: TMonitor;
begin
  Monitor := Screen.MonitorFromWindow( Form.Handle );
end;

2. TMonitor에는 API를 래핑한 여러가지 프로퍼티가 존재한다.
작업 영역을 얻기 위해서는 TMonitor의 WorkareaRect를 이용하자.
작업 영역을 구해보자
var
  WorkRect: TRect;
begin
  WorkRect := Monitor.WorkareaRect;
end;


전체 코드

var
  Monitor: TMonitor;
  WorkRect: TRect;
begin
  Form.WindowState := wsMaximized;

  Monitor := Screen.MonitorFromWindow( Form.Handle );
  WorkRect := Monitor.WorkareaRect;
  Form.SetBounds( WorkRect.Left, WorkRect.Top, WorkRect.Right - WorkRect.Left, WorkRect.Bottom - WorkRect.Top );
end;


PS
더 자세한 사항은 Forms 유닛의 TScreen, TMonitor 클래스를 참조하기 바란다.
화면과 관련한 Windows API 사용법이 자세히 작업되어 있다.

맨날 TOpenDialog로 코딩할때 이런식으로 했었다.
혹시나 사용자가 이름을 막 넣을 수도 있기 때문에 파일이 있는지 꼭 확인하곤 했었다.


if OpenDialog1.Execute then
begin
  if FileExists(OpenDialog1.FileName) then
  begin
    // Todo...
  end;
end;

사람이 머리가 나쁘면 손발이 고생이고
프로그래머가 머리가 나쁘면 코딩량이 늘어난다. ㅠ.ㅜ

TOpenDialog의 Property를 보면 Options라는 집합 프로퍼티가 보인다.

 ofReadOnly
 Causes the Read Only check box to be selected initially when the dialog box is created. This flag indicates the state of the Read Only check box when the dialog box is closed.
 ofOverwritePrompt  덮어쓰기 할것인지 확인한다.
 Save as 다이얼로그에서 이용한다.
 ofHideReadOnly  읽기 전용 체크박스를 숨긴다.
 
 동작 확인 불가
 ofNoChangeDir  사용자가 다를 폴더를 탐색하더라도 current directory가 변경되지 않도록 원래의 값으로 복원한다.
 ofShowHelp  도움말 버튼이 보이도록 한다.
 ofNoValidate  파일명이 시스템에 유효한 파일명인지 검사하지 않는다.
 ofAllowMultiSelect  여러개의 파일을 선택할수 있도록 한다.
 Files 프로퍼티에 선택한 파일명이 넘어온다.
 ofExtensionDifferent Specifies that the user typed a file name extension that differs from the extension specified by lpstrDefExt. The function does not use this flag if lpstrDefExt is NULL.

무슨 말인지 모르겠다 -_-;
 ofPathMustExist  폴더가 반드시 존재해야 한다.
 ofFileMustExist  파일이 반드시 존재해야 한다.
 파일이 없는 경우는 경고 메시지가 출력된다.
 이 플래그가 활성화되면 ofPathMustExist도 활성화된다.
 ofCreatePrompt  사용자가 입력한 파일명이 존재하지 않는 경우 생성할 것인지 물어보는 다이얼로그를 띄운다.
 ofShareAware Specifies that if a call to the OpenFile function fails because of a network sharing violation, the error is ignored and the dialog box returns the selected file name. If this flag is not set, the dialog box notifies your hook procedure when a network sharing violation occurs for the file name specified by the user. If you set the OFN_EXPLORER flag, the dialog box sends the CDN_SHAREVIOLATION message to the hook procedure. If you do not set OFN_EXPLORER, the dialog box sends the SHAREVISTRING registered message to the hook procedure.
 ofNoReadOnlyReturn  파일을 포함하고 있는 폴더가 읽기전용이거나 읽기 전용 파일을 반환하지 않도록 한다.
 ofNoTestFileCreate  다이얼로그가 닫히기 전에 파일이 생성되지 않도록 한다.
 ofNoNetworkButton  Network 버튼을 숨긴다.
 ofNoLongNames  8.3의 옛날 스타일로 보이게 한다.
 ofOldStyleDialog가 설정되어 있어야 한다.
 ofOldStyleDialog  다이얼로그가 고전 스타일로 보이도록 한다.
 ofNoDereferenceLinks  바로가기 파일(.LNK)의 원래 경로를 받을 수 있도록 한다.
 ofEnableIncludeNotify  폴더를 열었을때 CDN_INCLUDEITEM을 보낼것인지 설정한다.
 (델파이에는 OFN_ENABLEHOOK을 설정하는 옵션이 없는데 어떻게 사용할수 있는지는 모르겠음.)
 ofEnableSizing  다이얼로그 사이즈 변경 가능하도록 한다.
 ofDontAddToRecent  선택한 파일이 CSIDL_RECENT 즉 최근 폴더에 추가되지 않도록 한다.
 ofForceShowHidden
 hidden, system 파일을 보여준다.
 주의 : 파일이 hidden과 system 속성을 모두 가지고 있다면 보이지 않는다.

주황색 부분은 무슨 내용인지 확인 불가.
뭐 크게 필요하지는 않은 옵션같다.
TOpenDialog는 기본으로 ofHideReadOnly, ofEnableIncludeNotify 옵션이 켜져있다.


MSDN에 관련 내용이 더 있으니 확인하면 된다.
http://msdn.microsoft.com/en-us/library/ms646839%28VS.85%29.aspx

CFileDialog 도움말
http://msdn.microsoft.com/en-us/library/43xtah3y.aspx

1. TArray 클래스는 ?

Generics.Collections 유닛의 맨 위에 TArray 클래스가 있다.

주의) TArray<T>형이 아니다 -_-;
TArray<T>는 System 유닛에 array of T라고 선언되어 있다.

TArray 클래스는 class function으로만 이루어져 있다.
Sort 함수와 BinarySearch 함수이다.


2. 초기화

함수의 테스트를 위해 배열을 선언하고 초기화해보자.

  private
    FMyArray: TArray<string>;

  SetLength( FMyArray, 5 );
  FMyArray[0] := 'ccc';
  FMyArray[1] := 'ddd';
  FMyArray[2] := 'aaa';
  FMyArray[3] := 'eee';
  FMyArray[4] := 'fff';


3. Sort

인자들의 비교를 위한 TComparer를 선언한다.
인자가 string이니 선언도 string으로 한다.

type
  TMyStringComparer = TComparer<string>;


1) 함수를 이용한 방법

TComparer의 Construct 클래스 메소드를 이용해서 IComparer인터페이스를 생성해서 넘겨준다.
어차피 인터페이스를 넘기는 것이므로 메모리 해제는 신경쓸 필요가 없다.

function MyStringComparison(const Left, Right: string): Integer;
begin
  Result := CompareStr(Left, Right);
end;

procedure TForm2.Button1Click(Sender: TObject);
begin
  TArray.Sort<string>( FMyArray, TMyStringComparer.Construct(MyStringComparison) );
end;


2) Anonymous Method를 이용한 방법

동일한 방법이나 Delphi 2009에서부터 추가된 Anonymous Method를 이용해서 정렬하는 방법이다.

자세한 설명은 Dr.Bob에서 보면 되겠다.

http://www.drbob42.com/examines/examinA5.htm

procedure TForm2.Button2Click(Sender: TObject);
begin
  TArray.Sort<string>( FMyArray, TMyStringComparer.Construct(
    function (const Left, Right: string): integer
    begin
      Result := CompareStr(Left, Right);
    end
  ) );
end;


4. BinarySearch

Sort와 동일하다.
FoundIndex 인자에 찾은 Index를 알려준다.

procedure TForm2.Button3Click(Sender: TObject);
var
  FoundIndex: Integer;
begin
  TArray.BinarySearch<string>( FMyArray, 'ccc', FoundIndex, TMyStringComparer.Construct(MyStringComparison) );

  ShowMessage( IntToStr(FoundIndex) );
end;


5. 결론

괜히 코드로 처리하느라 힘빼지 말고 배열의 정렬과 검색, 이제는 간단하게 처리하자.
대신 원리 파악을 위해 반드시 TArray 클래스의 소스를 보고 동작 원리는 파악하도록 한다.

먼저 private와 protected를 모르겠다면 OOP의 encapsulation에 관해 공부를... -_-a


Delphi의 Class는 멤버 접근에 독특한 특징이 있다.

private이나 protected로 지정이 되어있다고 하더라고 같은 유닛에 있는 클래스라면 접근이 가능하다.

사용할때는 편하지만 설계할때는 대략 좋지 않다.

  TClass1 = class
  private
    FField1: string;
    FField2: string;
  end;

  TClass2 = class
  public
    procedure Test;
  end;

procedure TClass2.Test;
var
  Class1: TClass1;
begin
  Class1 := TClass1.Create;

  ShowMessage( Class1.FField1 );
  ShowMessage( Class1.FField2 );

  Class1.Free;
end;


TClass1과 TClass2가 같은 Unit에 있다면 위의 소스는 컴파일이 성공한다.

문제는 두 Class가 서로 다른 Unit에 있다면 E2261 Cannot access private symbol 에러가 발생한다.

Class를 분리하거나 하면 웃기게 된다.


확실한 구분을 위해 private앞에 strict를 추가해보자.

  TClass1 = class
  strict private
    FField1: string;
    FField2: string;
  end;


컴파일해보면

E2261 Cannot access private symbol TClass1.FField1
E2261 Cannot access private symbol TClass1.FField2

에러가 발생한다.


명확한 OOP를 위해서 확실하게 strict 를 지정해주자.

추가된 문법은 팍팍 이용해 주자~
보통 Delphi에서는 외부 공개용 const 를 interface 아래에 놓고 처리한다.

그런데 가끔 보면 한 유닛안에 몽땅 다 때려넣어서 꽤나 복잡해지는 경우가 있다.

이런때는 const를 연관된 Class내에 넣어버리면 명확하게 구분하여 사용할 수 있다.


unit MainLib;

interface

const
  CLASS_NAME = 'TMainClass';
  ARRAY_COUNT = 6;

위와 같이 CLASS_NAME const 상수가 있을때 MainLib를 uses에 추가한다면 ctrl+space로 code insight를 불러내면 바로 보여서 어느 유닛에 속해 있는지 한눈에 보이지 않게 된다.

게다가 아래에 다른 클래스가 있다면 이 const 상수가 어느 클래스와 연관이 있는지 찾기가 힘들다.


const 상수를 클래스에 아래와 같이 넣어본다.

type
  TMainClass = class
  const
    CLASS_NAME = 'TMainClass';
    ARRAY_COUNT = 6;
  end;


값을 쓰고 싶을때는 클래스 생성하지 않고도 불러다 쓸수 있다.

  ShowMessage( TMainClass.CLASS_NAME );



ps.
상속받은 클래스의 선언부에서 부모의 const를 사용하려고 하면 ARRAY_COUNT 상수를 찾을수 없다고 컴파일이 되지 않는다.

type
  TSubClass = class(TMainClass)
  strict private
    FIntegerArray: array[0..ARRAY_COUNT - 1] of Integer;
  end;

TMainClass.ARRAY_COUNT 로 변경하면 된다.
TSubClass의 구현부에서는 그냥 불러다 써도 된다.
아래의 예제를 참고한다.

type
  TSubClass = class(TMainClass)
  strict private
    FIntegerArray: array[0..TMainClass.ARRAY_COUNT - 1] of Integer;
  public
    procedure PrintMessage;
  end;

implementation

procedure TSubClass.PrintMessage;
begin
  ShowMessage( IntToStr(ARRAY_COUNT) );
end;

이제 const도 관련 Class안으로 넣어버리자!!!!!


Delphi 2009부터 추가된 Generic이 있다.
간단하게 말하자면 C++의 Template라고 생각하면 되겠다.
이제 자바나 C++ 포팅할때도 충분히 쉽게 포팅할수 있게 되었다.
Generics.Collections를 보면 List 등의 클래스를 쉽게 포팅할수 있는 좋은 클래스가 많이 있다.

자바쪽 소스를 포팅하다보니 클래스에 ToArray라는 함수가 있는데 열어봐도 없길래 Generics.Collections 유닛을 열어보니 주석처리가 되어있다.
이유는 pending compiler support 라고 씌여있다.

흠... 없다면 상속해서 만들어주면 되겠군.
TObject를 담을 때 유용한 TObjectList를 상속받아서 하나 만들어보자.

type
  TMyObjectList<T: class> = class( TObjectList<T> )
  public
    function ToArray: TArray<T>;
    class procedure FreeArray( AMyArray: TArray<T> );
  end;

implementation

class procedure TMyObjectList<T>.FreeArray(AMyArray: TArray<T>);
var
  I: Integer;
begin
  for I := 0 to Length(AMyArray) - 1 do
    FreeAndNil( AMyArray[I] );

  SetLength( AMyArray, 0 );
end;

function TMyObjectList<T>.ToArray: TArray<T>;
var
  i: Integer;
begin
  SetLength(Result, Count);
  for i := 0 to Count - 1 do
    Result[i] := Items[i];
end;



type
  TMyItem = class
  public
    Data: string;
  end;

List에 담을 Item 클래스를 하나 선언하고 테스트 코드를 넣어봤다.

자동 해제 옵션을 켜고 사용할때
procedure TForm2.Button2Click(Sender: TObject);
var
  List: TMyObjectList<TMyItem>;
  Item: TMyItem;

  ItemArray: TArray<TMyItem>;
  I: Integer;
begin
  List := TMyObjectList<TMyItem>.Create;

  Item := TMyItem.Create;
  Item.Data := 'first item';
  List.Add(Item);

  Item := TMyItem.Create;
  Item.Data := 'second item';
  List.Add(Item);

  ItemArray := List.ToArray;

  for I := 0 to Length(ItemArray) - 1 do
    ShowMessage(ItemArray[I].Data);

  // 주의! 리스트가 해제될때 아이템도 같이 해제된다.
  // Free한 이후에 ItemArray는 사용이 불가하다.
  List.Free;
end;

자동 해제 옵션을 끄고 사용할때
procedure TForm2.Button1Click(Sender: TObject);
var
  List: TMyObjectList<TMyItem>;
  Item: TMyItem;

  ItemArray: TArray<TMyItem>;
  I: Integer;
begin
  List := TMyObjectList<TMyItem>.Create(False);

  Item := TMyItem.Create;
  Item.Data := 'first item';
  List.Add(Item);

  Item := TMyItem.Create;
  Item.Data := 'second item';
  List.Add(Item);

  ItemArray := List.ToArray;

  // 생성할 때 AOwnsObjects 옵션을 False로 설정했기 때문에 List를 해제하더라도 Item은 해제되지 않는다.
  List.Free;

  for I := 0 to Length(ItemArray) - 1 do
    ShowMessage(ItemArray[I].Data);

  // Array의 사용을 마치면 배열을 해제하여 Item을 해제한다.
  // 해제하기 전까지는 ItemArray를 계속 이용할 수 있다.
  TMyObjectList<TMyItem>.FreeArray( ItemArray );
end;


예전에는 TObjectList를 이용하더라도 Item을 형변환해서 사용하거나 상속받아서 새로 만들고 했었는데 Generic을 이용하니 너무나 편하다.
C++의 Template가 이제 가물가물하니 시간내서 Delphi와 비교 좀 해봐야겠다.

===============================================
20110919 수정사항 추가

    else if Str1[Pos1] = #0 then
    begin
      Result := 0;
      Exit;
    end

에셔 결과값 0을 -1로 변경해야함.

================================================


---------
a - 1
a - 10
a - 99
a - 9
a - 10000
---------
위와 같은 데이터가 있을때

델파이의 CompareStr로 비교하면
---------
a - 1
a - 10
a - 10000
a - 9
a - 99
---------
로 나옵니다.

윈도우탐색기에서 입력해보면
--------
a - 1
a - 9
a - 10
a - 99
a - 10000
--------
으로 정렬됩니다.


출처는 http://www.scottandmichelle.net/scott/code/index2.mv?codenum=100 입니다.
씨 소스로 되어있길래 포팅해봤습니다.

function NaturalOrderCompareString( const A1, A2: string; ACaseSensitive: Boolean ): Integer;
var
  Str1, Str2: PChar;
  Pos1, Pos2: Integer;
  EndPos1, EndPos2: Integer;
begin
  Str1 := PChar(A1);
  Str2 := PChar(A2);

  Pos1 := -1;
  Pos2 := -1;

  while True do
  begin
    Inc( Pos1 );
    Inc( Pos2 );

    if (Str1[Pos1] = #0) and (Str2[Pos2] = #0) then
    begin
      Result := 0;
      Exit;
    end
    else if Str1[Pos1] = #0 then
    begin
      Result := -1;
      Exit;
    end
    else if Str2[Pos2] = #0 then
    begin
      Result := 1;
      Exit;
    end;

    if (Str1[Pos1] >= '0') and (Str1[Pos1] <= '9') and
       (Str2[Pos2] >= '0') and (Str2[Pos2] <= '9') then
    begin
      EndPos1 := Pos1;
      repeat
        Inc(EndPos1);
      until not ((Str1[EndPos1] >= '0') and (Str1[EndPos1] <= '9'));

      EndPos2 := Pos2;
      repeat
        Inc(EndPos2);
      until not ((Str2[EndPos2] >= '0') and (Str2[EndPos2] <= '9'));

      while True do
      begin
        if EndPos1 - Pos1 = EndPos2 - Pos2 then
        begin
          // 이부분이 숫자비교임. StrToInt 한 다음에 빼도 될 것임
          Result := CompareStr( Copy(Str1, Pos1+1, EndPos1 - Pos1),  Copy(Str2, Pos2+1, EndPos1 - Pos1) ) ;

          if Result = 0 then
          begin
            Pos1 := EndPos1 - 1;
            Pos2 := EndPos2 - 1;
            Break;
          end
          else
          begin
            Exit;
          end;
        end
        else if EndPos1 - Pos1 > EndPos2 - Pos2 then
        begin
          if Str1[Pos1] = '0' then
            Inc(Pos1)
          else
          begin
            Result := 1;
            Exit;
          end;
        end
        else
        begin
          if Str2[Pos2] = '0' then
            Inc( Pos2 )
          else
          begin
            Result := -1;
            Exit;
          end;
        end;
      end;
    end
    else
    begin
      if ACaseSensitive then
        Result := CompareStr( Copy(Str1, Pos1, 1), Copy(Str2, Pos2, 1) )
      else
        Result := CompareText( Copy(Str1, Pos1, 1), Copy(Str2, Pos2, 1) );

      if Result <> 0 then
        Exit;
    end;
  end;
end;



리스트뷰 OnCompare에 델파이의 CompareStr, NaturalOrderCompareString 함수, Windows 유닛 내의 CompareString 함수로 정렬해서 비교한 간단한 예제가 있으니 비교해보시기 바랍니다.

정렬부분 소스는
procedure TForm3.ListView1Compare(Sender: TObject; Item1, Item2: TListItem;
  Data: Integer; var Compare: Integer);
var
  CompareResult: Integer;
begin
  case ComboBox1.ItemIndex of
    0:
    begin
      // Na
      Compare := NaturalOrderCompareString( Item1.Caption, Item2.Caption, True );
    end;
    1:
    begin
      // 일반 문자열 비교
      Compare := CompareStr( Item1.Caption, Item2.Caption );
    end;
    2:
    begin
      // Window API
      CompareResult := CompareString( LOCALE_USER_DEFAULT, 0, PChar(Item1.Caption), Length(Item1.Caption), PChar(Item2.Caption), Length(Item2.Caption) );

      case CompareResult of
        CSTR_LESS_THAN:     Compare := -1;
        CSTR_GREATER_THAN:  Compare := 1;
        CSTR_EQUAL:         Compare := 0;
      end;
    end;
  end;
end;


+ Recent posts