고급 COM 기술의 활용

Similar documents
PowerPoint Template

JAVA PROGRAMMING 실습 08.다형성

COM의 기초 개념

gnu-lee-oop-kor-lec06-3-chap7

API - Notification 메크로를통하여어느특정상황이되었을때 SolidWorks 및보낸경로를통하여알림메시지를보낼수있습니다. 이번기술자료에서는메크로에서이벤트처리기를통하여진행할예정이며, 메크로에서작업을수행하는데유용할것입니다. 알림이벤트핸들러는응용프로그램구현하는데있어

PowerPoint Presentation

C# Programming Guide - Types

Microsoft PowerPoint - chap02-C프로그램시작하기.pptx

PowerPoint 프레젠테이션

<4D F736F F F696E74202D20C1A63038C0E520C5ACB7A1BDBABFCD20B0B4C3BC4928B0ADC0C729205BC8A3C8AF20B8F0B5E55D>

Microsoft Word - ntasFrameBuilderInstallGuide2.5.doc

SOFTBASE XFRAME DEVELOPMENT GUIDE SERIES ActiveX 컴포넌트가이드 서울특별시구로구구로 3 동한신 IT 타워 1215 호 Phone Fax

PowerPoint 프레젠테이션

PowerPoint Presentation

Chapter #01 Subject

No Slide Title

- JPA를사용하는경우의스프링설정파일에다음을기술한다. <bean id="entitymanagerfactory" class="org.springframework.orm.jpa.localentitymanagerfactorybean" p:persistenceunitname=

PowerPoint Presentation

Visual Basic 반복문

슬라이드 1

ISP and CodeVisionAVR C Compiler.hwp

쉽게 풀어쓴 C 프로그래밍

Windows 8에서 BioStar 1 설치하기

다른 JSP 페이지호출 forward() 메서드 - 하나의 JSP 페이지실행이끝나고다른 JSP 페이지를호출할때사용한다. 예 ) <% RequestDispatcher dispatcher = request.getrequestdispatcher(" 실행할페이지.jsp");

歯MDI.PDF

제이쿼리 (JQuery) 정의 자바스크립트함수를쉽게사용하기위해만든자바스크립트라이브러리. 웹페이지를즉석에서변경하는기능에특화된자바스크립트라이브러리. 사용법 $( 제이쿼리객체 ) 혹은 $( 엘리먼트 ) 참고 ) $() 이기호를제이쿼리래퍼라고한다. 즉, 제이쿼리를호출하는기호

11장 포인터

슬라이드 1

Microsoft Word - src.doc

구조화 저장소 기법

Microsoft PowerPoint - chap06-2pointer.ppt

 메소드 오버로딩

17장 클래스와 메소드

1. auto_ptr 다음프로그램의문제점은무엇인가? void func(void) int *p = new int; cout << " 양수입력 : "; cin >> *p; if (*p <= 0) cout << " 양수를입력해야합니다 " << endl; return; 동적할

PowerPoint 프레젠테이션

쉽게 풀어쓴 C 프로그래밍

PowerPoint Presentation

설계란 무엇인가?

Network Programming

. 스레드 (Thread) 란? 스레드를설명하기전에이글에서언급되는용어들에대하여알아보도록하겠습니다. - 응용프로그램 ( Application ) 사용자에게특정서비스를제공할목적으로구현된응용프로그램을말합니다. - 컴포넌트 ( component ) 어플리케이션을구성하는기능별요

JAVA 프로그래밍실습 실습 1) 실습목표 - 메소드개념이해하기 - 매개변수이해하기 - 새메소드만들기 - Math 클래스의기존메소드이용하기 ( ) 문제 - 직사각형모양의땅이있다. 이땅의둘레, 면적과대각

어댑터뷰

액티브X 컨트롤의 사용과 제작

JAVA PROGRAMMING 실습 05. 객체의 활용

PowerPoint Presentation

A Hierarchical Approach to Interactive Motion Editing for Human-like Figures

Microsoft PowerPoint - e pptx

Microsoft PowerPoint - chap10-함수의활용.pptx

쉽게 풀어쓴 C 프로그래밍

JVM 메모리구조

[ 마이크로프로세서 1] 2 주차 3 차시. 포인터와구조체 2 주차 3 차시포인터와구조체 학습목표 1. C 언어에서가장어려운포인터와구조체를설명할수있다. 2. Call By Value 와 Call By Reference 를구분할수있다. 학습내용 1 : 함수 (Functi

Microsoft PowerPoint - 04-UDP Programming.ppt

제8장 자바 GUI 프로그래밍 II

q 이장에서다룰내용 1 객체지향프로그래밍의이해 2 객체지향언어 : 자바 2

Microsoft PowerPoint - chap01-C언어개요.pptx

Microsoft PowerPoint - Chapter 6.ppt

교육자료

Design Issues

임베디드시스템설계강의자료 6 system call 2/2 (2014 년도 1 학기 ) 김영진 아주대학교전자공학과

델파이 4 프로그래밍의 이해 (Understandings of Delphi 4 Programming)

JUNIT 실습및발표

오버라이딩 (Overriding)

SQL Developer Connect to TimesTen 유니원아이앤씨 DB 기술지원팀 2010 년 07 월 28 일 문서정보 프로젝트명 SQL Developer Connect to TimesTen 서브시스템명 버전 1.0 문서명 작성일 작성자

쉽게 풀어쓴 C 프로그래밍

View Licenses and Services (customer)

Microsoft PowerPoint - ch09 - 연결형리스트, Stack, Queue와 응용 pm0100

슬라이드 1

[ 그림 8-1] XML 을이용한옵션메뉴설정방법 <menu> <item 항목ID" android:title=" 항목제목 "/> </menu> public boolean oncreateoptionsmenu(menu menu) { getme

Microsoft PowerPoint - ch07 - 포인터 pm0415

Microsoft PowerPoint 세션.ppt

Microsoft PowerPoint - 2강

제11장 프로세스와 쓰레드

PowerPoint Presentation

Microsoft PowerPoint - java1-lab5-ImageProcessorTestOOP.pptx

iii. Design Tab 을 Click 하여 WindowBuilder 가자동으로생성한 GUI 프로그래밍환경을확인한다.

학습목표 함수프로시저, 서브프로시저의의미를안다. 매개변수전달방식을학습한다. 함수를이용한프로그래밍한다. 2

Spring Boot/JDBC JdbcTemplate/CRUD 예제

<4D F736F F F696E74202D203137C0E55FBFACBDC0B9AEC1A6BCD6B7E7BCC72E707074>

금오공대 컴퓨터공학전공 강의자료

PowerPoint Presentation

Microsoft PowerPoint - chap11-포인터의활용.pptx

Microsoft PowerPoint - additional01.ppt [호환 모드]

The Pocket Guide to TCP/IP Sockets: C Version

var answer = confirm(" 확인이나취소를누르세요."); // 확인창은사용자의의사를묻는데사용합니다. if(answer == true){ document.write(" 확인을눌렀습니다."); else { document.write(" 취소를눌렀습니다.");

Microsoft PowerPoint Android-SDK설치.HelloAndroid(1.0h).pptx

Secure Programming Lecture1 : Introduction

<322EBCF8C8AF28BFACBDC0B9AEC1A6292E687770>

Microsoft Word - Armjtag_문서1.doc

2) 활동하기 활동개요 활동과정 [ 예제 10-1]main.xml 1 <LinearLayout xmlns:android=" 2 xmlns:tools="

안드로이드기본 11 차시어댑터뷰 1 학습목표 어댑터뷰가무엇인지알수있다. 리스트뷰와스피너를사용하여데이터를출력할수있다. 2 확인해볼까? 3 어댑터뷰 1) 학습하기 어댑터뷰 - 1 -

학습목차 2.1 다차원배열이란 차원배열의주소와값의참조

쉽게

Microsoft PowerPoint - 07-Data Manipulation.pptx

슬라이드 1

Microsoft PowerPoint 장강의노트.ppt

< 목차 > Ⅰ. 개요 3 Ⅱ. 실시간스팸차단리스트 (RBL) ( 간편설정 ) 4 1. 메일서버 (Exchange Server 2007) 설정변경 4 2. 스팸차단테스트 10

A Dynamic Grid Services Deployment Mechanism for On-Demand Resource Provisioning

Windows Server 2012

Modern Javascript

Microsoft PowerPoint SDK설치.HelloAndroid(1.5h).pptx

Transcription:

고급 COM 기술의활용 (I) (Using Advanced COM Techniques I.) 이번장에서는비교적고급이라고할수있는 COM 에서컬렉션을구현하는방법과콜백 함수를이용하여인터페이스간통신을하는방법, 그리고연결점 (Connection Point) 인터페 이스를사용하여이벤트를구현하는방법을예제를통해익히도록한다. 컬렉션객체의구현 델파이의컴포넌트들중에는여러개의서브아이템을소유하는클래스들이많다. 대표적인것이 TStringList 로이클래스는리스트박스, 콤보박스, 메모컴포넌트등에서 Lines 또는 Items 프로퍼티로접근할수있도록되어있다. TStringList 는문자열들을인덱스로접근할수있도록허용한일종의컬렉션클래스라고말할수있다. 이밖에도 TList, TTreeNodes 등의클래스가컬렉션의형태로이루어져있다. IEnumXXXX 인터페이스 그렇다면, COM 에서이런컬렉션을구현하려면어떻게하면될까? COM 에서컬렉션을구현하기위해서는 IEnumXXXX 라는인터페이스를구현해야한다. 이런 IEnumXXXX 와같은인터페이스를열거인터페이스라고하며, 대표적으로구현된예는이벤트를구현하기위해사용되는 IConnectionPoint 와 IConnectionPointContainer 에서이용하는 IEnumConnectionPoints, IEnumConnections 인터페이스를들수있다. IEnumXXXX 인터페이스는진정한인터페이스라고는할수없고, 모든환경인터페이스의요청을지시하기위한문서화도구 (documentation device) 이다. 기본적으로이런열거형인터페이스는 Next, Skip, Reset, Clone 이라는 4 가지메소드를지원한다. 다음의코드는예제에서사용할 IEnumVariant 인터페이스의선언부이다. IEnumVariant = interface(iunknown) ['{00020404-0000-0000-C000-000000000046}'] function Next(celt: Longint; out elt; pceltfetched: PLongint): HResult; stdcall; function Skip(celt: Longint): HResult; stdcall; function Reset: HResult; stdcall; function Clone(out Enum: IEnumVariant): HResult; stdcall;

클라이언트는항목을가지고있는배열을할당하고, 이것을배열의크기와함께 Next 메소드로전달한다. 여기에서배열의이름이들어가는파라미터가 elt 이다. celt 는아마도 count of elements of T 의약자일것으로생각되는데, 여기서 T 는데이터형을의미하며배열의크기를지정한다. pceltfetched 파라미터는카운터변수로사용된다. 이런클라이언트의요구가있으면서버는지정된항목의수만큼배열을채우고자시도할것이고, 카운터에실제로들어간항목의수를반환하게된다. Reset 메소드는클라이언트가첫번째항목으로부터다시열거를시작하게할때사용되며, 열거하는도중에항목을건너뛸때에는 Skip 메소드를사용한다. 마지막으로 Clone 메소드는해당되는 IEnumXXXX 인터페이스에게현재의열거자객체의복사본을생성해준다. 그러면실제로예제를통해이를익히도록하자. 컬렉션을구현한자동화서버 이번에작성할예제자동화서버는필자가인터넷에서구한 Coll_Demo 라는프로그램을참고하여작성한것인데, 아쉽게도작성자에대한정보가없어서이를구체적으로알리지못한다는것을미리알려둔다. 이예제는파일을찾아주는역할을하는자동화서버를제작하는데, 해당되는파일들의정보를 IFileObject 라는인터페이스에저장하고이들의컬렉션을관리하는 IFileObjects 인터페이스와메인인터페이스역할을하는 IFileFinder 인터페이스의 3 가지인터페이스를이용한다. 예제를직접작성하기전에, 먼저이들인터페이스를디자인하도록한다. IFileObject = interface(idispatch) function Get_Name: WideString; safecall; function Get_FullName: WideString; safecall; function Get_Size: Integer; safecall; property Name: WideString read Get_Name; property FullName: WideString read Get_FullName; property Size: Integer read Get_Size; 가장기본적인요소가되는인터페이스가 IFileObject 이다. 3 가지프로퍼티를지원하는데, 이들은모두읽기전용이다. 파일의전체경로를포함한이름을저장하는 FullName, 패스 정보를제외한파일이름인 Name, 파일의크기정보인 Size 프로퍼티를가진다.

IFileObjects = interface(idispatch) function _NewEnum: IUnknown; safecall; function Get_Item(Index: Integer): IFileObject; safecall; function Get_Count: Integer; safecall; property Item[Index: Integer]: IFileObject read Get_Item; property Count: Integer read Get_Count; IFileObjects 인터페이스는기본적으로 IFileObject 인터페이스를요소로한컬렉션역할을하게된다. _NewEnum 메소드가여기서중요한역할을하는데, IEnumVariant 인터페이스를구현한클래스를생성해서여기에접근할수있도록 IUnknown 인터페이스로형변환하여결과값을리턴한다. 참고로 IEnumVariant 인터페이스는 ActiveX.pas 유닛에선언되어있으므로이를따로선언할필요는없다. Item 프로퍼티는인덱스를가진프로퍼티로, 해당인덱스의 IFileObject 인터페이스를반환한다. Count 프로퍼티는전체 IFileObject 인터페이스의수를반환한다. IFileFinder = interface(idispatch) function FindFiles(const Spec: WideString): IFileObjects; safecall; IFileFinder 인터페이스는 FindFiles 메소드를호출할때, 파라미터로 c:\*.* 와같이파일을열거할조건을문자열로넘겨주면 IFileObjects 인터페이스를반환하는역할을한다. 그러면, 이들을실제로구현해보도록하자. 먼저 File New 메뉴의 ActiveX 탭에서 ActiveX Library 아이콘을더블클릭하여새로운 DLL 프로젝트를시작한다. 그리고, 자동화객체를선언하도록한다. File New 메뉴의 ActiveX 탭에서 Automation Object 아이콘을더블클릭하고클래스이름으로 FileFinder 를입력하고 OK 버튼을클릭하면 IFileFinder 인터페이스가타입라이브러리에디터에추가될것이다. 마찬가지로다시 File New 메뉴의 AcitveX 탭에서 Automation Object 아이콘을더블클릭하고클래스이름을 FileObjects 를입력하고 OK 를클릭하고, 다시한번반복하여 FileObject 를추가한다. 이렇게하면 IFileFinder, IFileObjects, IFileObject 인터페이스와이들에대한 CoClass 들이선언될것이다. 그러면, 인터페이스에메소드와프로퍼티를추가해보자. IFileFinder 인터페이스를선택하고 New Method 버튼을클릭한다. 메소드의이름을 FindFiles 로설정하고, Parameters 탭을선택한다. Return Type 콤보박스에서 IFileObjects 인터페이스를선택하고, Add 버튼을클릭하여파라미터를추가한다. 추가된

파라미터의 Name 을 Spec 으로설정하고, Type 을콤보박스에서 WideString 으로설정한다. IFileObjects 인터페이스에서는 _NewEnum 메소드와 Item, Count 프로퍼티를추가해야한다. 마찬가지방법으로추가하면되는데, 이때 Item 과 Count 프로퍼티는모두읽기전용으로설정해야하므로 Attributes 탭의 Invoke Kind 콤보박스에서 property Get 을선택해야한다. Item 프로퍼티는 IFileObject, Count 프로퍼티는 Integer 로 Type 을설정한다. _NewEnum 메소드는 Parameters 탭에서 Return Type 으로 IUnknown 을선택한다. 이제마지막으로 IFileObject 인터페이스의프로퍼티를추가하도록하자. New Properties 버튼을클릭하여 Name, FullName, Size 프로퍼티를추가한다. 이들은모두읽기전용이므로 Attributes 탭의 Invoke Kind 콤보박스에서 property Get 을선택해야한다. Name, FullName 프로퍼티는 WideString, Size 프로퍼티는 Integer 로 Type 을설정한다. 이렇게해서작성된타입라이브러리에디터의형태는다음과같을것이다. 타입라이브러리에디터를닫으면자동화객체에대한유닛이추가되는데, 이들을모두적당한이름으로저장하도록하자. 먼저 IFileFinder 인터페이스를먼저구현하도록하자. FindFiles 메소드만구현하면되는데, FindFiles 메소드는 Spec 파라미터의내용을바탕으로 IFileObjects 인터페이스를반환하는역할을하게되므로 IFileObjects 인터페이스를구현한유닛을 uses 절에추가해야한다. 여기서는 U2_ExamSvr1Impl.pas 유닛을추가한다. FindFiles 메소드는다음과같이구현한다. function TFileFinder.FindFiles(const Spec: WideString): IFileObjects;

Result := TFileObjects.Create(Spec); IFileObjects 인터페이스의구현방법을알아보기전에, 먼저구현하기쉬운 IFileObject 인터페이스를먼저구현하도록하자. IFileObject 인터페이스를구현한 TFileObject 클래스에프로퍼티의정보를저장할 FName, FFullName, FSize 변수를 private 섹션에추가하고, constructor 인 Create 메소드를 public 섹션에다음과같이추가한다. TFileObject = class(tautoobject, IFileObject) private FFullName: String; FName: String; FSize: Integer; public constructor Create(const sr: TSearchRec); protected function Get_FullName: WideString; safecall; function Get_Name: WideString; safecall; function Get_Size: Integer; safecall; 이들메소드는다음과같이비교적쉽게구현할수있다. 참고로 TFileObject 클래스의 constructor 는 IFileObjects 인터페이스에의해서호출되므로, 파라미터인 sr 등을조작할필요는없다. constructor TFileObject.Create(const sr: TSearchRec); inherited Create; FFullName := sr.name; FName := ExtractFilename(FFullName); FSize := sr.size; function TFileObject.Get_FullName: WideString;

Result := FFullName; function TFileObject.Get_Name: WideString; Result := FName; function TFileObject.Get_Size: Integer; Result := FSize; 이제가장구현하기어려운 IFileObjects 인터페이스를구현하도록한다. IFileObjects 인터페이스를구현하기위해서는 IEnumVariant 인터페이스를구현해주어야한다. 이를위해서 uses 절에 ActiveX.pas 유닛을추가한다. 그밖에도이들을구현하기위해서여러가지함수를사용하게되는데여기에필요한 SysUtils.pas, Windows.pas 유닛과 TList 클래스를사용하기위해 Classes.pas 유닛을 uses 절에추가해야된다. 그리고, 앞에서구현한 TFileObject 클래스를이용하게되므로 IFileObject 인터페이스를구현한유닛을 uses 절에추가한다 ( 여기서는 U3_ExamSvr1Impl.pas). 먼저, IEnumVariant 인터페이스를구현할 TEnumVariant 클래스를다음과같이선언한다. TEnumVariant = class(tinterfacedobject, IEnumVariant) private FIndex: Integer; FList: TList; FParent: IUnknown; protected function Next(celt: Longint; out elt; pceltfetched: PLongint): HResult; stdcall; function Skip(celt: Longint): HResult; stdcall; function Reset: HResult; stdcall; function Clone(out enum: IEnumVariant): HResult; stdcall; public constructor Create(aParent: IUnknown; alist: TList);

여기서열거자역할을하는메소드는 protected 섹션에선언된 4 개의메소드이다. 내부적으로사용하기위해 FIndex, FParent, FList 변수를 private 섹션에추가하고, public 섹션에 constructor 로 Create 메소드를추가한다. IFileObjects 인터페이스를선언하는 TFileObjects 클래스에는 TList 클래스의객체를저장할 FList 변수를 private 섹션에추가하고 constructor 와 destructor 로사용할 Create, Destroy 메소드를 public 섹션에다음과같이추가한다. TFileObjects = class(tautoobject, IFileObjects) private FList: TList; public constructor Create(const afilespec: String); destructor Destroy; override; protected function _NewEnum: IUnknown; safecall; function Get_Count: Integer; safecall; function Get_Item(Index: Integer): IFileObject; safecall; 그러면, 먼저 TEnumVariant 클래스를구현해보도록하자. constructor 인 Create 메소드 에서는 TEnumVariant 클래스의 Parent 가되는클래스와열거할항목을저장할 TList 클 래스변수의값을다음과같이설정한다. constructor TEnumVariant.Create(aParent: IUnknown; alist: TList); inherited Create; FParent := aparent; FList := alist; 그리고, 가장중요한 Next 메소드는다음과같이구현한다. function TEnumVariant.Next(celt: Longint; out elt; pceltfetched: PLongint): HResult; type TVariantArray = packed array[0..0] of OleVariant;

var i: Integer; for i := 0 to celt - 1 do VariantClear(TVariantArray(elt)[i]); 앞에서도설명한바있지만, 여기서 elt 는배열이름을가리키며 celt 는배열의항목의수 가된다. 그러므로, 이코드는선언한 TVariantArray 라는 OleVariant 의배열의값을초 기화하는역할을한다. i := 0; while (celt > 0) and (FIndex < FList.Count) do TVariantArray(elt)[i] := IUnknown(FList[FIndex]) as IDispatch; Inc(i); Dec(celt); Inc(FIndex); 이코드는 FList 의마지막항목까지의객체를 IDispatch 로 TVariantArray 배열에저장하 는역할을한다. 즉, TVariantArray 배열의내용과 FList 변수의내용을동기화하는코드 이다. try if Assigned(pceltFetched) then pceltfetched^ := i; except 제대로작업이끝났으면 pceltfetched 파라미터의값을설정한다. if celt = 0 then else Result := S_OK

Result := S_FALSE; 마지막으로결과값을반환하면된다. Reset, Skip, Clone 메소드는구현하기가비교적쉽다. 다음과같이구현하면된다. function TEnumVariant.Reset: HResult; FIndex := 0; Result := S_OK; function TEnumVariant.Skip(celt: Longint): HResult; while (celt > 0) and (FIndex < FList.Count) do Dec(celt); Inc(FIndex); if celt = 0 then Result := S_OK else Result := S_FALSE; function TEnumVariant.Clone(out enum: IEnumVariant): HResult; var r: TEnumVariant; r := TEnumVariant.Create(FParent, FList); r.findex := FIndex; enum := r; Result := S_OK; 이렇게함으로써 FList 의아이템에 IDispatch 인터페이스로서 IFileObject 인터페이스를저

장할수있게되었다. FList 에내용을저장하고, 이들에접근할때자동으로 IEnumVariant 인터페이스의메소드를호출하여사용하게된다. 그러면, FList 를이용하여 IFileObject 인터페이스객체를관리하는 TFileObjects 클래스를구현하도록하자. 가장중요한것이 constructor 인 Create 메소드이다. 이메소드는 IFileFinder 인터페이스의 FindFiles 메소드에의해서도호출되며, 실제로 FindFiles 메소드에서파라미터로사용된문자열을바탕으로파일을검색해서파일의내용을바탕으로 TFileObject 클래스를생성하고, IFileObject 인터페이스를 FList 에저장한다. Create 메소드를다음과같이구현한다. constructor TFileObjects.Create(const afilespec: String); var r: Integer; sr: TSearchRec; Obj: IFileObject; inherited Create; FList := TList.Create; r := FindFirst(aFileSpec, faanyfile, sr); try while r = 0 do Obj := TFileObject.Create(sr); Obj._AddRef; FList.Add(Pointer(Obj)); r := FindNext(sr); finally SysUtils.FindClose(sr); 그다지어렵지않은코드이므로자세한설명은생략한다. 주의해야할부분은 Obj 변수를 IFileObject 형으로선언한뒤에이를 TFileObject.Create 메소드에찾은파일의 TSearchRec 데이터형데이터를저장한 sr 변수를파라미터로사용하여호출하는부분과, IFileObject 인터페이스를사용하기때문에 _AddRef 메소드를호출한부분이다.

이부분에서실수를하면 COM 의핵심부분이라고할수있는참조계수관리에실패하게 된다. 참고 : COM 참조계수관리 델파이는기본적으로참조계수관리를자동으로해준다. 그렇지만, 여기에는일반적인참조계수와는다른델파이만의규칙이있기때문에, 이를잘숙지하고있어야한다. 만약변수의데이터형으로 IUnknown 을이용한경우에는델파이가자동으로참조계수관리를하므로 AddRef 나 Release 메소드를사용하면안된다. 이때주의할것은어떤식으로 COM 객체를사용하는지여부에따라참조계수가증가하기도하고, 변화를주지않을수도있다. 다음의예제코드를참고하기바란다. var MyIxxxVariable: ISomeCOMInterface;... MyIxxxVariable := TMyCOMObject.Create; // 내부적으로 AddRef 가호출된다. SomeAPIFunc(MyIxxxVariable); 이경우에는내부적으로 ISomeComInterface 에대한참조계수가증가하기때문에, 함수를 호출하는데문제가없다. 그렇지만다음의코드는사정이다르다. var MyDelphiVariable: TMyCOMObject;... MyDelphiVariable := TMyCOMObject.Create; //AddRef 가호출되지않는다. SomeAPIFunc(MyDelphiVariable); 이경우에는 API 함수의파라미터로직접 COM 객체를사용해도형변환을하기때문에문 제가없지만 AddRef 를호출하지않기때문에, 문제가된다. 이를해결하기위해서는다음과같은코드를사용하면된다. var MyDelphiVariable: TMyCOMObject;... MyDelphiVariable := TMyCOMObject.Create; SomeAPIFunc(MyDelphiVariable as ISomeCOMInterface);

as 연산자에의해 AddRef 가호출되므로잘동작하게된다. 그런데, IFileObject 인터페이스의 _AddRef 를호출한이유는델파이의 FList 아이템으로추가할경우이들에의해인터페이스가추가, 삭제될때참조계수의변화를주어야하기때문이다. 그러므로, 나중에 IFileObject 인터페이스를아이템에서삭제할경우 _Release 메소드를호출해야한다. 그리고, FList 의 Add 메소드를호출할때 IFileObject 인터페이스를저장한 Obj 변수를 Pointer 형으로형변환하여저장하면된다. 즉, 이렇게 FList 에아이템을추가할때 Pointer 형으로형변환하는것으로는 _AddRef 가호출되지않기때문에, 그이전에 _AddRef 메소드를명시적으로호출하는것이다. TFileObjects 클래스의 _NewEnum 메소드는다음과같이구현한다. function TFileObjects._NewEnum: IUnknown; Result := TEnumVariant.Create(Self, FList) as IUnknown; 즉, 열거를담당하는 aparent 로 Self(TFileObjects) 를넘기고열거할항목을저장할배열로 FList 변수를지정하는것이다. TFileObjects 클래스의 IFileObject 인터페이스를항목으로제공하는 Item 프로퍼티와항목의수를제공하는 Count 프로퍼티는 Get_Item, Get_Count 메소드로다음과같이구현할수있다. function TFileObjects.Get_Count: Integer; Result := FList.Count; function TFileObjects.Get_Item(Index: Integer): IFileObject; Assert((Index > 0) and (Index <= FList.Count)); Result := IFileObject(FList[Index - 1]); Result._AddRef;

여기에서도주의할것은 IFileObject 인터페이스를이용하는클라이언트를위해서 _AddRef 를호출한다는것이다. 마지막으로 Destroy 메소드를다음과같이구현하면된다. destructor TFileObjects.Destroy; var i: Integer; for i := 0 to FList.Count - 1 do IUnknown(FList[i])._Release; FList.Free; inherited Destroy; Destroy 메소드는이와같이 FList 에저장된 IFileObject 인터페이스를모두 _Release 메소드를호출하여해제하는것이중요하며, 동시에생성한 FList 클래스를해제하면된다. 이것으로 TFileObjects 클래스의구현이모두끝났다. 쉽지않은내용이지만, IEnumXXXX 인터페이스에대해서는연결점에대해설명하면서다시다루게될것이다. 그러면, 프로젝트를컴파일하고 Run Register ActiveX Server 메뉴를선택하여자동화서버를등록하도록한다. 클라이언트어플리케이션의제작 그러면, 제작한자동화서버를이용해서디렉토리브라우저기능을하는클라이언트어플리케이션을하나만들어보자. 먼저폼위에 TDriveComboBox, TDirectoryListBox, TComboBox, TListView, TButton 컴포넌트를하나씩올려놓자. DriveComboBox1 의 DirList 프로퍼티는 DirectoryListBox1 으로설정한다. 그리고 ComboBox1 의 Items 프로퍼티에디터를이용하여 *.*, *.txt, *.exe, *.dll 을기본적으로추가하여이들을이용하거나직접입력이가능하도록하자. 버튼컴포넌트는 Caption 프로퍼티를 찾기 로설정한다. ListView1 컴포넌트는먼저 ViewStyle 프로퍼티를 vsreport 로설정한다. 그리고, Columns 프로퍼티에디터를이용하여 2 개의새로운컬럼을추가하고이들의 Caption 프로퍼티를 파일이름 과 파일크기 로설정한다. 이들의 Alignment 프로퍼티를각각 alleft, alright 로설정하고다음그림과같이적절하게 Width 프로퍼티를조절한다.

ExamSvr1 의타입라이브러리를사용해야하므로, uses 절의 ExamSvr1_TLB.pas 유닛을 추가하도록한다. 그리고전역변수로 IFileFinder 인터페이스변수를다음과같이선언한다. var Form1: TForm1; FileFinder: IFileFinder; 그리고, 폼의 OnCreate 이벤트핸들러에서 FileFinder 변수에 early 바인딩을이용하여값 을대입한다. procedure TForm1.FormCreate(Sender: TObject); FileFinder := CoFileFinder.Create; 마지막으로 Button1 의 OnClick 이벤트핸들러를다음과같이구현하면클라이언트어플리 케이션은완성된다. procedure TForm1.Button1Click(Sender: TObject); var FileObjects: IFileObjects; FileObject: IFileObject; i: Integer; FileItem: TListItem;

ListView1.Items.Clear; FileObjects := FileFinder.FindFiles(DirectoryListBox1.Directory + '\' + ComboBox1.Text); for i := 1 to FileObjects.Count do FileObject := FileObjects.Item[i]; FileItem := ListView1.Items.Add; FileItem.Caption := FileObject.Name; FileItem.SubItems.Add(IntToStr(FileObject.Size div 1024) + ' KB'); 그러면클라이언트어플리케이션을컴파일하고실행해보자. 그리고, 디렉토리를선택한후콤보박스에파일찾기에서입력할수있는여러가지옵션을주고 찾기 버튼을클릭하면해당되는파일들을리스트뷰에서볼수있을것이다. COM 에서의콜백함수활용 COM 콜백인터페이스를활용하면 COM 서버컴포넌트가클라이언트어플리케이션에존재하는객체의메소드를호출할수있게된다. 이때콜백은이렇게서버에중요한변화가나타났을때마다클라이언트에이를알리는역할을하게된다. 콜백에대해서는후킹예제와함께제 7 부에서더자세하게다루게될것이다. 이러한콜백을잘활용하면멀티-유저환경의클라이언트-서버데이터베이스어플리케이션에서많은수의클라이언트에데이터변경등에대한여러가지조작을할수있게된다.

연결점 (Connection point) 방법론 실제로연결점에대한개념을이해하는것은그다지어려운것이아니다. 그렇지만이를어렵게느끼게하는것은몇가지의기술적인용어가많이나오기때문이다. 연결점의기본적인개념은클라이언트객체와서버객체가서로표준프로토콜을이용해서쉽게통신을하게만들자는것이다. 이때프로토콜에서클라이언트객체가서버객체에게특정콜백인터페이스에대해서알고있는지묻게되고, 서버객체는여기에대해긍정, 또는부정의반응을할수있다. 서버객체가긍정의답변을하게되면, 클라이언트는자신의콜백인터페이스를제공하고, 이를이용해서통신을할수있도록요청하게된다. 이때부터서버와클라이언트는서로의메소드를호출할수있게된다. 이개념을이용하면대단히유연한어플리케이션을개발할수있게된다. 클라이언트는특정콜백인터페이스를지원하는특정서버객체에대해자세히알필요가없으며, 단지어느서버객체에나자신이구현할수있는인터페이스를지원하는지여부만을알아보고서버가긍정의답변을할경우에만통신을하면된다. 서버의관점에서볼때이러한콜백인터페이스를 outgoing 인터페이스라고한다. 이는인터페이스가클라이언트에서구현되며, 서버에의해사용되기때문이다. 반대로서버에서구현되고클라이언트에의해사용되는인터페이스는 incoming 인터페이스라고한다. 최소한하나이상의 outgoing 인터페이스를지원하는서버객체를연결가능객체 (connectable object), 또는소스 (source) 라고하며, 콜백인터페이스를구현하는클라이언트객체를싱크 (sink) 라고한다. 클라이언트가콜백인터페이스를이용해서연결가능객체에연결하기위해서, 연결가능객체는반드시특정인터페이스에대한연결점 (connection point) 을구현해야한다. 서버객체가둘이상의 outgoing 인터페이스를지원하는경우도많은데, 이런경우에는여러종류의클라이언트에대한여러개의연결점을구현해야한다. COM 객체는연결점을구현하기위해 IConnectionPointContainer 와 IConnectionPoint 인터페이스를사용한다. 서버객체에연결하고자하는클라이언트객체는일단서버객체에게 IConnectionPointContainer 인터페이스를질의한다. 서버가유효한인터페이스를전달하면, 클라이언트는이인터페이스의 FindConnectionPoint 메소드를호출한다. 이때파라미터로클라이언트가구현하고있는 outgoing 인터페이스의인터페이스 ID(IID) 를넘기게된다. 서버가넘어온인터페이스를지원한다면 IConnectionPoint 인터페이스의포인터를 cp 파라미터에담아서돌려주게되며, 이파라미터가클라이언트가사용하는연결점이된다. 마지막으로클라이언트가실제인터페이스를구현하는부분의포인터를서버에넘겨주게되면연결이확립된다. 이를위해, cp 파라미터를이용해서클라이언트는 IConnectionPoint 인터페이스의 Advise 메소드를호출할수있는데, 여기에지원하는싱크인터페이스의포인터를 unksink 파라미터에담아서보내게된다. Advise 메소드의 dwcookie 파라미터는서버가클라이언트에게반환하는것으로, 일종의 ID 와같은것이다. 이를이용해서, 클라

이언트가연결점과의연결을해제할수있다. 실제로연결을해제할때에는 IConnectionPoint 인터페이스의 UnAdvise 메소드를사용한다. 쉽게말해서, 연결점컨테이너 (IConnectionPointContainer) 는서버객체가지원하는모든연결점들에대한목록이다. 이는서버객체가거의무한대의 outgoing 인터페이스를지원할수있다는것으로, 이들인터페이스는각각특정연결점으로정의된다. 또한, 하나의연결점은무한대의클라이언트를지원할수있다. 즉, 클라이언트가 IConnectionPoint 인터페이스의 Advise 메소드를호출할때, dwcookie 파라미터에각클라이언트에대해서유일한 ID 를제공하기때문에, 하나의연결점에연결되는클라이언트들이라도서버는각각을 dwcookie 를이용해서구별할수있게된다. 이들인터페이스는다음과같이선언되어있다. IConnectionPointContainer = interface function EnumConnectionPoints(out enum: IEnumConnectionPoints): HResult; function FindConnectionPoint(const iid: TIID; out cp: IConnectionPoint): HResult; IConnectionPoint = interface function GetConnectionInterface(out iid: TIID): HResult; function GetConnectionPointContainer(out cpc: IConnectionPointContainer): HResult; function Advise(const unksink: IUnknown; out dwcookie: Longint): HResult; function Unadvise(dwCookie: Longint): HResult; function EnumConnections(out enum: IEnumConnections): HResult; 그러면, 실제로연결점 (ConnectionPoint) 을이용하여이벤트를지원하는 COM 객체를생성하고, 이를활용하는클라이언트를간단하게작성하도록하자. 델파이 4 에서는이벤트를지원하는 COM 객체를쉽게만들수있도록위저드의기능이확장되었다. 그러므로, COM 객체에이벤트를지원하게확장하는것은그리어렵지않게구현할수있다. 그런데, 이렇게위저드의형태로확장한이벤트지원이반쪽밖에없어서이벤트를지원하는 COM 객체는쉽게만들수있으나, 이를이용하여실제이벤트를지원하는클라이언트를제작하는것은꽤어렵다. 보통은해당 COM 객체에대한이벤트 wrapper 클래스를오브젝트파스칼에맞도록작성하여, 이를이용하게된다. 다행히, 이문제를쉽게해결하기위해 Binh Ly(bly@castle.net) 가이벤트싱크를쉽게처리할수있는유틸리티유닛과컴포넌트를개발하여프리웨어로배포하고있어서비교적쉽게이벤트를처리할수있게되었다. 그러나, 이런컴포넌트와클래스의이용방법을소개

하는것으로는이벤트에대한명확한이해가어렵기때문에, 다소복잡하더라도먼저간단 한이벤트를지원하는 COM 객체와클라이언트를직접작성한뒤에이들에대해따로설명 하도록하겠다. 이벤트를지원하는 COM 객체 COM 객체에이벤트를지원하게하는것은그리어렵지않게구현할수있다. 먼저 File New 메뉴를선택한뒤 ActiveX 탭에서 ActiveX Library 아이콘을더블클릭하여프로젝트파일을생성한다. 그리고, File New 메뉴의 ActiveX 탭에서 Automation Object 아이콘을더블클릭하여자동화객체를생성할대화상자를띄우도록한다. 이대화상자에생성할클래스이름을지정하고, 이벤트를지원하기위해서는다음과같이 Generate event support code 체크박스를선택한다. OK 버튼을클릭하면, 타입라이브러리에디터가실행되는데여기에서인터페이스의메소드 들을다음과같이설정하도록한다.

즉, 사용할메소드를 ISampleEvent 메소드에추가하고이벤트는 DispInterface 인 ISampleEventEvents 인터페이스에 Event1 메소드를추가한다. 이렇게하고, OK 버튼을클릭하면다음과같은코드가자동으로생성될것이다. unit U_ExamSvr2; interface uses ComObj, ActiveX, AxCtrls, ExamSvr2_TLB; type TSampleEvent = class(tautoobject, IConnectionPointContainer, ISampleEvent) private { Private declarations } FConnectionPoints: TConnectionPoints; FEvents: ISampleEventEvents; public procedure Initialize; override; protected { Protected declarations } property ConnectionPoints: TConnectionPoints read FConnectionPoints implements IConnectionPointContainer; procedure EventSinkChanged(const EventSink: IUnknown); override; procedure SampleEvent1; safecall; implementation uses ComServ; procedure TSampleEvent.EventSinkChanged(const EventSink: IUnknown); FEvents := EventSink as ISampleEventEvents;

procedure TSampleEvent.Initialize; inherited Initialize; FConnectionPoints := TConnectionPoints.Create(Self); if AutoFactory.EventTypeInfo <> nil then FConnectionPoints.CreateConnectionPoint(AutoFactory.EventIID, cksingle, EventConnect); procedure TSampleEvent.SampleEvent1; initialization TAutoObjectFactory.Create(ComServer, TSampleEvent, Class_SampleEvent, cimultiinstance, tmapartment); end. 이코드를설명하면, 앞에서도설명한대로연결점컨테이너인터페이스를이용하여이벤트를구현하게된다. 클라이언트가 IConnectionPoint 인터페이스의 Advise 메소드를호출할때, dwcookie 파라미터에각클라이언트에대해서유일한 ID 를제공하기때문에, 하나의연결점에연결되는클라이언트들이라도서버는각각을 dwcookie 를이용해서구별할수있게된다. 그런데, 델파이에서는 TConnectionPoints 클래스에서이인터페이스를구현하기때문에이와같이간단히선언하는것으로충분하다. TAutoObject 클래스의 EventSinkChanged 메소드는다른인터페이스를이벤트로처리할수있도록제공되는가상메소드로, 이를오버라이드하여파라미터인 EventSink 를이벤트를지원하는 Dispinterface 로타입캐스팅하여이를이용하여이벤트의지원이가능하다. TAutoObject 클래스의 Initialize 메소드는객체의초기화를할수있는가상메소드로, 여기에서 TConnectionPoints 클래스의객체를생성하고, 이객체의 CreateConnectionPoint 메소드를호출하여파라미터로지정된연결점객체를생성하여컨테이너에포함한다. 개발자가할일은어떤메소드에서이벤트를발생시킬것인지를결정하면된다. 앞에서델파이가자동으로생성한코드에의해 FEvents 필드에 ISampleEventEvents 인터페이스에서정의한메소드들 ( 이번예제의경우 Event) 이포함되므로, 특정메소드에서이들 FEvents 의메소드를호출하면특정메소드를호출할때이벤트가발생한다.

이번예제에서는 ISampleEvent 인터페이스의 SampleMethod1 메소드를호출하면다른작 업은하지않고 Event1 을호출하여이벤트만발생시키도록다음과같이입력한다. procedure TSampleEvent.SampleEvent1; FEvents.Event1; 이것으로이벤트를지원하는 COM 객체서버의제작되었다. 실제로개발자가한일은이벤트코드를생성하도록하는체크박스를선택한것과타입라이브러리에서인터페이스의이름뒤에 Events 가붙은 Dispinterface 에이벤트로사용할메소드를추가하는것, 그리고인터페이스의메소드중에서이벤트를발생시킬곳에서 FEvents 필드에저장된이벤트메소드를호출한것밖에없다. 나머지는모두델파이가알아서한다. 이제이를컴파일하고, Run Register ActiveX Server 명령을선택하여액티브 X 서버를등록하도록한다. 실제로이렇게이벤트를지원하는 COM 객체서버를작성하는것보다, 이벤트를지원하는서버를사용하는것이훨씬더어렵다. 그러면, 앞에서작성한 COM 객체서버를이용하는클라이언트어플리케이션을만들어보자. 먼저폼을버튼 3 개를올려놓고다음과같이디자인한다. 이벤트를구현한서버를이용하기위해서는먼저 interface 섹션의 uses 절에 ActiveX.pas 유닛과 ExamSvr2_TLB.pas 유닛을추가한다. 또한 implementation 섹션의 uses 절에는 ComObj.pas 유닛을추가하여여러유틸리티함수를이용할수있도록한다. 그리고, 이벤트싱크를지원하는클래스와이를객체로사용하여실제사용할수있도록하는클래스를선언하고이를구현해야한다. 먼저이벤트싱크를지원하는클래스를다음과같이선언한다. TSampleEventSink = class(tinterfacedobject, IUnknown, IDispatch) private FOwner : TObject; FDispatch: IDispatch; FDispIntfIID: TGUID;

FConnection: Integer; FOnEvent: TNotifyEvent; protected //IUnknown function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; //IDispatch function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall; function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall; function GetTypeInfoCount(out Count: Integer): HResult; virtual; stdcall; function Invoke(dispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall; public constructor Create(AOwner: TObject; ADispatch: IDispatch; const DispIntfIID: TGUID); destructor Destroy; override; property OnEvent: TNotifyEvent read FOnEvent write FOnEvent; 여기서 TSampleEventSink 클래스는 TInterfacedObject 를상속하되, 이벤트를지원하기위해서 IUnknown 과 IDispatch 인터페이스를다시구현하는클래스이다. private 섹션에선언된 FOwner 필드는이벤트를발생시킬객체를지정하게되고, FDispatch 는사용할 IDispatch 인터페이스를그리고, FDispIntfIID 는이벤트를지원하는 Dispinterface 의 IID 를저장한다. FConnection 필드는이벤트와연결을하기위해호출하는함수에서필요로하는필드이다. 그리고, 실제이벤트로사용할필드인 FOnEvent 를선언하는데이필드는 TNotifyEvent 형으로선언한다. protected 섹션에는 IUnknown 과 IDispatch 인터페이스를구현하기위해이들인터페이스의메소드들을선언한다. 그리고, public 섹션에클래스의 constructor 인 Create 메소드를새로정의하고 Destroy 메소드는오버라이드한다. 마지막으로 OnEvent 라는이벤트를프로퍼티로정의한다. 그러면, 이클래스를구현해보도록하자. 먼저, constructor 와 destructor 를다음과같이구현한다. constructor TSampleEventSink.Create(AOwner: TObject; ADispatch: IDispatch; const DispIntfIID: TGUID);

inherited Create; FOwner := AOwner; FDispIntfIID := DispIntfIID; FDispatch := ADispatch; InterfaceConnect(FDispatch, FDispIntfIID, Self, FConnection); destructor TSampleEventSink.Destroy; InterfaceDisconnect(FDispatch, FDispIntfIID, FConnection); inherited Destroy; 즉, InterfaceConnect 와 InterfaceDisconnect 함수를호출하기위해서필드에 constructor 에넘어온파라미터를저장하고, 이를이용하여이벤트와연결을한다. InterfaceConnect 프로시저는 IConnectionPoint 인터페이스를이용하여 COM 서버에서이벤트를지원할수있도록한다. 이프로시저는다음과같이선언되어있다. 파라미터로이벤트의 Source 로사용될인터페이스를처음에, 이벤트 dispinterface 의 IID 를두번째, 이벤트 Sink 로사용할인터페이스를세번째파라미터로사용하며, 마지막에는연결된인터페이스의핸들에해당되는값을넘겨받게된다. procedure InterfaceConnect(const Source: IUnknown; const IID: TIID; const Sink: IUnknown; var Connection: Longint); 마찬가지로, InterfaceDisconnect 프로시저는지정된인터페이스의이벤트연결을해제하게된다. 이렇게이벤트를지원하는인터페이스가있으면, IUnknown 인터페이스의 QueryInterface 메소드를다시구현해서이벤트인터페이스에접근할수있도록해야한다. 그러므로, QueryInterface 를다음과같이구현한다. function TSampleEventSink.QueryInterface(const IID: TGUID; out Obj): HRESULT; Result := E_NOINTERFACE; if GetInterface(IID,Obj) then Result := S_OK;

if IsEqualGUID(IID,FDispIntfIID) and GetInterface(IDispatch, Obj) then Result := S_OK; 즉, Obj 로넘어온파라미터만검사하는것이아니라 FDIspIntfIID 필드에저장된이벤트인터페이스의 IID 와도동일한지검사하여이를모두허용하도록하는것이다. IUnknown 인터페이스의다른메소드인 _AddRef, _Release 는다음과같이구현한다. 이내용은인터페이스가자동으로해제되지않도록참조계수를지정하는것이다. function TSampleEventSink._AddRef: Integer; Result := 2; function TSampleEventSink._Release: Integer; Result := 1; 이제는 IDispatch 인터페이스의 GetTypeInfo, GetTypeInfoCount, GetIDsOfNames, Invoke 메소드를구현할차례인데이들중 Invoke 를제외하고는그다지중요하지않으므로다음과 같이간단하게구현한다. function TSampleEventSink.GetTypeInfoCount(out Count: Integer): HRESULT; Count := 0; Result := S_OK; function TSampleEventSink.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HRESULT; Result := E_NOTIMPL; function TSampleEventSink.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HRESULT;

Result := E_NOTIMPL; Invoke 메소드에서는이벤트를지원하는인터페이스에서 DispID 파라미터를이용하여여러 개의이벤트메소드를매핑하는역할을하는데, 이예제에서는 DispID 가 1 인메소드하나 만존재하므로다음과같이간단하게구현이가능하다. function TSampleEventSink.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HRESULT; case DispID of 1: if Assigned(FOnEvent) then FOnEvent(FOwner); Result := S_OK; 이것으로이벤트싱크를지원하는클래스는모두구현하였다. 이번에는우리가작성한자동화객체서버와그이벤트를사용할수있도록 wrapping 한클래스를선언하고구현할차례이다. Wrapper 클래스를다음과같이선언한다. TSampleEventObject = class private FSampleEvent: ISampleEvent; FEventSink : TSampleEventSink; function GetOnEvent: TNotifyEvent; procedure SetOnEvent(Value: TNotifyEvent); public constructor Create; destructor Destroy; override; property SampleEvent: ISampleEvent read FSampleEvent; property OnEvent: TNotifyEvent read GetOnEvent write SetOnEvent; private 섹션에사용할인터페이스인 ISampleEvent 를담을필드변수인 FSampleEvent 와 이벤트싱크객체를담을필드변수인 FSampleEventSink, 그리고이벤트를실제로구현할

접근메소드 (access method) 인 GetOnEvent, SetOnEvent 메소드를선언한다. 그리고, public 섹션에 constructor 와 destructor, 그리고자동화객체서버의인터페이스를프로퍼티와이벤트를프로퍼티로제공한다. 이클래스의구현은간단하다. 먼저 constructor 를다음과같이구현한다. constructor TSampleEventObject.Create; FSampleEvent := CoSampleEvent.Create; FEventSink := TSampleEventSink.Create(Self, FSampleEvent, DIID_ISampleEventEvents); 즉, ISampleEvent 를프로퍼티로접근하여사용할수있도록필드변수에 CoClass 를생성하여대입하고, 이벤트를사용할수있도록앞서작성한이벤트싱크클래스를인스턴스화하면된다. 이때이벤트를발생시키는객체를첫번째파라미터로사용하게된다. 주의할것은세번째파라미터인 DIID_ISampleEventEvents 인데이값을이용하여이벤트를지원하는인터페이스와연결하게된다. 이값은서버의타입라이브러리의파스칼버전인 ExamSvr2_TLB.pas 유닛에서인터페이스의이벤트를지원하기위해선언된 Dispinterface 의 IID 상수를사용해야한다. destructor 와 GetOnEvent, SetOnEvent 메소드는다음과같이간단히구현할수있다. destructor TSampleEventObject.Destroy; FEventSink := nil; inherited Destroy; function TSampleEventObject.GetOnEvent: TNotifyEvent; Result := FEventSink.OnEvent; procedure TSampleEventObject.SetOnEvent(Value: TNotifyEvent); FEventSink.OnEvent := Value;

이것으로 wrapper 클래스의구현이끝났다. 이제이를이용하여실제로이벤트가동작하는지알아보도록하자. 우리가앞에서작성한자동화서버는서버의인터페이스인 ISampleEvent 의 SampleMethod1 메소드를호출하면이벤트가발생하도록구현하였다. 이를위해이벤트인이벤트싱크클래스를구현하여델파이의이벤트구조와호환되도록하였으므로다음과같이 TNotifyEvent 형의메소드를하나구현하고, 이값을이벤트에대입하여이벤트가발생시이메소드가실행되도록하면된다. 먼저, wrapper 클래스를담을수있는전역변수를다음과같이선언한다. var Form1: TForm1; SampleEventObject: TSampleEventObject; 그리고, 폼의 private 섹션에 OnEvent 이벤트핸들러로사용할수있는메소드를다음과 같이추가하고구현한다. private procedure Event(Sender: TObject); ( 중략 ) procedure TForm1.Event(Sender: TObject); ShowMessage('Event Fired!'); Button1 을클릭하면이벤트싱크객체를생성하고, 이벤트핸들러로앞서선언하고구현한 Event 메소드를사용하도록대입하도록 OnClick 이벤트핸들러를다음과같이작성한다. procedure TForm1.Button1Click(Sender: TObject); if not Assigned(SampleEventObject) then SampleEventObject := TSampleEventObject.Create; SampleEventObject.OnEvent := Event;

그리고, Button3 를클릭하면생성된 wrapper 객체가있으면이를해제하도록다음과같이 OnClick 이벤트핸들러를작성한다. procedure TForm1.Button3Click(Sender: TObject); if Assigned(SampleEventObject) then SampleEventObject.Free; SampleEventObject := nil; 마지막으로이벤트를발생시키는 Button2 의 OnClick 이벤트핸들러는다음과같이작성한다. 단순히 ISampleEvent 인터페이스의 SampleEvent1 메소드를호출하는것으로이벤트가발생할것이다. procedure TForm1.Button2Click(Sender: TObject); if Assigned(SampleEventObject) then SampleEventObject.SampleEvent.SampleEvent1; 프로젝트를컴파일하고실행한뒤에, Connect 버튼과 Event 버튼을차례로클릭하면다 음과같이이벤트가발생하여이벤트핸들러가실행되는화면을볼수있을것이다.

이벤트지원컴포넌트의활용 간단한이벤트를지원하도록했지만, 생각보다작업이만만치않다는것을알수있을것이다. 특히, 이벤트를지원하도록서버를작성하는것도문제지만이벤트를지원하는서버를사용하는클라이언트의제작이대단히까다로운것을알수있다. 그렇다면, 이를보다편하게해결할수있는방법은없을까? 다행히 Binh Ly(bly@castle.net) 가공개한컴포넌트와유틸리티를이용하면까다로운자동화객체의이벤트를쉽게이용할수있다. 이컴포넌트와유틸리티의소스와데모어플리케이션이이장에해당되는디렉토리의 EventSink 서브디렉토리에제공되므로이를참고하기바란다. 이해를돕기위해사용방법을간단히소개하면다음과같다. 먼저 EventSinkImp 유틸리티를실행하면, 컴퓨터에설치된자동화서버의타입라이브러리에대한정보가나열될것이다. 이중에서사용할타입라이브러리를선택하고 Import 버튼을클릭하거나더블클릭하면이벤트를쉽게사용할수있는파스칼유닛파일이다음과같이자동으로생성된다.

이렇게생성된소스코드는 Output folder 로지정된디렉토리에생성되는데, 이소스코드는컴포넌트소스코드이므로 Component Install Component 메뉴를이용하여설치가가능하다. 컴포넌트를설치한뒤에는이컴포넌트를폼에올려놓고, 필요한싱크메소드를후킹하여사용하면된다. 이렇게설치된컴포넌트에는공통적인서버객체에이벤트싱크를연결하고해제하는 Connect, Disconnect 메소드가제공되는데이들은다음과같이 early 바인딩과 late 바인딩을모두사용하여이용할수있다. Early 바인딩의경우에는다음과같이한다. var pobject1: IObject1; pobject1 := CoObject1.Create; SinkComponent.Connect (pobject1 as IUnknown); 그리고, late 바인딩의경우사용하는방법은다음과같다. var vobject1 : OleVariant; vobject1 := CreateOleObject ('Server.Object1'); SinkComponent.Connect (IUnknown (vobject1)); 이렇게연결한서버객체와의연결을해제하려면 Disconnect 메소드를호출하면되는데, 싱크컴포넌트가파괴되는경우에는자동으로호출되므로따로호출할필요는없다. 디렉토리에포함된데모는 IE 4.0 의이벤트를활용하는예제이다. 이를분석하면구체적인사용방법을익힐수있을것이다. 정리 (Summary) 이번장에서는컬렉션과이벤트를구현하는고급스러운 COM 기술의구현방법에대해서 알아보았다. 간단한자동화객체를만들기위해서는이런어려운내용을특별히익힐필요 가없겠지만, 실제로쓸모가있는제대로된객체를만들어사용하려면컬렉션과이벤트의

구현은필수적이다. 아무쪼록, 이장에서설명한내용을바탕으로국내에서도훌륭한자동화서버객체나 COM 객체를작성하여강력한어플리케이션을개발하고, 가능하면많은개발자들이공유할수있도록범용성을갖춘멋진객체를개발하고, 이를공개하여여러개발자들에게도움을줄수있는개발자들이많았으면하는바람이다.