::: 델파이 Tip&Trick :::

델파이 Tip&Trick 성격에 맞지 않는 광고,비방,질문의 글은 즉시 삭제하며
내용을 복사하여 사용할 경우 반드시 이곳(http://www.howto.pe.kr)을 출처로 명시하여 주세요


Category

  김영대(2003-11-18 19:20:40, Hit : 11080, Vote : 884
 IOCP(I/O Completion Port) class

{ *********************************************************************** }
{                                                                         }
{ Author : qiusonglin                                                     }
{ Date   : 2003-07-06                                                     }
{ EMail  : qiusonglin@hotmail.com                                         }
{                                                                         }
{ *********************************************************************** }

unit IOCPComp;

interface

uses
  Windows, Messages, WinSock2, Classes, SysUtils;

const
  MAX_BUFSIZE = 4096;
  WM_CLIENTSOCKET = WM_USER + $2000;

type
  TCMSocketMessage = packed record
    Msg: Cardinal;
    Socket: TSocket;
    SelectEvent: Word;
    SelectError: Word;
    Result: Longint;
  end;

  TSocketEvent = (seInitIOPort, seInitSocket,  seConnect, seDisconnect, seListen, seAccept, seWrite, seRead);
  TErrorEvent = (eeGeneral, eeSend, eeReceive, eeConnect, eeDisconnect, eeAccept);

  PPerHandleData = ^TPerHandleData;
  TPerHandleData = packed record
    Overlapped: OVERLAPPED;
    wsaBuffer: WSABUF;
    Event: TSocketEvent;
    IsUse: Boolean;
    Buffer: array [0..MAX_BUFSIZE - 1] of Char;
  end;

  PBlock = ^TBlock;
  TBlock = packed record
    Data: TPerHandleData;
    IsUse: Boolean;
  end;

  EMemoryBuffer = class(Exception);
  ESocketError = class(Exception);

  TCustomSocket = class;
  TServerClientSocket = class;

  TOnDataEvent = function(Socket: TCustomSocket; Data: Pointer; Count: Integer): Integer of object;
  TSocketErrorEvent = procedure(Socket: TCustomSocket; ErrorEvent: TErrorEvent; var ErrCode: Integer) of object;
  TSocketEventEvent = procedure(Socket: TCustomSocket; SocketEvent: TSocketEvent) of object;


  TMemoryBuffer = class
  private
    FList: TList;
    FSocket: TCustomSocket;
    function GetCount: Integer;
    function GetBlock(const Index: Integer): PBlock;
  protected
    property Count: Integer read GetCount;
    property Blocks[const Index: Integer]: PBlock read GetBlock;
  public
    constructor Create(ASocket: TCustomSocket); overload;
    constructor Create(ASocket: TCustomSocket; BlockCount: Integer); overload;
    destructor Destroy; override;
    function AllocBlock: PBlock;
    procedure RemoveBlock(Block: PBlock);
  end;

  TCustomSocket = class
  private
    FData: Pointer;
    FSocket: TSocket;
    FActive: Boolean;
    FInitLock: Boolean;
    FLock: TRTLCriticalSection;
    FOnRead: TOnDataEvent;
    FOnErrorEvent: TSocketErrorEvent;
    FOnEventEvent: TSocketEventEvent;
    function GetRemoteAddress: string;
    function GetRemoteHost: string;
    procedure DoRead(Data: Pointer; Count: Integer);
  protected
    procedure SetActive(Value: Boolean); virtual; abstract;
    procedure Event(SocketEvent: TSocketEvent); virtual;
    procedure Error(ErrorEvent: TErrorEvent; var ErrCode: Integer); virtual;
    property OnRead: TOnDataEvent read FOnRead write FOnRead;
    property OnErrorEvent: TSocketErrorEvent read FOnErrorEvent write
FOnErrorEvent;
    property OnEventEvent: TSocketEventEvent read FOnEventEvent write FOnEventEvent;
  public
    constructor Create(ASocket: TSocket);
    destructor Destroy; override;
    procedure Close;
    procedure Open;
    procedure Lock;
    procedure UnLock;
    function Read(var Buf; Count: Integer): Integer; virtual;
    function Write(var Buf; Count: Integer): Integer; virtual;
    property SocketHandle: TSocket read FSocket;
    property Data: Pointer read FData write FData;
    property Active: Boolean read FActive write SetActive;
    property RemoteHost: string read GetRemoteHost;
    property RemoteAddress: string read GetRemoteAddress;
  end;

  TCustomerServerSocket = class(TCustomSocket)
  private
    FOnClientRead: TOnDataEvent;
    FOnClientError: TSocketErrorEvent;
    FOnClientEvent: TSocketEventEvent;
  protected
    function DoClientRead(ASocket: TCustomSocket; AData: Pointer; ACount: Integer): Integer;
    procedure ClientSocketError(ASocket: TCustomSocket;
      ErrorEvent: TErrorEvent; var ErrCode: Integer);
    procedure ClientSocketEvent(ASocket: TCustomSocket; SocketEvent: TSocketEvent);
  public
    property OnClientRead: TOnDataEvent read FOnClientRead write FOnClientRead;
    property OnClientError: TSocketErrorEvent read FOnClientError write FOnClientError;
    property OnClientEvent: TSocketEventEvent read FOnClientEvent write FOnClientEvent;
    property OnErrorEvent;
    property OnEventEvent;
  end;

  TGetSocketEvent = procedure(Socket: TSocket; var ClientSocket: TServerClientSocket) of object;
  TServerSocket = class(TCustomerServerSocket)
  private
    FPort: Integer;
    FAddr: TSockAddr;
    FAcceptThread: TThread;
    FCompletionPort: THandle;
    FClients: TList;
    FThreads: TList;
    FHandle: THandle;
    FBuffer: TMemoryBuffer;
    FOnGetSocket: TGetSocketEvent;
    procedure SetPort(Value: Integer);
    procedure RegisterClient(ASocket: TCustomSocket);
    procedure RemoveClient(ASocket: TCustomSocket);
    procedure WMClientClose(var Message: TCMSocketMessage); message WM_CLIENTSOCKET;
    procedure WndProc(var Message: TMessage);
    function FindClientSocket(ASocket: TSocket): TCustomSocket;
    function GetClientCount: Integer;
    function GetClients(const Index: Integer): TServerClientSocket;
  protected
    procedure InternalOpen;
    procedure InternalClose;
    procedure SetActive(Value: Boolean); override;
    property CompletionPort: THandle read FCompletionPort;
    function IsAccept(Socket: TSocket): Boolean; virtual;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Accept(ASocket: TSocket; ACompletionPort: THandle);
    property Handle: THandle read FHandle;
    property Port: Integer read FPort write SetPort;
    property ClientCount: Integer read GetClientCount;
    property Clients[const Index: Integer]: TServerClientSocket read GetClients;
    property OnGetSocket: TGetSocketEvent read FOnGetSocket write FOnGetSocket;
  end;

  TServerClientSocket = class(TCustomSocket)
  private
    FBlock: TList;
    FBuffer: TMemoryBuffer;
    FServerSocket: TServerSocket;
    function AllocBlock: PBlock;
    function PrepareRecv(Block: PBlock = nil): Boolean;
    function WorkBlock(var Block: PBlock; Transfered: DWORD): DWORD;
  protected
    procedure SetActive(Value: Boolean); override;
  public
    constructor Create(AServerSocket: TServerSocket; ASocket: TSocket);
    destructor Destroy; override;
    function Read(var Buf; Count: Integer): Integer; override;
    function Write(var Buf; Count: Integer): Integer; override;
  end;

  TSocketThread = class(TThread)
  private
    FServer: TServerSocket;
  public
    constructor Create(AServer: TServerSocket);
  end;

  TAcceptThread = class(TSocketThread)
  protected
    procedure Execute; override;
  end;

  TWorkerThread = class(TSocketThread)
  protected
    procedure Execute; override;
  end;

implementation

uses RTLConsts;

const
  SHUTDOWN_FLAG = $FFFFFFFF;
  BlockSize: Word = SizeOf(TBlock);

var
  WSData: TWSAData;

{ TMemoryBuffer }

constructor TMemoryBuffer.Create(ASocket: TCustomSocket);
begin
  Create(ASocket, 200);
end;

constructor TMemoryBuffer.Create(ASocket: TCustomSocket; BlockCount:
Integer);
var
  I: Integer;
  P: PBlock;
begin
  inherited Create;
  FSocket := ASocket;
  FList := TList.Create;
  for I := 0 to BlockCount - 1 do
  begin
    New(P);
    FillChar(P^, BlockSize, 0);
    FList.Add(P);
  end;
end;

destructor TMemoryBuffer.Destroy;
var
  I: Integer;
begin
  for I := 0 to FList.Count - 1 do
    FreeMem(FList[I]);
  FList.Free;
  inherited Destroy;
end;

function TMemoryBuffer.AllocBlock: PBlock;
var
  I: Integer;
begin
  FSocket.Lock;
  try
    Result := nil;
    for I := 0 to FList.Count - 1 do
    begin
      Result := FList[I];
      if not Result.IsUse then
        break;
    end;
    if not Assigned(Result) or Result.IsUse then
    begin
      New(Result);
      FList.Add(Result);
    end;
    FillChar(Result^.Data, SizeOf(Result^.Data), 0);
    Result^.IsUse := True;
  finally
    FSocket.UnLock;
  end;
end;

procedure TMemoryBuffer.RemoveBlock(Block: PBlock);
begin
  FSocket.Lock;
  try
    Block.IsUse := False;
  finally
    FSocket.UnLock;
  end;
end;

function TMemoryBuffer.GetCount: Integer;
begin
  Result := FList.Count;
end;

function TMemoryBuffer.GetBlock(const Index: Integer): PBlock;
begin
  if (Index >= Count) or (Index <= -1) then
    raise EMemoryBuffer.CreateFmt(SListIndexError, [Index])
  else
    Result := FList[Index];
end;

procedure CheckError(ResultCode: Integer; const OP: string);
var
  ErrCode: Integer;
begin
  if ResultCode <> 0 then
  begin
    ErrCode := WSAGetLastError;
    if (ErrCode <> WSAEWOULDBLOCK) or (ErrCode <> ERROR_IO_PENDING) then
      raise ESocketError.CreateFmt(SWindowsSocketError,
        [SysErrorMessage(ErrCode), ErrCode, Op]);
  end;
end;

{ TCustomSocket }

constructor TCustomSocket.Create(ASocket: TSocket);
begin
  inherited Create;
  FInitLock := False;
  if WSAStartup($0202, WSData) <> 0 then
    raise ESocketError.Create(SysErrorMessage(GetLastError));
  FSocket := ASocket;
  FActive := FSocket <> INVALID_SOCKET;
end;

destructor TCustomSocket.Destroy;
begin
  SetActive(False);
  WSACleanup;
  if FInitLock then
    DeleteCriticalSection(FLock);
  inherited Destroy;
end;

procedure TCustomSocket.Lock;
begin
  if not FInitLock then
  begin
    InitializeCriticalSection(FLock);
    FInitLock := True;
  end;
  EnterCriticalSection(FLock);
end;

procedure TCustomSocket.UnLock;
begin
  if FInitLock then
    LeaveCriticalSection(FLock);
end;

procedure TCustomSocket.Close;
begin
  SetActive(False);
end;

procedure TCustomSocket.Open;
begin
  SetActive(True);
end;

procedure TCustomSocket.DoRead(Data: Pointer; Count: Integer);
begin
  if Assigned(FOnRead) then
    FOnRead(Self, Data, Count);
end;

procedure TCustomSocket.Error(ErrorEvent: TErrorEvent; var ErrCode:
Integer);
begin
  if Assigned(FOnErrorEvent) then
    FOnErrorEvent(Self, ErrorEvent, ErrCode);
end;

procedure TCustomSocket.Event(SocketEvent: TSocketEvent);
begin
  if Assigned(FOnEventEvent) then
    FOnEventEvent(Self, SocketEvent);
end;

function TCustomSocket.GetRemoteAddress: string;
var
  SockAddrIn: TSockAddrIn;
  Size: Integer;
begin
  Result := '';
  if not FActive then Exit;
  Size := SizeOf(SockAddrIn);
  CheckError(getpeername(FSocket, SockAddrIn, Size), 'getpeername');
  Result := inet_ntoa(SockAddrIn.sin_addr);
end;

function TCustomSocket.GetRemoteHost: string;
var
  SockAddrIn: TSockAddrIn;
  Size: Integer;
  HostEnt: PHostEnt;
begin
  Result := '';
  if not FActive then Exit;
  Size := SizeOf(SockAddrIn);
  CheckError(getpeername(FSocket, SockAddrIn, Size), 'getpeername');
  HostEnt := gethostbyaddr(@SockAddrIn.sin_addr.s_addr, 4, PF_INET);
  if HostEnt <> nil then Result := HostEnt.h_name;
end;

function TCustomSocket.Read(var Buf; Count: Integer): Integer;
begin
  raise ESocketError.Create('Error');
end;

function TCustomSocket.Write(var Buf; Count: Integer): Integer;
begin
  raise ESocketError.Create('Error');
end;

{ TCustomerServerSocket }

function TCustomerServerSocket.DoClientRead(ASocket: TCustomSocket;
  AData: Pointer; ACount: Integer): Integer;
begin
  if not Assigned(FOnClientRead) then
    Result := 0 else
    Result := FOnClientRead(ASocket, AData, ACount);
end;

procedure TCustomerServerSocket.ClientSocketError(ASocket: TCustomSocket;
  ErrorEvent: TErrorEvent; var ErrCode: Integer);
begin
  if Assigned(FOnClientError) then
    FOnClientError(ASocket, ErrorEvent, ErrCode);
end;

procedure TCustomerServerSocket.ClientSocketEvent(ASocket: TCustomSocket;
  SocketEvent: TSocketEvent);
begin
  if Assigned(FOnClientEvent) then
    FOnClientEvent(ASocket, SocketEvent);
end;

{ TServerSocket }

procedure TServerSocket.Accept(ASocket: TSocket; ACompletionPort: THandle);
var
  Addr: TSockAddrIn;
  AddrLen, Ret, ErrCode: Integer;
  ClientWinSocket: TSocket;
  ClientSocket: TServerClientSocket;
begin
  AddrLen := SizeOf(Addr);
  ClientWinSocket := WinSock2.accept(ASocket, Addr, AddrLen);
  if ClientWinSocket <> INVALID_SOCKET then
  begin
    if not Active and not IsAccept(ClientWinSocket) then
    begin
      closesocket(ClientWinSocket);
      Exit;
    end;
    try
      Event(seAccept);
      ClientSocket := nil;
      if Assigned(FOnGetSocket) then
        FOnGetSocket(ClientWinSocket, ClientSocket);
      if not Assigned(ClientSocket) then
        ClientSocket := TServerClientSocket.Create(Self, ClientWinSocket);
    except
      closesocket(ClientWinSocket);
      ErrCode := GetLastError;
      Error(eeAccept, ErrCode);
      Exit;
    end;
    Ret := CreateIoCompletionPort(ClientWinSocket, ACompletionPort, DWORD(ClientSocket), 0);
    if Ret = 0 then
      ClientSocket.Free;
  end;
end;

constructor TServerSocket.Create;
begin
  inherited Create(INVALID_SOCKET);
  FBuffer := TMemoryBuffer.Create(Self);
  FClients := TList.Create;
  FThreads := TList.Create;

  FPort := 211;
  FAcceptThread := nil;
  FCompletionPort := 0;
  IsMultiThread := True;
  FHandle := Classes.AllocateHWnd(WndProc);
end;

destructor TServerSocket.Destroy;
begin
  SetActive(False);
  FThreads.Free;
  FClients.Free;
  Classes.DeallocateHWnd(FHandle);
  FBuffer.Free;
  inherited Destroy;
end;

function TServerSocket.FindClientSocket(ASocket: TSocket): TCustomSocket;
var
  I: Integer;
begin
  Lock;
  try
    for I := 0 to FClients.Count - 1 do
    begin
      Result := FClients[I];
      if ASocket = Result.SocketHandle then Exit;
    end;
    Result := nil;
  finally
    UnLock;
  end;
end;

function TServerSocket.GetClientCount: Integer;
begin
  Result := FClients.Count;
end;

function TServerSocket.GetClients(const Index: Integer):
TServerClientSocket;
begin
  Result := FClients[Index];
end;

procedure TServerSocket.InternalClose;

  procedure CloseObject(var Handle: THandle);
  begin
    if Handle <> 0 then
    begin
      CloseHandle(Handle);
      Handle := 0;
    end;
  end;

var
  I: Integer;
  Thread: TThread;
begin
  Lock;
  try
    while FClients.Count > 0 do
      TObject(FClients.Last).Free;
    FClients.Clear;

    for I := FThreads.Count - 1 downto 0 do
    begin
      Thread := FThreads[I];
      PostQueuedCompletionStatus(FCompletionPort, 0, 0, Pointer(SHUTDOWN_FLAG));
      Thread.Terminate;
    end;
    FThreads.Clear;

    if FSocket <> INVALID_SOCKET then
    begin
      Event(seDisconnect);
      closesocket(FSocket);
      FSocket := INVALID_SOCKET;
    end;
    FAcceptThread.Terminate;
    CloseObject(FCompletionPort);
  finally
    UnLock;
  end;
end;

procedure TServerSocket.InternalOpen;
var
  I: Integer;
  Thread: TThread;
  SystemInfo: TSystemInfo;
begin
  Lock;
  try
    try
      FCompletionPort := CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
      if FCompletionPort = 0 then
        raise ESocketError.Create(SysErrorMessage(GetLastError));

      Event(seInitIOPort);
      GetSystemInfo(SystemInfo);
      for I := 0 to SystemInfo.dwNumberOfProcessors * 2 - 1 do
      begin
        Thread := TWorkerThread.Create(Self);
        FThreads.Add(Thread);
      end;

      FSocket := WSASocket(PF_INET, SOCK_STREAM, 0, nil, 0, WSA_FLAG_OVERLAPPED);
      if FSocket = INVALID_SOCKET then
        raise ESocketError.Create(SysErrorMessage(GetLastError));
      Event(seInitSocket);

      FillChar(FAddr, SizeOf(FAddr), 0);
      FAddr.sin_family := AF_INET;
      FAddr.sin_port := htons(FPort);
      FAddr.sin_addr.S_addr := INADDR_ANY;
      CheckError(bind(FSocket, @FAddr, SizeOf(FAddr)), 'bind');

      Event(seListen);
      CheckError(listen(FSocket, SOMAXCONN), 'listen');
      FAcceptThread := TAcceptThread.Create(Self);
    except
      InternalClose;
      raise;
    end;
  finally
    UnLock;
  end;
end;

function TServerSocket.IsAccept(Socket: TSocket): Boolean;
begin
  Result := True;
end;

procedure TServerSocket.RegisterClient(ASocket: TCustomSocket);
begin
  Lock;
  try
    if FClients.IndexOf(ASocket) = -1 then
    begin
      FClients.Add(ASocket);
      WSAAsyncSelect(ASocket.SocketHandle, FHandle, WM_CLIENTSOCKET, FD_CLOSE);
    end;
  finally
    UnLock;
  end;
end;

procedure TServerSocket.RemoveClient(ASocket: TCustomSocket);
var
  Index: Integer;
begin
  Lock;
  try
    Index := FClients.IndexOf(ASOcket);
    if Index <> -1 then
      FClients.Delete(Index);
  finally
    UnLock;
  end;
end;

procedure TServerSocket.SetActive(Value: Boolean);
begin
  if FActive = Value then Exit;
  if Value then
    InternalOpen
  else
    InternalClose;
  FActive := Value;
end;

procedure TServerSocket.SetPort(Value: Integer);
begin
  if Active then
    raise ESocketError.Create('Cann''t change port');
  FPort := Value;
end;

procedure TServerSocket.WMClientClose(var Message: TCMSocketMessage);
var
  ClientSocket: TCustomSocket;
begin
  ClientSocket := FindClientSocket(Message.Socket);
  if Assigned(ClientSocket) then
    ClientSocket.Free;
end;

procedure TServerSocket.WndProc(var Message: TMessage);
begin
  try
    Dispatch(Message);
  except
    if Assigned(ApplicationHandleException) then
      ApplicationHandleException(Self);
  end;
end;

{ TServerClientSocket }

constructor TServerClientSocket.Create(AServerSocket: TServerSocket;
  ASocket: TSocket);
begin
  inherited Create(ASocket);
  FServerSocket := AServerSocket;
  FBuffer := FServerSocket.FBuffer;
  FBlock := TList.Create;
  FServerSocket.RegisterClient(Self);
  FOnRead := FServerSocket.OnClientRead;
  OnErrorEvent := FServerSocket.ClientSocketError;
  OnEventEvent := FServerSocket.ClientSocketEvent;
  PrepareRecv;
  Event(seConnect);
end;

destructor TServerClientSocket.Destroy;
var
  I: Integer;
begin
  FServerSocket.RemoveClient(Self);
  for I := FBlock.Count - 1 downto 0 do
    FBuffer.RemoveBlock(FBlock[I]);
  FBlock.Free;
  inherited Destroy;
end;

procedure TServerClientSocket.SetActive(Value: Boolean);
var
  Linger: TLinger;
begin
  if FActive = Value then Exit;
  if not Value then
  begin
    if FSocket <> INVALID_SOCKET then
    begin
      Event(seDisconnect);
      FillChar(Linger, SizeOf(Linger), 0);
      setsockopt(FSocket, SOL_SOCKET, SO_LINGER, @Linger, Sizeof(Linger));
      closesocket(FSocket);
      FSocket := INVALID_SOCKET;
    end;
  end else
    raise ESocketError.Create('not support connect.');
  FActive := Value;
end;

function TServerClientSocket.AllocBlock: PBlock;
var
  I: Integer;
begin
  for I := 0 to FBlock.Count - 1 do
  begin
    Result := FBlock[I];
    if not Result.Data.IsUse then
    begin
      Result.Data.IsUse := True;
      Exit;
    end;
  end;
  Result := FBuffer.AllocBlock;
  FBlock.Add(Result);
  Result.Data.IsUse := True;
end;

function TServerClientSocket.Read(var Buf; Count: Integer): Integer;
begin
  { In OnRead }
  raise ESocketError.Create('Read error.');
end;

function TServerClientSocket.Write(var Buf; Count: Integer): Integer;
var
  Block: PBlock;
  ErrCode: Integer;
  Flags, BytesSend: Cardinal;
begin
  Result := Count;
  if Result = 0 then Exit;
  Block := AllocBlock;
  with Block^.Data do
  begin
    Flags := 0;
    Event := seWrite;
    wsaBuffer.buf := @Buf;
    wsaBuffer.len := Result;
    if SOCKET_ERROR = WSASend(FSocket, @wsaBuffer, 1, BytesSend, Flags, @Overlapped, nil) then
    begin
      ErrCode := WSAGetLastError;
      if ErrCode <> ERROR_IO_PENDING then
      begin
        Result := SOCKET_ERROR;
        Error(eeSend, ErrCode);
      end;
    end;
  end;
end;

function TServerClientSocket.PrepareRecv(Block: PBlock = nil): Boolean;
var
  ErrCode: Integer;
  Flags, Transfer: Cardinal;
begin
  if not Assigned(Block) then
    Block := AllocBlock;
  with Block^.Data do
  begin
    Flags := 0;
    Event := seRead;
    FillChar(Buffer, SizeOf(Buffer), 0);
    FillChar(Overlapped, SizeOf(Overlapped), 0);
    wsaBuffer.buf := Buffer;
    wsaBuffer.len := MAX_BUFSIZE;
    Result := SOCKET_ERROR <> WSARecv(FSocket, @wsaBuffer, 1, Transfer, Flags, @Overlapped, nil);
    if not Result then
    begin
      ErrCode := WSAGetLastError;
      Result := ErrCode = ERROR_IO_PENDING;
      if not Result then
      begin
        Block.Data.IsUse := False;
        Error(eeReceive, ErrCode);
      end;
    end;
  end;
end;

const
  RESPONSE_UNKNOWN = $0001;
  RESPONSE_SUCCESS = $0002;
  RESPONSE_FAIL = $FFFF;

function TServerClientSocket.WorkBlock(var Block: PBlock; Transfered: DWORD): DWORD;
var
  ErrCode: Integer;
  Flag, BytesSend: Cardinal;
begin
  Result := RESPONSE_SUCCESS;
  with Block^.Data do
  try
    case Block^.Data.Event of
      seRead:
      begin
        Self.Event(seRead);
        DoRead(@Buffer, Transfered);
        if not PrepareRecv(Block) then
          Result := RESPONSE_FAIL;
      end;
      seWrite:
      begin
        Self.Event(seWrite);
        Dec(wsaBuffer.len, Transfered);
        if wsaBuffer.len <= 0 then
        begin
          { send over, Block return buffer }
          Block.Data.IsUse := False;
          Block := nil;
        end else
        begin
          { goon send }
          Flag := 0;
          Inc(wsaBuffer.buf, Transfered);
          FillChar(Overlapped, SizeOf(Overlapped), 0);
          if SOCKET_ERROR = WSASend(FSocket, @wsaBuffer, 1, BytesSend, Flag, @Overlapped, nil) then
          begin
            ErrCode := WSAGetLastError;
            if ErrCode <> ERROR_IO_PENDING then
              Error(eeSend, ErrCode);
          end;
        end;
      end;
    end;
  except
    Result := RESPONSE_FAIL;
  end;
end;

{ TSocketThread }

constructor TSocketThread.Create(AServer: TServerSocket);
begin
  FServer := AServer;
  inherited Create(False);
  FreeOnTerminate := True;
end;

{ TAcceptThread }

procedure TAcceptThread.Execute;
begin
  with FServer do
    while not Terminated and Active do
      Accept(SocketHandle, CompletionPort);
end;

{ TWorkerThread }

procedure TWorkerThread.Execute;
var
  Block: PBlock;
  Transfered: DWORD;
  ClientSocket: TServerClientSocket;
begin
  while FServer.Active do
  begin
    Block := nil;
    Transfered := 0;
    ClientSocket := nil;
    if not GetQueuedCompletionStatus(FServer.CompletionPort, Transfered, DWORD(ClientSocket), POverlapped(Block), INFINITE) then
    begin
      if Assigned(ClientSocket) then
        FreeAndNil(ClientSocket);
      Continue;
    end;

    { client disconnect or I/O fail }
    if Transfered = 0 then
    begin
      FreeAndNil(ClientSocket);
      Continue;
    end;
    { Notify showdown }
    if Cardinal(Block) = SHUTDOWN_FLAG then
      break;
    if not FServer.Active then break;

    case ClientSocket.WorkBlock(Block, Transfered) of
      RESPONSE_UNKNOWN:
        { reserve }
        FreeAndNil(ClientSocket);
      RESPONSE_FAIL:
        FreeAndNil(ClientSocket);
    end;
  end;
end;

end.


// 예제 서버
//ServerDpr.dpr
program ServerDpr;

uses
  Forms,
  ServerMainUnit in 'ServerMainUnit.pas',
  IOCPComp in 'IOCPComp.pas',
  CommonUnit in 'CommonUnit.pas';

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TServerMainForm, ServerMainForm);
  Application.Run;
end.
////////////////////////////////////////////////////////////////////////////
//ServerMainUnit.dfm
object ServerMainForm: TServerMainForm
  Left = 192
  Top = 107
  Width = 497
  Height = 291
  Caption = 'ServerMainForm'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -12
  Font.Name = 'Courier New'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 15
  object Label1: TLabel
    Left = 32
    Top = 8
    Width = 49
    Height = 15
    Caption = 'Port(&P)'
    FocusControl = PortEdit
  end
  object PortEdit: TEdit
    Left = 32
    Top = 28
    Width = 161
    Height = 23
    TabOrder = 0
    Text = '211'
  end
  object ActiveButton: TButton
    Left = 32
    Top = 48
    Width = 75
    Height = 25
    Caption = 'Start'
    TabOrder = 1
    OnClick = ActiveButtonClick
  end
  object MsgMemo: TMemo
    Left = 280
    Top = 8
    Width = 193
    Height = 241
    Lines.Strings = (
      'MsgMemo')
    ScrollBars = ssVertical
    TabOrder = 2
  end
  object Button1: TButton
    Left = 32
    Top = 120
    Width = 161
    Height = 25
    Caption = 'SendMsgToClient'
    TabOrder = 3
    OnClick = Button1Click
  end
  object ClientMsgEdit: TEdit
    Left = 32
    Top = 96
    Width = 161
    Height = 23
    TabOrder = 4
    Text = 'ClientMsgEdit'
  end
  object GroupBox1: TGroupBox
    Left = 24
    Top = 160
    Width = 249
    Height = 89
    Caption = 'Result'
    TabOrder = 5
    object Label3: TLabel
      Left = 24
      Top = 56
      Width = 84
      Height = 15
      Caption = 'Packet Count'
      FocusControl = CountText
    end
    object Label6: TLabel
      Left = 38
      Top = 24
      Width = 70
      Height = 15
      Caption = 'Connection'
      FocusControl = ConnectionCountText
    end
    object CountText: TStaticText
      Left = 112
      Top = 56
      Width = 97
      Height = 16
      AutoSize = False
      TabOrder = 0
    end
    object ConnectionCountText: TStaticText
      Left = 112
      Top = 24
      Width = 97
      Height = 16
      AutoSize = False
      TabOrder = 1
    end
  end
  object Timer1: TTimer
    Interval = 2000
    OnTimer = Timer1Timer
    Left = 216
    Top = 8
  end
end

////////////////////////////////////////////////////////////////////////////
//ServerMainUnit.pas
unit ServerMainUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, IOCPComp, SyncObjs, StdCtrls, ExtCtrls;

type
  TServerMainForm = class(TForm)
    PortEdit: TEdit;
    ActiveButton: TButton;
    Label1: TLabel;
    MsgMemo: TMemo;
    Button1: TButton;
    ClientMsgEdit: TEdit;
    GroupBox1: TGroupBox;
    Label3: TLabel;
    Label6: TLabel;
    CountText: TStaticText;
    ConnectionCountText: TStaticText;
    Timer1: TTimer;
    procedure ActiveButtonClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    FRequestCount: Integer;
    FLock: TCriticalSection;
    FServerSocket: TServerSocket;
    FServerActive: Boolean;
    function OnClientRead(ASocket: TCustomSocket; AData: Pointer;
      ACount: Integer): Integer;
    procedure OnClientError(ASocket: TCustomSocket; ErrorEvent: TErrorEvent;
      var ErrCode: Integer);
    procedure OnClientEvent(ASocket: TCustomSocket; SocketEvent: TSocketEvent);
    procedure SetServerActive(const Value: Boolean);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property ServerActive: Boolean read FServerActive write SetServerActive;
  end;

var
  ServerMainForm: TServerMainForm;

implementation

uses CommonUnit, TypInfo;
{$R *.dfm}

{ TServerMainForm }

constructor TServerMainForm.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FLock := TCriticalSection.Create;
  FServerSocket := TServerSocket.Create;
  FServerSocket.OnClientRead := OnClientRead;
  FServerSocket.OnClientError := OnClientError;
  FServerSocket.OnClientEvent := OnClientEvent;
  FServerSocket.OnErrorEvent := OnClientError;
  FServerSocket.OnEventEvent := OnClientEvent;
end;

destructor TServerMainForm.Destroy;
begin
  Timer1.Enabled := False;
  FServerSocket.Free;
  FLock.Free;
  inherited Destroy;
end;

procedure TServerMainForm.OnClientError(ASocket: TCustomSocket;
  ErrorEvent: TErrorEvent; var ErrCode: Integer);
begin
  FLock.Enter;
  try
    MsgMemo.Lines.Add('Error: ' + SysErrorMessage(ErrCode))
  finally
    FLock.Leave;
  end;
end;

procedure TServerMainForm.OnClientEvent(ASocket: TCustomSocket;
  SocketEvent: TSocketEvent);
begin
{ FLock.Enter;
  try
    MsgMemo.Lines.Add(GetEnumName(TypeInfo(TSocketEvent), Ord(SocketEvent)));
    if SocketEvent in [seConnect, seDisconnect] then
  finally
    FLock.Leave;
  end; }
end;

function TServerMainForm.OnClientRead(ASocket: TCustomSocket;
  AData: Pointer; ACount: Integer): Integer;
begin
  FLock.Enter;
  try
    Inc(FRequestCount);
  finally
    FLock.Leave;
  end;
  ASocket.Write(AData^, ACount);
  Result := 0;
end;

procedure TServerMainForm.SetServerActive(const Value: Boolean);
begin
  if Value then
  begin
    FServerSocket.Port := StrToInt(PortEdit.Text);
    FServerSocket.Open;
  end else
    FServerSocket.Close;
  FServerActive := Value;
  FRequestCount := 0;
end;

procedure TServerMainForm.ActiveButtonClick(Sender: TObject);
const
  SCaption: array [Boolean] of string = ('Start', 'Stop');
begin
  ServerActive := not ServerActive;
  ActiveButton.Caption := SCaption[ServerActive];
end;

procedure TServerMainForm.Button1Click(Sender: TObject);
var
  S: string;
  D: TDataBlock;
  I, Count: Integer;
begin
  S := ClientMsgEdit.Text;
  FillChar(D, SizeOf(D), 0);
  D.Len := Length(S);
  StrPCopy(D.Content, S);
  Count := SizeOf(Integer) + Length(S);
  for I := 0 to FServerSocket.ClientCount - 1 do
    FServerSocket.Clients[I].Write(D, Count);
end;

procedure TServerMainForm.Timer1Timer(Sender: TObject);
begin
  ConnectionCountText.Caption := IntToStr(FServerSocket.ClientCount);
  CountText.Caption := IntToStr(FRequestCount);
end;

end.

////////////////////////////////////////////////////////////////////////////
//CommonUnit.pas
unit CommonUnit;

interface

type
  PDataBlock = ^TDataBlock;
  TDataBlock = record
    Len: Integer;
    Content: array [0..100] of Char;
  end;

implementation

end.
////////////////////////////////////////////////////////////////////////////


// 예제 클라이언트
///////////////////////////////////////////////////////////////////////////
//ClientDpr.dpr
program ClientDpr;

uses
  Forms,
  ClientMainUnit in 'ClientMainUnit.pas' {ClientMainForm};

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TClientMainForm, ClientMainForm);
  Application.Run;
end.
///////////////////////////////////////////////////////////////////////////
//ClientMainUnit.dfm
object ClientMainForm: TClientMainForm
  Left = 192
  Top = 107
  Width = 327
  Height = 341
  Caption = 'ClientMainForm'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -12
  Font.Name = 'Courier New'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 15
  object Label1: TLabel
    Left = 16
    Top = 8
    Width = 28
    Height = 15
    Caption = '&host'
    FocusControl = HostEdit
  end
  object Label2: TLabel
    Left = 16
    Top = 50
    Width = 84
    Height = 15
    Caption = '&Thread Count'
    FocusControl = ThreadCountEdit
  end
  object Label7: TLabel
    Left = 144
    Top = 8
    Width = 28
    Height = 15
    Caption = '&Port'
    FocusControl = PortEdit
  end
  object Label8: TLabel
    Left = 224
    Top = 8
    Width = 49
    Height = 15
    Caption = '&Timeout'
    FocusControl = TimeoutEdit
  end
  object StartButton: TButton
    Left = 16
    Top = 101
    Width = 89
    Height = 25
    Caption = '&Add test'
    TabOrder = 0
    OnClick = StartButtonClick
  end
  object HostEdit: TEdit
    Left = 16
    Top = 24
    Width = 97
    Height = 23
    TabOrder = 1
    Text = '127.0.0.1'
  end
  object ThreadCountEdit: TEdit
    Left = 16
    Top = 66
    Width = 265
    Height = 23
    MaxLength = 100
    TabOrder = 2
    Text = '200'
  end
  object StopButton: TButton
    Left = 120
    Top = 101
    Width = 89
    Height = 25
    Caption = 'St&op All'
    TabOrder = 3
    OnClick = StopButtonClick
  end
  object GroupBox1: TGroupBox
    Left = 16
    Top = 152
    Width = 273
    Height = 137
    Caption = 'Result'
    TabOrder = 4
    object Label3: TLabel
      Left = 87
      Top = 53
      Width = 35
      Height = 15
      Caption = 'Count'
      FocusControl = CountText
    end
    object Label4: TLabel
      Left = 52
      Top = 82
      Width = 70
      Height = 15
      Caption = 'RightCount'
      FocusControl = RightCountText
    end
    object Label5: TLabel
      Left = 52
      Top = 112
      Width = 70
      Height = 15
      Caption = 'ErrorCount'
      FocusControl = ErrorCountText
    end
    object Label6: TLabel
      Left = 10
      Top = 24
      Width = 112
      Height = 15
      Caption = 'Connection Count'
      FocusControl = ConnectionCountText
    end
    object CountText: TStaticText
      Left = 136
      Top = 53
      Width = 97
      Height = 16
      AutoSize = False
      TabOrder = 0
    end
    object RightCountText: TStaticText
      Left = 136
      Top = 82
      Width = 97
      Height = 15
      AutoSize = False
      TabOrder = 1
    end
    object ErrorCountText: TStaticText
      Left = 136
      Top = 112
      Width = 97
      Height = 17
      AutoSize = False
      TabOrder = 2
    end
    object ConnectionCountText: TStaticText
      Left = 136
      Top = 24
      Width = 97
      Height = 16
      AutoSize = False
      TabOrder = 3
    end
  end
  object PortEdit: TEdit
    Left = 144
    Top = 24
    Width = 57
    Height = 23
    TabOrder = 5
    Text = '211'
  end
  object TimeoutEdit: TEdit
    Left = 224
    Top = 24
    Width = 57
    Height = 23
    TabOrder = 6
    Text = '500'
  end
  object Timer1: TTimer
    OnTimer = Timer1Timer
    Left = 208
    Top = 104
  end
end

///////////////////////////////////////////////////////////////////////////
//ClientMainUnit.pas
unit ClientMainUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, CommonUnit, SyncObjs, ExtCtrls;

type
  TClientMainForm = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    StartButton: TButton;
    HostEdit: TEdit;
    ThreadCountEdit: TEdit;
    StopButton: TButton;
    GroupBox1: TGroupBox;
    Label3: TLabel;
    CountText: TStaticText;
    Label4: TLabel;
    RightCountText: TStaticText;
    Label5: TLabel;
    ErrorCountText: TStaticText;
    Label6: TLabel;
    ConnectionCountText: TStaticText;
    Timer1: TTimer;
    Label7: TLabel;
    PortEdit: TEdit;
    Label8: TLabel;
    TimeoutEdit: TEdit;
    procedure StartButtonClick(Sender: TObject);
    procedure StopButtonClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    FThreads: TList;
    FRightCount: Integer;
    FErrorCount: Integer;
    FLock: TCriticalSection;
    procedure AddMsg(RightCount, ErrorCount: Integer);
    procedure ThreadTerminate(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

var
  ClientMainForm: TClientMainForm;

implementation

uses WinSock2, ScktComp;

{$R *.dfm}
type
  TAddEvent = procedure(RightCount, ErrorCount: Integer) of object;
  TClient = class(TThread)
  private
    FPort: Integer;
    FTimeout: Integer;
    FHost: string;
    FAddMsg: TAddEvent;
    FSocket: TClientWinSocket;
  protected
    procedure Execute; override;
  public
    constructor Create(AHost: string; APort, ATimeout: Integer; AAddMsg:
TAddEvent);
    destructor Destroy; override;
  end;

{ TClient }

constructor TClient.Create;
begin
  inherited Create(False);
  FHost := AHost;
  FPort := APort;
  FTimeout := ATimeout;
  FAddMsg := AAddMsg;
  FSocket := TClientWinSocket.Create(Integer(not(0)));
  FSocket.ClientType := ctBlocking;
  FreeOnTerminate := True;
end;

destructor TClient.Destroy;
begin
  FSocket.Free;
  inherited Destroy;
end;

procedure TClient.Execute;
const
  SizeInt = SizeOf(Integer);
  SizeBlock = SizeOf(TDataBlock);
  Data: TDataBlock = (len: 22; Content: 'hellohellohellohello22');

  function IsClose(socket, event: Cardinal): Boolean;
  var
    Network: TWSANetworkEvents;
  begin
    Result := True;
    FillChar(Network, SizeOf(Network), 0);
    if WSAEnumNetworkEvents(FSocket.SocketHandle, Event, @Network) = -1 then
      Exit;
    { Close msg }
    Result := ((Network.lNetworkEvents and FD_CLOSE) = FD_CLOSE) and
       (Network.iErrorCode[FD_CLOSE_BIT] <> 0);
  end;

var
  msg: TMsg;
  P: Pointer;
  D: TDataBlock;
  TimeOut, RetLen: Integer;
  Event: THandle;
begin
  try
    FSocket.Open(FHost, FHost, '', FPort);
    Timeout := 2000;
    setsockopt(FSocket.SocketHandle, SOL_SOCKET, SO_RCVTIMEO, @Timeout, SizeOf(Timeout));
  except
    SetWindowText(ClientMainForm.Handle, PChar(SysErrorMessage(GetLastError)));
    Exit;
  end;
  PeekMessage(msg, 0, 0, 0, PM_NOREMOVE);
  Event := WSACreateEvent;
  try
    WSAEventSelect(FSocket.SocketHandle, Event, FD_READ or FD_CLOSE);
    while not Terminated do
      case MsgWaitForMultipleObjects(1, Event, False, FTimeout, QS_ALLINPUT) of
        WAIT_OBJECT_0:
        begin
          if IsClose(FSocket.SocketHandle, Event) then
          begin
            { server close ; }
            break;
          end;
          FillChar(D, SizeBlock, 0);
          RetLen := FSocket.ReceiveBuf(D.Len, SizeInt);
          if RetLen = 0 then
            break;
          if RetLen <> SizeInt then
          begin
            FAddMsg(0, 1);
            Continue;
          end;
          RetLen := FSocket.ReceiveBuf(D.Content, D.Len);
          if RetLen <> D.Len then
          begin
            FAddMsg(0, 1);
            Continue;
          end;
          FAddMsg(1, 0);
          WSAResetEvent(Event);
        end;
        WAIT_OBJECT_0 + 1:
          while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do
            case msg.message of
              WM_USER:
              begin
                RetLen := PDataBlock(msg.lParam)^.Len + SizeInt;
                FSocket.SendBuf(Pointer(msg.lParam)^, RetLen);
              end;
              WM_CLOSE:
              begin
                FSocket.Close;
                break;
              end;
            end;
        WAIT_TIMEOUT:
        begin
          P := @Data;
          FSocket.SendBuf(P^, 26);
        end;
      end;
  finally
    WSACloseEvent(Event);
    FSocket.Close;
  end;
end;

{ TClientMainForm }

procedure TClientMainForm.AddMsg(RightCount, ErrorCount: Integer);
begin
  FLock.Enter;
  try
    Inc(FRightCount, RightCount);
    Inc(FErrorCount, ErrorCount);
  finally
    FLock.Leave;
  end;
end;

constructor TClientMainForm.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FLock := TCriticalSection.Create;
  FThreads := TList.Create;
end;

destructor TClientMainForm.Destroy;
begin
  while FThreads.Count > 0 do
    with TThread(FThreads.Last) do
    begin
      Terminate;
      PostThreadMessage(ThreadID, WM_CLOSE, 0, 0);
      Sleep(10);
    end;
  FThreads.Free;
  FLock.Free;
  inherited Destroy;
end;

procedure TClientMainForm.ThreadTerminate(Sender: TObject);
begin
  FLock.Enter;
  try
    FThreads.Remove(Sender);
  finally
    FLock.Leave;
  end;
end;

procedure TClientMainForm.StartButtonClick(Sender: TObject);
var
  Host: string;
  Thread: TThread;
  I, ThreadCount, Port, Timeout: Integer;
begin
  Port := StrToInt(PortEdit.Text);
  Timeout := StrToInt(TimeoutEdit.Text





211   [윈도우즈 API] 콤포넌트의 Hint 에 그림(Bitmap) 넣기  김영대 2003/04/11 5230 1360
210   [일반/컴포넌트] 키보드의 Shift+Tab 이 눌린것처럼 처리하기  김영대 2003/04/14 4766 1312
209   [윈도우즈 API] 폼이 Minimized 되었을때 깜박이게 하기 2  김영대 2003/04/14 6017 1324
208   [COM/OLE] 그리드 자료 엑셀로 좀더 빠르게 보내기  공성환 2003/04/16 5560 976
207   [일반/컴포넌트] C에서 한글자르기  공성환 2003/04/16 4880 973
206   [COM/OLE] 기존 Excel 문서 불러와서 편집후 저장하기  김영대 2003/04/18 5504 1293
205   [일반/컴포넌트] thread-safe Queue 구현  김영대 2003/08/18 6189 1361
204   [알고리즘] 숫자를 KB, MB, GB 단위로 환산하기  김영대 2003/11/13 5102 1172
203   [일반/컴포넌트] StrToFloatDef  김영대 2003/11/13 5002 1259
202   [알고리즘] 구분자(delimiter)를 사용한 문자열 파싱(parsing)  김영대 2003/11/13 5209 1149
201   [일반/컴포넌트] thread-safe Queue를 이용한 TLogThread  김영대 2003/11/18 4854 1143
  [네트웍/인터넷] IOCP(I/O Completion Port) class  김영대 2003/11/18 11080 884
199   [네트웍/인터넷] Winsock WriteFile and Overlapped IO  김영대 2003/11/18 5464 1235
198   [시스템] 윈도우즈 서비스 목록 구하기  김영대 2004/07/22 4627 1240
197   [시스템] 윈도우즈 서비스 상태 구하기  김영대 2004/07/22 5037 1284
196   [시스템] 윈도우즈 서비스 시작/중지 하기  김영대 2004/07/22 6199 1553
195   [시스템] 윈도우즈 시스템의 스크롤바 두께 바꾸기  김영대 2004/07/24 5658 1386
194   [시스템] 마우스 아래의 윈도우 핸들 구하기  김영대 2004/07/24 9460 1987
193   [시스템] 내 프로그램의 실행 우선순의 바꾸기  김영대 2004/07/24 5155 1399
192   [윈도우즈 API] 내 프로그램의 화면을 가리는 프로그램 리스트  김영대 2004/07/24 4598 1210
191   [윈도우즈 API] Taskbar 의 특정 위치에 popup 메뉴 띄우기  김영대 2004/07/24 4627 1207
190   [윈도우즈 API] Taskbar 의 위치 추적하기  김영대 2004/07/24 4032 1072
189   [일반/컴포넌트] 윈도우즈"시작" 버튼위에 글씨 쓰기  김영대 2004/07/24 4210 1156
188   [시스템] 제어판의 모든 applet 정보 구하기  김영대 2004/07/24 4458 1141
187   [윈도우즈 API] 바로 직전에 active 되었던 윈도우와 콘트롤 구하기  김영대 2004/07/24 4667 1175
186   [윈도우즈 API] 지원하는 키보드 입력 언어 구하고 변경하기  김영대 2004/07/24 4767 1275
185   [윈도우즈 API] 현재 키보드 입력 언어 구하기  김영대 2004/07/24 5017 1364
184   [COM/OLE] 윈도우즈 "작업 표시줄 및 시작 메뉴 등록 정보" 화면  김영대 2004/07/25 6103 1639
183   [COM/OLE] 윈도우즈 "인터넷 등록 정보" 화면  김영대 2004/07/25 4279 1331
182   [COM/OLE] 윈도우즈 "날짜/시간 등록 정보" 화면  김영대 2004/07/25 6426 1654
181   [COM/OLE] 윈도우즈 "검색: 파일 또는 폴더" 화면  김영대 2004/07/25 5006 1402
180   [COM/OLE] 윈도우즈 "시스템 종료" 화면  김영대 2004/07/25 4621 1312
179   [COM/OLE] 윈도우즈 "모든 창을 최소화"  김영대 2004/07/25 6263 1717
178   [일반/컴포넌트] TList 를 이용한 stack 구조 구현  김영대 2004/07/25 4402 1146
177   [윈도우즈 API] 폼에 애니메이션 효과 주기  김영대 2004/07/25 4685 1240
176   [일반/컴포넌트] 모서리가 둥근(rounded ends) TMemo 만들기  김영대 2004/07/25 4632 1191
175   [시스템] 로컬 가상 드라이버(substitution device) 만들고 제거하기  김영대 2004/07/25 6148 1335
174   [네트웍/인터넷] 네트워크 드라이브 연결 화면 띄우기  김영대 2004/07/26 5979 1609
173   [일반/컴포넌트] TProgressbar 의 색상 바꾸기  김영대 2004/07/26 4646 1242
172   [일반/컴포넌트] TTrewView, TListView 를 이미지로 저장하기  김영대 2004/07/26 4381 965

[이전 10개] [1]..[11][12][13][14][15][16][17][18][19] 20 ..[25] [다음 10개]
 

Copyright 1999-2022 Zeroboard / skin by zero