[2012.01.27] 델파이 프로젝트 조건부 컴파일(Conditional compilation) 델파이(Delphi)

Project - Options - Directories/Conditionals - Conditionals - Conditional defines

이전부터 델파이에서 조건부 컴파일(Conditional compilation)을 사용하면서 각 소스마다 {$define TESTMODE} 를 추가하고 삭제하고 하는 것이 번거로웠고 어떻게 프로젝트 전반에 걸쳐 한번에 처리할 방법이 없을까 하고 고민한 적이 있었다.
C 언어에서는 헤더 파일 하나 #include 하고 그 헤더 파일을 수정하면 되었었는데 델파이는 그런 방법이 안되는 것이었다.

이전에 델파이에 그런 것은 없다는 소리를 들어서 그냥저냥 번거로워도 일일이 각 소스마다 $define 부분을 수정해왔었다.

그러다 오늘 문득 이렇게 쓰기 불편한 것이 이상하다는 생각이 들었고 만약 조건부 컴파일 기능이 IDE 에 있다면 어느 구석에 둘까 하는 생각을 해보았다.
답은 Project Options 메뉴...들어가서 보니 Conditionals 가 눈에 띄었다.
그래서 얼씨구나 하고 Conditional defines 항목에 TESTMODE 라고 입력하고 모든 소스의 {$define TESTMODE} 들을 주석처리하고 F9 를 눌렀다.
...안된다.
그래서 F1 키를 누르고 도움말을 읽어보고 도움말 링크를 따라다니고 하면서
-TESTMODE, $TESTMODE, -$TESTMODE, DTESTMODE, -DTESTMODE 등등을 입력하면서 실행해보았지만...역시...

방황의 시간을 거쳐 결국 구글신에 도달하였다. 역시 구글님은 전지하시다.

Delphi Conditional Define 라는 검색어로 검색하니 답이 나왔다.

Project Options 에서 조건부 컴파일 항목을 수정한 후에는 Build All Projects 를 해야 한다.
만약 소스가 수정되었다면 그냥 F9 를 눌러도 적용되지만 소스가 수정되지 않은 경우에는 다시 Build 를 해야 적용이 되는 것이다.
즉, 앞의 경우 Conditional defines 항목에 TESTMODE 로 설정하고 Build All Projects 한 후 Run 하면 적용되는 것이다.


[2012.01.11] Indy10 TIdTCPClient 객체에서 연결 끊어짐 처리. 델파이(Delphi)

Indy9 과 Indy10 의 차이인지 델파이7 과 델파이 XE2(델파이2009 부터...) 의 차이인지 모르겠지만...

연결 끊어짐을 확인하는데 보통 CheckForGracefullyDisconnect, CheckForDisconnect 과 CheckForDataOnSource 을 사용했었다.

Indy9에서 쓰던 방식을 Indy10 에서 수정해서 다음과 같이 사용했었다.

try
    FConnectionToView.IOHandler.CheckForDisconnect(True, True);
    FConnectionToView.IOHandler.CheckForDataOnSource(100);
except
end;
try
    FConnectionToView.Host := ConnectionInfo^.Host;
    FConnectionToView.Port := ConnectionInfo^.Port;
    if (Not FConnectionToView.Connected) then FConnectionToView.Connect;

    // 이런저런 패킷 던지기
except
    // FConnectionToView.Connect 에 대한 예외 처리.
end;

그런데 서버가 켜져있는 상태에서 클라이언트가 접속하여 이런저런 데이터를 주고 받은 후, 서버를 강제 종료시켰다가 다시 켠 뒤 클라이언트에서 위의 동작을 하면 안되는 것이었다.
여튼 아래와 같이 해서 해결하기는 했는데 좀 찜찜하다.

try
    if FConnectionToView.IOHandler <> nil then
    begin
        FConnectionToView.IOHandler.CheckForDisconnect(True, True);
        FConnectionToView.IOHandler.CheckForDataOnSource(100);
    end;
except
    FConnectionToView.IOHandler.Free;    // 핵심은 바로 여기
end;
try
    FConnectionToView.Host := ConnectionInfo^.Host;
    FConnectionToView.Port := ConnectionInfo^.Port;
    if (Not FConnectionToView.Connected) then FConnectionToView.Connect;

    // 이런저런 패킷 던지기
except
    // FConnectionToView.Connect 에 대한 예외 처리.
end;

핵심은 FConnectionToView.IOHandler.Free; 이다.
메모리 누수가 생길까 걱정스러워 프로젝트 소스에

ReportMemoryLeaksOnShutdown := True;

를 추가하고 실행해봤는데 문제가 없었다.


[2011.12.20] Windows COMMTIMEOUTS structure 델파이(Delphi)

최근에 델파이로 필테라호출기 제어 프로그램을 수정한 적이 있다.
그런데 동작이 이상해서 한참 이런저런 삽질하다 시간이 모잘라서 그냥 내보냈다.
아무래도 맘에 걸리고 결국에는 다시 수정작업을 하게 될 듯 하여 자료를 찾아봤다.

원본은 아래의 링크를 참고한다.
http://msdn.microsoft.com/en-us/library/windows/desktop/aa363190(v=vs.85).aspx


멤버는 아래와 같다.

ReadIntervalTimeout
ReadTotalTimeoutMultiplier
ReadTotalTimeoutConstant
WriteTotalTimeoutMultiplier
WriteTotalTimeoutConstant

내 경우 호출기로 송신한 후 호출기가 응답하는 패킷을 읽어야 하는데, 분명 호출기가 있는데 응답 패킷을 읽지 못하고 그냥 나오는 것이었다.

다음과 같은 원인때문일 것으로 추측하는데
첫 번째는 호출기로 송신한 패킷을 호출기가 수신하지 못한 경우이고
두 번째는 호출기가 응답하는 패킷을 수신하지 못한 경우이다.

통신선과 장치에 문제가 없다면 둘다 타임아웃 설정과 관련이 있다.
첫 번째는 WriteTotalTimeoutMultiplier 와 WriteTotalTimeoutConstant 설정 때문이고
두 번째는 ReadIntervalTimeout, ReadTotalTimeoutMultiplier, ReadTotalTimeoutConstant 설정 때문이다.

우선 이야기를 더 진행하기 전에 각 Timeout 들의 내용을 살펴본다.

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

ReadIntervalTimeout

The maximum time allowed to elapse between the arrival of two bytes on the communications line, in milliseconds. During a ReadFile operation, the time period begins when the first byte is received. If the interval between the arrival of any two bytes exceeds this amount, the ReadFile operation is completed and any buffered data is returned. A value of zero indicates that interval time-outs are not used.

A value of MAXDWORD, combined with zero values for both the ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier members, specifies that the read operation is to return immediately with the bytes that have already been received, even if no bytes have been received.

읽기간격타임아웃
도착하는 2 바이트 사이의 최대 시간 간격을 1000분의1초 단위로 설정할 수 있게 한다. ReadFile 동작시, 첫 번째 바이트가 수신되면서 타임아웃 간격이 시작된다. 만약 1 바이트가 수신된 후 지정한 시간이 지나도 다음 1 바이트가 수신되지 않으면 ReadFile 동작은 완료되고 버퍼에 저장된 값이 반환된다. 0 값으로 설정하면 읽기 간격 타임아웃을 사용하지 않는다는 것을 나타낸다.
ReadTotalTimeoutMultiplier 과 ReadTotalTimeoutConstant 을 0으로 설정했을 때, ReadIntervalTimeout를 MAXDWORD 값 (2^32-1 = 4294967295) 으로 설정하면 읽기 동작은 수신한 데이터가 있으면 수신한 데이터를 가지고, 수신한 데이터가 없다면 없는대로 곧바로 리턴한다.

ReadTotalTimeoutMultiplier

The multiplier used to calculate the total time-out period for read operations, in milliseconds. For each read operation, this value is multiplied by the requested number of bytes to be read.

읽기전체타임아웃곱하기계수
읽기동작의 전체 타임아웃 간격을 계산하는데 사용하는 곱하기계수로 단위는 1000분의 1초 이다. 각각의 읽기 동작시, 읽기 요청된 바이트 수에 곱해진다.

ReadTotalTimeoutConstant

A constant used to calculate the total time-out period for read operations, in milliseconds. For each read operation, this value is added to the product of the ReadTotalTimeoutMultiplier member and the requested number of bytes.

A value of zero for both the ReadTotalTimeoutMultiplier and ReadTotalTimeoutConstant members indicates that total time-outs are not used for read operations.

읽기전체타임아웃상수
전체 타임아웃 간격을 계산하는데 사용되는 상수로 단위는 1000분의 1초이다. 각각의 읽기 동작시, 이 값은 읽기 요청된 바이트 수와 ReadTotalTimeoutMultiplier의 곱에 더해진다.
ReadTotalTimeoutMultiplier와 ReadTotalTimeoutConstant을 0으로 설정하면, 읽기 동작시 전체 타임아웃을 사용하지 않는다.

Remarks

If an application sets ReadIntervalTimeout and ReadTotalTimeoutMultiplier to MAXDWORD and sets ReadTotalTimeoutConstant to a value greater than zero and less than MAXDWORD, one of the following occurs when the ReadFile function is called:

  • If there are any bytes in the input buffer, ReadFile returns immediately with the bytes in the buffer.
  • If there are no bytes in the input buffer, ReadFile waits until a byte arrives and then returns immediately.
  • If no bytes arrive within the time specified by ReadTotalTimeoutConstant, ReadFile times out.

주의
애플리케이션에서 ReadIntervalTimeout과 ReadTotalTimeoutMultiplier를  MAXDWORD 값 (2^32-1 = 4294967295) 으로 설정하고 ReadTotalTimeoutConstant 값을 0보다 크고 MAXDWORD 값보다 작게 ( 0 < ReadTotalTimeoutConstant < MAXDWORD ) 설정하면 ReadFile 함수가 호출되었을 때 다음의 경우들 중 하나가 된다.

  • 만약 입력 버퍼에 수신 데이터가 있으면, ReadFile은 즉시 입력 버퍼의 데이터를 반환한다.
  • 만약 입력 버퍼에 수신 데이터가 없으면, ReadFile은 1바이트 수신할 때까지 기다렸다가 수신되면 즉시 입력 버퍼의 데이터를 반환한다.
  • 만약 ReadTotalTimeoutConstant로 지정한 시간 안에 데이터가 수신되지 않으면, ReadFile 는 타임아웃 된다.


끝.
---------------------------------------------------------------------------------------------------------------------

각 타임아웃 값들의 의미를 몰라 많이 헷갈렸는데 현재 대충 정리한 것은 이렇다.
ReadIntervalTimeout 은 하나의 데이터가 수신된 후 다음 데이터가 수신될 때까지의 타임아웃이다.
ReadTotalTimeoutConstant 과 ReadTotalTimeoutMultiplier 는 Read 동작 전체에 걸쳐 적용되는 타임아웃.
그리고 2개의 타임아웃은 각각 동작. ReadIntervalTimeout 이든 ReadTotalTimeout 이든 두 타임아웃 조건 중 어느 것이라도 만족한다면 타임아웃이 발생.
대략 9600 bps 라면 1 바이트 수신하는데 소모되는 시간은 1ms 정도인데 여유를 봐서 10 ms 정도가 최대값이라고 생각되면 ReadIntervalTimeout은 10ms 로 설정.
그리고 호출기가 최대 50 ms 이내에 응답한다고 한다면 ReadTotalTimeoutConstant 은 50 ms 로 설정.
ReadTotalTimeoutMultiplier 는 1 ms ~ 10 ms 사이의 값으로 설정. 

만약 4바이트를 읽기 한다면...타임아웃이 발생하는 경우는

ReadTotalTimeoutConstant +  ReadTotalTimeoutMultiplier * 4 밀리초 동안 4바이트가 수신되지 못했을 경우 수신된 데이터까지 반환하고 타임아웃.

ReadTotalTimeoutConstant +  ReadTotalTimeoutMultiplier * 4 밀리초 전에
첫 번째 바이트가 수신된 후 ReadIntervalTimeout 동안 두 번째 바이트가 수신되지 않으면 수신된 데이터까지 반환하고 타임아웃.
(이 경우 반환되는 값은 첫 번째 바이트 까지일 것이다.)

ReadTotalTimeoutConstant +  ReadTotalTimeoutMultiplier * 4 밀리초 전에
두 번째 바이트가 수신된 후 ReadIntervalTimeout 동안 세 번째 바이트가 수신되지 않으면 수신된 데이터까지 반환하고 타임아웃.
(이 경우 반환되는 값은 두 번째 바이트 까지일 것이다.)

ReadTotalTimeoutConstant +  ReadTotalTimeoutMultiplier * 4 밀리초 전에
세 번째 바이트가 수신된 후 ReadIntervalTimeout 동안 네 번째 바이트가 수신되지 않으면 수신된 데이터까지 반환하고 타임아웃.
(이 경우 반환되는 값은 세 번째 바이트 까지일 것이다.)

이런 식일 것이다.

아! 타임아웃과 직접 관련 있는 내용은 아니지만 추가한다.
간혹 내 애플리케이션에서 뭔가 하느라고 그 사이 수신된 데이터를 받지 못하면 어쩌지 하는 경우가 있는데...
드라이버가 알아서 하드웨어 버퍼에서 열심히 애플리케이션에서 설정한 입력버퍼로 퍼 나르므로 그리 걱정할 필요가 없다.
버퍼만 넉넉하게 잡으면...그런 일은 없겠지만 만약 그 퍼나르는 속도보다 빠르게 들어온다면?
음...우선 Read / Write 동작은 오버헤드가 있다...는 것을 기억하기 바란다.
1 Byte 단위로 읽기 / 쓰기를 하는 것보다는 Byte Block 단위로 읽기 / 쓰기를 하는 것이 부하가 적다는 것이다.
프로토콜과 애플리케이션 구조를 이런 생각을 바탕으로 작성한다.
...뭔 짓을 해도 소용없다면?
.
.
.
...하드웨어의 한계...업그레이드 뿐...(먼산)




2011.12.21.수요일.
필테라 호출기 제어 프로그램의 문제를 알아냈다. 좀 어이없었는데...
읽기 함수를 호출할 때 사용하는 버퍼의 형(Type)이 문제였다.

ReadBuffer: array of Byte;

는 괜찮은데

ReadBuffer: array of Integer;

는 문제를 일으킨다.
...원래 알고 있었던 문제라서 내가 작성한 경우에는 전부 Byte 배열로 선언하고 있었다.
다른 개발자가 만든 프로그램을 수정한 것이라서 함수들 속에 이런 부분들이 좀 있다.
그런데 어떤 함수에서는 Byte 로 되어 있는데 어떤 함수들에서는 Integer 로 되어 있다. 이거 뭔가...;;
...여튼 수정하고 나니 잘 된다.
단 ReadTotalConstant 를 25ms 로 했더니 좀 놓치는 것이 있어서 고민하다가 50 ms 로 바꾸고 나니 놓치는 응답이 없었다.
반응성도 나쁘지 않아서 굳이 25ms 에서 50ms 사이의 값을 시행착오로 최적화 시킬 필요가 없다는 생각이 들어 빌드하고 릴리즈 한 뒤 봉인했다.


[2011.12.13] 이벤트 구동 방식의 프로그래밍 - 이벤트 연결 테스트 델파이(Delphi)

정리는 아직 못하고 테스트만 했다. 뭐라고 설명해야할지 잘 몰라서이다. ;;


program Project1;

uses
    FastMM4, FastMM4Messages, SyncObjs,
    Forms,
    uMain in 'uMain.pas' {fmMain},
    uTest1 in 'uTest1.pas';

{$R *.res}

begin
    //RegisterExpectedMemoryLeak(TCriticalSection ,1);  // TCriticalSection 이 SyncObjs 안에 있음.

    Application.Initialize;
    Application.CreateForm(TfmMain, fmMain);
    Application.Run;
end.



unit uMain;

interface

uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
    Dialogs, StdCtrls, uTest1;

type
    TfmMain = class(TForm)
        Memo1: TMemo;
        Button1: TButton;
        Button2: TButton;
        Button3: TButton;
        Button4: TButton;
        procedure FormCreate(Sender: TObject);
        procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    private
        { Private declarations }
        FTest1List: array of TTest1;
        procedure DoTest(Sender: TObject);
        procedure DoTestButtonClick(Sender: TObject);
    public
        { Public declarations }
    end;

var
    fmMain: TfmMain;

implementation

{$R *.dfm}

procedure TfmMain.FormCreate(Sender: TObject);
var
    iTmp1: Integer;
    iLoop1: Integer;
begin
    Left := 0;
    Top := 0;

    iTmp1 := 3;
    SetLength(FTest1List, iTmp1);
    for iLoop1 := 0 to iTmp1 - 1 do
    begin
        FTest1List[iLoop1] := TTest1.Create;
        FTest1List[iLoop1].TestIndex := iLoop1 + 1;
        FTest1List[iLoop1].OnTest := DoTest;
    end;
end;

procedure TfmMain.FormDestroy(Sender: TObject);
var
  iTmp1: Integer;
  iLoop1: Integer;
begin
  for iLoop1 := Length(FTest1List) - 1 downto 0 do
  begin
    FTest1List[iLoop1].Free;
    FTest1List[iLoop1] := nil;
  end;
end;

procedure TfmMain.Button1Click(Sender: TObject);
begin
    DoTestButtonClick(Sender);
end;

procedure TfmMain.DoTestButtonClick(Sender: TObject);
begin
    try
        if Assigned(FTest1List[TButton(Sender).tag]) then
        begin
            FTest1List[TButton(Sender).tag].Test;
        end;
    except
        on E: Exception do
        begin
            Memo1.Lines.Add('TfmMain.DoTestButtonClick Exception ' + E.Message);
        end;
    end;
end;

procedure TfmMain.DoTest(Sender: TObject);
begin
    if (Sender is TTest1) then
    begin
        Memo1.Lines.Add('TTest1, TestIndex is ' + IntToStr(TTest1(Sender).TestIndex));
    end;
end;

end.



unit uTest1;

interface

uses
    SysUtils, Classes, Dialogs
    ;

type
    TTest1 = class
    private
        FOnTest: TNotifyEvent;
    public
        TestIndex: Integer;
        procedure Test;
        property OnTest: TNotifyEvent read FOnTest write FOnTest;
    end;

implementation

{ TTest1 }

procedure TTest1.Test;
begin
    ShowMessage('TTest1, TestIndex is ' + IntToStr(TestIndex));

    if Assigned(FOnTest) then
    begin
        FOnTest(Self);
    end;
end;

end.


TTest1 객체를 여러 개 생성한 후 TTest1 의 테스트 이벤트 OnTest 에 이 TTest1 의 객체를 생성하고 사용하는 fmMain 의 DoTest 프로시져를 연결한 경우 어떤 TTest1 의 객체에서라도 OnTest 이벤트가 발생하면 DoTest 프로시져가 실행되는 것이다.
보면 알겠지만 TTest1 의 이벤트와 fmMain의 프로시져를 연결한 것이라 TTest1 이 fmMain을 직접 알고 있지 않아도, TTest1 이 있는 pas 파일의 Uses 에 uMain 이 없어도 된다는 것이다. 이래야 나중에 fmMain 이 바뀌거나 할 때도 TTest1 의 소스는 수정하지 않을 수 있다. 특히나 fmMain 에 TMemo 객체 하나 올려놓고 로그를 찍는 경우라면 더 말할 나위 없을 것이다. TMemo 객체 이름이 바뀌거나 대상이 바뀌거나 하면 이곳저곳 수정하기 바쁜데 수정할 곳을 fmMain 안에 모아둘 수 있다.

...이걸 어디에 써먹으려고 한 것이가하면...Indy 의 OnConnect 와 OnDisconnect 이벤트를 이벤트 방식으로 처리하기 위해서 시험한 것이다.


[2011.12.06] 근황?

어제부터 묘하다 했다.
12월 5일 오전에 전체회의가 일정에 잡혀있었다.
나는 모르고 안 나간 상태.
그러니...

아침에 불려갔다.
인사를 하란다...과장인데 그러면 쓰냐고...그런데 누구한테 해야하지...
...목소리 크게 하고 소리치라는 이야긴가...
힘들다.

그리고는 연차를 낸 이유를 알려달라고 한다.
...뭔가 해명하는 분위기였다. 허허...
장모님 대장 내시경 검사, 남은 하나는 가족 회의라고 이야기했다.
전자는 진짜고 뒤는 둘러댄 것이지만...

여튼 슬슬 힘들다. 군대처럼 하고 싶은가본데...
해병대 출신 프로그래머를 뽑으라고 말하고 나는 나가고 싶다.


1 2 3 4 5 6 7 8 9