HallymUnv

[알림판목록 I] [알림판목록 II] [글목록][이 전][다 음]
[ HallymUnv ] in KIDS
글 쓴 이(By): jkcheong (하얀냥이)
날 짜 (Date): 1999년 10월  9일 토요일 오전 09시 39분 17초
제 목(Title): Re: [질문] COM(Component Object Model)?


 
Dr. GUI의 구성 요소, COM 및 ATL 강의
Dr. GUI Online
1부: 1998년 2월 2일
2부: 1998년 2월 9일
3부: 1998년 2월 23일
4부: 1998년 3월 2일

편집자 메모   Visual Studio 버전 6.0을 발표하였을 때는 이 칼럼의 1부에서 
4부까지만 사용할 수 있었습니다. 이 칼럼은 MSDN Library의 1998년 7월 판에 
전체가 인쇄될 것입니다. MSDN Online(http://www.microsoft.com/msdn/)의 주간 
칼럼인 "Dr. GUI Online"을 방문하여 이 칼럼과 이전 칼럼에 대한 기록을 볼 수도 
있습니다.

목차
1부: COM을 사용할 예정입니까? 이미 사용하고 있지는 않습니까?
2부: COM의 기본
3부: 개체 및 인터페이스 가져오기
4부: Object 클래스 및 Object 라이브러리

1부: COM을 사용할 예정입니까? 이미 사용하고 있지는 않습니까?
Dr. GUI는 이제 다음과 같은 여러분의 말을 경청할 준비가 되어 있습니다. "GUI 
박사님! 이제 무엇을 할 예정인가요? COM에 대하여 설명하실 건가요? 그 주제에 
대한 책이 이미 여러 권 나와 있는데요!"

정말 그렇습니다. Dr. GUI는 수 많은 좋은 책들을 추천합니다. 그는 Dale 
Rogerson의 Inside COM (Microsoft Press, 1997)과 Don Box의 Essential COM 
(Addison-Wesley, 1998)을 모두 좋아합니다. Adam Denning의 ActiveX Controls 
Inside Out (Microsoft Press, 1996)과 Sing Li와 Panos Economopoulos의 
Professional Visual C++ 5 ActiveX/COM Control Programming(WROX Press, 1997)도 
좋은 책입니다. 물론 Kraig Brockschmidt의 "Inside OLE, 2nd Edition (Microsoft 
Press, 1995)"은 누구나 인정하는 최고의 OLE 참고서입니다. (MSDN도 최고의 
참고서입니다. 물론 Dr. GUI의 의견은 아닙니다.) 구성 요소 소프트웨어에 대한 
일반적인 설명을 원한다면 Clemens Szyperski의 새 책, "Component Software: 
Beyond Object-Oriented Programming (Addison-Wesley, 1998)”를 참조하십시오.

"그러면 Dr. GUI는 이 책들에 무엇을 더 추가할 수 있습니까?"하고 물을 것입니다.

Dr. GUI는 이렇게 대답할 것입니다. "전혀". 이 저자들과 문서에는 진실로 모든 
것이 설명되어 있습니다. 시간을 내어 이 책들을 읽을 수 있다면 알게 될 것입니다.

"아하! 선생님은 매주 몇 개의 화면만으로 COM과 ATL에 대해 자상히 소개하실 
예정이군요!"하고 외칠 것 입니다.

그러면 Dr. GUI는 "맞습니다. 제가 생각했던 것보다 더 쉬워질 것입니다."라고 
대답할 것입니다.

구성 요소라니? 구성 요소가 무엇인가요?
Visual Basic을 자주 사용한다면 구성 요소들을 사용하여 프로그래밍하는데 익숙할 
것입니다. 스핀 단추와 같은 시각적인 ActiveX 컨트롤과 데이터베이스 액세스 
개체와 같은 비시각적인 ActiveX 구성 요소 둘 다 사용할 수 있습니다. 대부분의 
Visual Basic 프로그램은 이전에 만들어진 재사용 가능한 구성 요소를 중복해서 
사용합니다. 그러나 많은 구성 요소를 재사용하고 있더라도 여전히 대부분의 
사용자는 재사용 가능한 구성 요소들을 모두 쓰고 있지 않습니다.

C++로 프로그래밍하고 있다면 재사용에 대한 다른 경험이 있을 것입니다. C++와 
개체 지향 프로그래밍이 재사용을 쉽게 하여 준다고 주장합니다. 하지만 여러분이 
경험한 바로는 어떻습니까? 재사용 가능한 개체의 라이브러리를 작성할 수 
있었습니까? 틀림없이 여러분들 중 몇 명은 작성할 수 있었을 것이지만 대부분은 
작성하지 못했을 것입니다. 그리고 그런 라이브러리가 있다면 일상적으로 잘 
사용하고 있습니까? 훈련이 부족해서 코드를 재사용하지 못하는 것은 아닙니다. 
사실은 코드를 재사용하기가 어렵고(결코 필요한 것을 정확히 할 수 있을 것 같지 
않음) 재 사용 가능한 코드를 기록하는 것은 훨씬 더 어렵기 때문입니다(매우 
일반적이지만 충분히 이용하기는 매우 어렵습니다).

무엇보다도 C++가 쉬운 것은 재사용 가능한 이진 구성 요소의 작성이 아니라 C++가 
원본 코드의 재사용을 비교적 쉽게 해주기 때문입니다. 대부분의 주요 C++ 
라이브러리는 컴파일 형식이 아니라 원본 형식으로 저장됩니다. 개체로부터 
올바르게 물려받기 위해 원본을 조사해야 할 일이 매우 자주 있으며 재사용할 때 
원본 라이브러리의 실행 세부사항에 따라야 할 때가 매우 자주 있으며 대단히 쉽기 
때문입니다. 마치 그렇게 나쁘지는 않은 것처럼, 원래의 원본을 변경하여 
개별적으로 라이브러리를 작성해야 합니다. (개별적으로 작성된 MFC가 얼마나 많이 
있을까요? 아무도 모릅니다 . . . .)

원본 코드 대신 이진 개체를 재사용합시다
이진 개체는 어떻게 재사용할 수 있을까요? Windows 프로그래머의 입장에서 
처음으로 떠오르는 대답은 간단합니다. DLL(dynamic-link libraries ? 동적 링크 
라이브러리)을 사용하십시오. DLL을 사용하면 됩니다. Windows 자체가 DLL이므로 
결국 DLL의 집합이 됩니다. 하지만 몇 가지 문제가 있습니다.

첫째, DLL이 반드시 프로그래밍 언어에 독립되어야 할 필요는 없습니다. C로 
작성된 DLL이라 하더라도 호출 규약(매개변수가 입력되는 순서)을 바꾸기가 매우 
쉬워서 DLL을 C 프로그램에서만 사용할 수 있습니다. 그렇습니다. Windows가 
사용하는 호출 규약은 Windows 시스템에 대해 표준으로 매우 잘 정해져 있지만 
일치되지 않은 호출 규약에 의해 DLL이 제대로 동작하지 않는 경우가 많이 
있습니다.

DLL에 대해 C형 인터페이스를 작성할 때 몇 가지 중요한 제약이 있습니다. 첫째 
C++의 개체 지향 기능에는 함수 이름에 대한 독특한 명명 방식이 있기 때문에 개체 
지향 프로그래밍을 제한합니다. 명명 방식에 대한 표준은 없으며 어떤 경우에는 
같은 컴파일러라도 버전이 다르면 이름이 다르게 작성됩니다. 둘째, 다형성이 
다르게 구현됩니다. 양 측에 대해 래퍼 클래스를 작성하여 이 문제를 처리할 수 
있지만 상당한 어려움이 따릅니다. Dr.GUI는 그렇게까지 힘이 들지는 않습니다.

명명 방식 문제를 해결하여 DLL에 성공적으로 연결하더라도 개체를 업데이트할 때 
다른 문제가 발생합니다.

첫째, 개체를 업데이트할 때 개체에 가상 함수를 추가하면 그럭저럭 임시 방편으로 
해결할 수는 있습니다. 끝에 새 함수를 추가하면 된다고 생각할 수도 있겠지만 
그렇지 않습니다. 계승한 모든 개체에 대한 vtable 항목을 바꾼 것에 불과합니다. 
가상 함수를 호출하려면 올바른 함수를 호출할 수 있도록 vtable안에 고정된 
오프셋이 필요하기 때문에 적어도 개체 또는 파생된 개체를 사용하는 모든 
프로그램을 다시 컴파일하지 않고는 vtable로 바꿀 수 없습니다. 즉, 개체를 
업데이트 할 때마다 다시 컴파일해야 합니다.

둘째, 클라이언트에서 new 명령을 사용하여 개체를 할당하는 경우, 해당 개체를 
모두 다시 컴파일하지 않고는 개체의 크기를 바꿀 수 없습니다. 즉 어떠한 
데이터도 추가할 수 없습니다.

마지막 내용이 가장 중요한데, DLL을 덮어씀으로써 "내부에" 업데이트하거나 
새로운 버전으로 이름을 바꾸는 두 가지 옵션을 가진 난관에 봉착하게 되기 때문에 
DLL을 업데이트하는 것은 끔찍한 작업입니다. 내부에 DLL을 업데이트하는 것은 
매우 나쁜 방법입니다. 인터페이스를 지속적으로 유지하더라도 사용자가 DLL을 
사용할 수 없게 될 확률이 매우 높습니다. Dr.GUI가 Microsoft를 포함한 업체들이 
이 문제로 인하여 야기했던 모든 문제에 대해 여러분에게 알려줄 필요는 없습니다.

대신에 새 DLL 이름을 사용하면 적어도 시스템 작업을 지속하게는 될 것입니다. 
하지만 일반 하드 디스크가 3기가 바이트 정도이면 그리 큰 정도는 아니지만 
디스크 공간에 대한 대가와 메모리 사용의 증가라는 부대 비용이 들 것입니다. 
사용자가 두 버전의 DLL을 사용하고 있다면 사용자의 작업 설정에 매우 비슷한 두 
가지 코드 복사본이 있을 것입니다. 예를 들어 사용자의 메모리 사용을 조사하여 
두세 버전의 Visual Basic 실행 모드 또는 Microsoft Foundation Class (MFC) 
DLL을 찾을 때는 이상한 것도 아닙니다. 거의 모든 Windows 시스템이 일반적으로 
물리적 메모리보다 더 많은 가상 메모리를 사용한다고 가정하면 디스크로 교체되는 
증가된 가상 메모리의 형태에 심각한 실행 결과가 초래됩니다. 반대 예제를 Brook 
법칙으로 바꿔 쓰려고 느린 시스템에 많은 메모리를 추가하면 시스템이 더 
빨라지기 때문입니다.

이상적으로 사용자 또는 응용 프로그램이 사용할 버전을 선택할 수 있게 하는 것이 
좋습니다. 이렇게 하면 정적으로 DLL을 연결하기가 매우 힘이 들지만 동적으로 
DLL을 로드하기는 대단히 쉽습니다.

C++에 대해 공정하게 말하자면 C++가 결코 이러한 문제들을 해결하고자 하지 
않았다는 점에 유의해야 합니다. C++는 한 파일에 있는 프로그램에서 코드를 
복구할 수 있도록 고안되었습니다. 따라서 모든 개체가 동시에 컴파일됩니다. 
C++에 모든 버전 및 생산 년도에 혼합되어 일치될 수 있는 재 사용 가능 2진 구성 
요소를 작성하는 방법이 제공되지는 않았습니다. 여하튼 Java 작성자들은 이러한 
문제를 알게 되었고 이러한 결함이 나중에 Java가 된 Oak의 개발을 위한 주요 
동기가 되었습니다.

Java와의 상관 관계는?
Java가 이러한 문제를 해결하였지만 여전히 일부 자체적인 문제를 추가하고 
있습니다. 가장 큰 문제는 Java의 구성 요소(일반적으로 JavaBeans)가 Java로 
기록한 프로그램에 의해서만 사용되도록 고안되었다는 것입니다. 지금은 Microsoft 
virtual machine (VM)을 사용하여 JavaBeans을 COM 개체로 사용할 수 있습니다. 
따라서 모든 언어로부터 JavaBeans를 사용할 수 있습니다. Sun에는 Java/ActiveX 
브리지가 있습니다. 하지만 일반적으로 Windows에서 실행하지 않으면 Java는 단일 
언어 시스템입니다. Java의 구성 요소는 Java 프로그램에서만 사용될 수 있습니다. 
대부분의 구성 요소에 대해 Java를 사용하려면 스크래치로부터 시스템을 다시 
기록하여야 합니다. 물론 고유의 호출을 할 수는 있지만 JNI(Java Native 
Interface)를 사용하여 하려면 커다란 어려움이 있으며 프로그램을 더 이상 다른 
시스템에 설치할 수 없을 것입니다. Dr. GUI는 이러한 것들이 용납되지가 않았기 
때문에 적어도 Windows에 대해서 만큼은 Microsoft virtual machine (VM)이 더 
융통성이 있다는 사실에 기뻐합니다. C++, Visual Basic, 또는 Java뿐 아니라 어떤 
언어도 모든 프로그래머와 모든 문제들에 대해서 정당하지 않습니다.

Java 역시 프로그램을 기록할 때 프로그램에 사용중인 구성 요소가 시스템상의 
지역 요소인지 다른 시스템상의 원격 요소인지 여부를 결정해 주어야 하며 지역 
구성 요소와 원격 구성 요소 사용을 위한 메서드에는 상당한 차이가 있습니다.

Java에는 모든 구성 요소의 필요에 대해 결코 이상적인 대답이 없는 몇 가지 다른 
문제들이 있습니다. 첫째 정말 확고한 방법으로 버전을 다룰지 못합니다. 
Microsoft VM의 패키지 관리자는 이 문제에 대한 많은 도움이 됩니다. 둘째 Java는 
C++보다 조금 더 느립니다. Dr. GUI에 따르면 online Java magazine에 출판된 
최근의 "it's as fast as C++" 벤치마크에서 Java가 성능이 떨어지는 테스트를 
생략하고 있습니다. 두 가지 기억 나는 예는 문자열 및 배열 조작(Java는 각각의 
액세스의 바운드를 확인해야 함)과 초기 메서드 호출(Java는 잡지에서 테스트했던 
것처럼 일련의 호출이 빠를 수는 있더라도 최초 호출을 위해 클래스의 테이블에 
서명하여 메서드를 조사해야 합니다. 마지막으로 Java의 "한 번에 한 클래스" 로드 
스키마에는 훨씬 더 많은 파일 또는 HTTP 트랜잭션이 필요하고 이 두 가지 모두는 
높은 오버헤드를 갖기 때문에 비록 코드가 적더라도 한 번에 모든 코드를 로드하는 
것보다는 상당히 느려질 수 있습니다.

좋은 성능을 제공하는 방식으로 Java를 사용하고 있더라도 다른 언어로부터 Java 
구성 요소를 사용할 때는 다른 언어와 개체 모델을 연결해야 하는 트랜잭션 층 
때문에 성능이 떨어질 것입니다.

Java가 두드러지는 점은 각 시스템의 프로세서와 운영 시스템을 다시 컴파일하지 
않고 다른 시스템에 컴파일된 구성 요소를 사용할 수 있다는 가능성에 있습니다. 
하지만 종종 “정상 작동”하지 않기 때문에 지원할 모든 플랫폼에 대해 
테스트하고 디버그해야 합니다.

그렇다면 대안은 무엇인가?
공교롭게도 C++를 사용하여 다시 사용할 수 있는 DLL 및 다른 2진 구성 요소를 
작성할 수 있습니다. Dale Rogerson의 책 “Inside COM”과 Don Box의 책 
“Essential COM”은 다시 사용할 C++ 클래스로부터 시작하여 위에서 언급한 
각각의 문제들을 몇 가지 교묘한 기술을 사용하여 해결하고 있습니다. 놀랄 것도 
없이 이 두 책은 모두 COM으로 끝을 맺습니다. 다시 말해서 2진 구성 요소 재사용 
문제의 해결책은 COM의 중요한 기능인 것입니다. 이러한 진행을 확인하려면 Markus 
Horstmann의 기사, "From CPP to COM"를 검토하십시오.

COM의 "고유 언어"는 C++입니다. 헤더가 지원하기만 하면 C로부터 COM을 사용하는 
것은 상당히 쉽습니다. 언어를 약간만 조정하여 Visual Basic, Java, Delphi등과 
같은 다른 언어로부터 양방향 COM을 지원할 수 있습니다. 즉 양방향 COM을 
지원하여 언어로부터 COM 개체의 사용 및 언어에 COM 개체의 기록을 할 수 
있습니다. 언어 실행 모드에 COM 호환성을 지원하는 것은 사소한 일이 아니라 
이점이 많습니다. 호환성을 지원하면 사용을 위해 이미 기록하고 디버그한 COM 
개체 전체를 엽니다. 그 곳에는 기록한 COM 구성 요소에 대한 넓은 시장이 
형성되어 있습니다. Giga Information Group의 측정에 따르면 현재 년간 4억불의 
시장이 형성되어 있으며 3년 내에 30억불 규모로 될 것으로 추정합니다(COM 구성 
요소 시장은 Microsoft보다 더 빠르게 성장하고 있습니다!). 이 시장 추정이 
Microsoft가 제공하는 COM 구성 요소를 배제한 제 3자 COM 개체에 대한 것임에 
유의하십시오.

COM의 다른 주요 기능은 프로세스(DLL), 지역(같은 시스템상의 개별 프로세스내의 
EXE)와 원격(배포된 COM 또는 DCOM을 경유하는 다른 시스템상의 DLL 또는 EXE)으로 
지원되는 세 가지 형식의 개체입니다. 사용을 종료할 COM의 형식에 대한 우려(또는 
지식)없이 COM 구성 요소를 사용하는 코드를 기록하여 프로세스, 지역 또는 원격 
개체 훅에 정확히 일치하는 코드를 사용합니다. COM이 올바른 개체에 훅하는 
방법은? 레지스트리 내의 개체의 Class ID를 조사하고 레지스트리 입력항목은 
사용할 수 있는 개체의 형식을 COM에 알려줍니다. COM은 네트워크를 통한 작업 및 
통신의 시작을 포함하여 나머지 작업을 합니다. 주의: 고려해야 할 다른 형식의 
COM 개체들 사이에는 기능의 차이가 있지만 적어도 연결할 코드와 개체의 사용은 
사용하는 개체의 형식에 관계없이 정확히 일치합니다.

그러나 COM이 모든 문제들을 해결하지는 않습니다. 예를 들어 구성 요소를 
업데이트할 때 구성 요소를 사용하는 프로그램을 중단시킬 가능성이 여전히 
있습니다. 사실 COM이 지원하는 세부 사항을 찾을 수 없는 구성 요소의 "black 
box" 보기를 사용하여 중단을 평소보다는 줄여주지만 여전히 중단이 발생됩니다. 
따라서 여전히 적당한 위치에 구성 요소를 업데이트하는 것과 중단의 위험과 새 
구성 요소에 대한 새 Class ID사용 사이에서 선택을 해야 합니다. 하지만 COM을 
사용하면 사용자 또는 응용 프로그램이 다시 컴파일하지 않고 사용할 버전을 
선택하기가 좀 더 쉬워집니다.

다시말해 대부분의 언어로 COM 구성 요소를 기록하여 사용할 수 있으며 COM 구성 
요소는 모든 시스템상에 있을 수 있습니다. 하지만 역플랫폼 지원에 대해서는 
어떻습니까?

역플랫폼에 대해서는 좋은 소식과 나쁜 소식이 있습니다. 나쁜 소식은 Win32를 
제외한 모든 플랫폼에 COM이 없다는 것입니다. 일부 비 Windows 플랫폼에 COM에 
대한 약간의 포트가 있지만 많지는 않습니다. 그러나 이것이 소식의 전부는 
아닙니다.

좋은 소식은 대부분의 일반 UNIX 버전과 MVS에 곧 더 많은 포트가 생긴다는 
것입니다. 더욱이 Microsoft가 자체적으로 일부 포트 작업을 진행하고 있습니다. 
따라서 오래지 않아 COM 및 DCOM을 즐겨찾기 주 프레임 및 UNIX 상자에서 사용할 
수 있을 것입니다. UNIX에 대한 사용이 2월에 발표될 예정입니다. 시스템상의 
언어(Visual Basic, Java, Delphi, C++)로부터 액세스할 수 있는 다소 빠른 주 
프레임에서 실행하는 언어로 기록된 원격 COM 개체가 생긴다니 얼마나 기쁜 
일입니까! Microsoft COM 웹 사이트(http://www.microsoft.com/com/)를 
검토하십시오.

Windows용으로 개발 중이라면 Visual Basic, Java, C++, Delphi, 또는 기타 COM 
호환 언어로 개발하고 있는 지에 상관없이 분명히 COM 개체의 기록을 고려할 
것입니다. 기록하는 개체는 지역 시스템상에서 사용할 수 있거나 구성 요소 또는 
구성 요소 클라이언트를 다시 작성하지 않고 원격적으로 사용할 수 있을 것입니다. 
COM 및 DCOM의 마력 덕택입니다. Windows가 아닌 다른 플랫폼에서 실행하기 위한 
해결책이 필요하다면 COM에 더 잘 나타날 것입니다. 따라서 여전히 조사하고 
심각히 고려해 볼 만한 가치가 있음이 분명합니다.

다음 계획: 알아야 할 기본 COM 개념

다음 주에 기본 COM 즉 개체 인터페이스 및 모듈에 대하여 언급할 것입니다. 
시간이 없으면 2주 후에 하겠지만 시간이 되면 예제 COM 개체에 대한 C++ 코드의 
조사를 시작할 것입니다.

2부: COM의 기본
위치한 장소: 가야할 장소
1부에서 good doctor는 C++와 같은 언어가 2진 구성 요소로부터 소프트웨어를 
작성할 수 있는 문제를 해결하지 못하는 이유에 대해 언급하였습니다. 토론의 
요지는 C++가 이 문제를 해결하고자 고안되지 않았으며 단일 실행 프로그램을 위해 
원본 코드를 쉽게 다시 사용할 수 있도록 고안되었다는 것입니다. C++는 그 목표에 
잘 부합됩니다. 그러나 구성 요소를 변경할 때마다 시스템의 구성 요소 또는 모든 
시스템을 다시 사용하지 않고 다른 벤더로부터 구성 요소를 혼합하여 일치시킬 수 
있기를 바랍니다. C++가 이 시나리오에 적용되지 않는 많은 이유가 있습니다.

그렇다면 그 이유는 무엇입니까? C++를 포기해야 합니까? 아닙니다. 사용했던 것과 
조금은 다른 방식으로 사용해야 합니다. C++로부터 COM의 사용 방법이 바로 우리가 
다음에 토론해야 할 것입니다.

C++ 프로그래머가 아니면 읽기를 중지해야 한다는 의미입니까? 아닙니다. 왜냐하면 
어떤 COM 호환 언어(Visual Basic, Visual J++, Delphi, 및 기타)를 사용하고 
있더라도 후드아래서(또는 운영 룸에서?) 토론한 것들을 하게 될 것이기 
때문입니다. 따라서 이 내용을 읽는 다면 ActiveX가 제어하는 방법과 COM 구성 
요소가 작동하는 방법에 대한 값진 통찰력을 얻게될 것입니다.

O.K. 그렇다면 COM이란 무엇입니까?
COM(Component Object Model)은 소프트웨어 구성 요소가 서로 통신하는 
방식입니다. 시스템이 연결되어 있는 한 실행하고 있는 시스템과 COM을 지원한 
다면 시스템이 실행 중인 운영 시스템 및 구성 요소를 기록하는 언어에 관계없이 
두 구성 요소가 통신할 수 있는 2진 네트워크 표준입니다. COM에는 더 많은 위치 
투명성이 제공됩니다. 구성 요소에 다른 구성 요소가 프로세스 DLL인지 지역 
EXE인지 아니면 다른 시스템에 있는 구성 요소인 지를 기록하는 시간관 관계가 
없습니다. 물론 기능이 함축되어 있지만 다른 구성 요소의 위치를 바꾸기 위해 
다시 기록할 필요는 없습니다. 이것이 핵심입니다.

개체
COM은 개체를 기초로 하지만 개체가 C++ 또는 Visual Basic에서 사용했던 그 
개체는 아닙니다. 어쨌든 개체와 구성 요소는 매우 흡사한 것입니다. Dr. GUI는 
응용 프로그램 구조에 대해 이야기할 때는 “구성 요소”를 지원에 관해 이야기할 
때는 “개체”를 말하는 것일 겁니다.

첫째 COM 개체는 잘 보호되어 있습니다. 개체의 내부 기능을 액세스할 수 없으며 
개체가 사용할 데이터 구조를 알 수 없습니다. 사실 개체가 잘 보호되어 있어서 
COM 개체는 일반적으로 상자로 그려집니다. 그림 1은 완전히 보호된 개체에 대한 
그림입니다. 기능의 세부 사항은 숨겨집니다.



그림 1. 완전히 보호된 비 COM 개체

보호는 매우 잘 되어 있지만 통신은 어떻습니까? 현재로는 상자 내에서 구성 
요소와 통신할 방법이 없습니다. 분명히 할 수 없습니다.

인터페이스: 개체와 통신
인터페이스가 오는 장소입니다. COM 개체를 액세스하는 유일한 방법은 
인터페이스를 통하는 것입니다. 그림 2에서처럼 한 개체에 IFoo라는 개체를 끌 수 
있습니다.



그림 2. 인터페이스가 있는 개체?아직은 COM이 아님

개체의 옆에 튀어나온 막대기 사탕처럼 보이는 것이 인터페이스입니다. 이 
경우에는 IFoo 인터페이스. 이 인터페이스가 이 개체와 통신하는 유일한 
방법입니다. good doctor는 인터페이스를 막대기 사탕이라기 보다는 플러그 
커넥터처럼 생긴 것으로 생각하는 것이 더 유용함을 압니다. 개체의 기능을 
연결하는 방법입니다. VCR이나 TV에 입력하는 안테나와 같은 것으로 생각하십시오.

인터페이스는 두 가지가 있습니다. 첫째 어떤 작업을 하기 위해 개체를 호출하여 
로드할 수 있는 함수의 집합. C++에서 인터페이스는 추상적인 기본 클래스로 
묘사됩니다. 가령 IFoo의 정의는 다음과 같습니다.

class IFoo {
   virtual void Func1(void) = 0;
   virtual void Func2(int nCount) = 0;
};

지금은 반환 형식과 계승을 무시할 것이지만 . . .  인터페이스에 하나 이상의 
함수가 있을 수 있으며 모든 함수는 순수 가상 함수임을 유의하십시오. 클래스 
IFoo에서 지원하지 않습니다. 여기서는 동작을 정의하지 않습니다. 인터페이스에 
있는 기능만을 정의합니다. 실제 개체에는 지원 기능이 있어야 합니다. 물론 이 
후에는 더 많은 기능이 있어야 합니다.

둘째 인터페이스는 구성 요소와 클라이언트 사이의 계약입니다. 다시 말해서 
인터페이스는 사용할 수 있는 기능을 정의할 뿐 아니라 기능을 호출할 때 개체가 
하는 작업도 정의합니다. 이 의미론적 정의는 개체에 대한 특정 지원 기능에 대한 
용어가 아니기 때문에 C++에 특정 지원 기능을 제공할 수 있다고 하더라도 C++ 
코드로 묘사할 수 있는 방법은 없습니다. 오히려 정의는 개체의 동작에 대한 
용어이기 때문에 인터페이스(계약)를 지원하기도 하는 개체 그리고/또는 새 개체에 
대한 개정이 가능합니다. 사실 개체는 계약이 정상적인 한 선택한 모든 방식으로 
계약을 자유롭게 지원합니다. 다시 말해서 계약은 원본 코드로부터 문서로 되어야 
합니다. 클라이언트가 원본 코드를 불러올 수 없으며 불러올 필요도 없기 때문에 
이것은 특별히 중요합니다.

일반적으로 특정 계약의 공시는 COM 및 구성 요소 소프트웨어에 중요합니다. 
“엄격한” 계약이 없이는 구성 요소를 서로 바꾸는 것이 불가능합니다.

인터페이스 계약은 다이아몬드처럼 영원합니다
COM에서 구성 요소를 설치하여 인터페이스 계약을 “인증”하게 되면 계약은 
불변입니다. 어떤 식으로든 변경할 수 없습니다. 추가할 수 없습니다. 삭제할 수 
없습니다. 변경할 수 없습니다. 이유는? 다른 구성 요소들이 계약에 종속되기 
때문입니다. 계약을 변경하면 그 소프트웨어를 중단하게 됩니다. 계약을 인정하는 
동안은 내부 지원 기능을 개선할 수 있습니다.

일부를 잊어버리면 어떻게 합니까? 필요 사항이 변경되면 어떻게 합니까? 환경을 
개선하는 방법은 무엇입니까?

대답은 간단합니다. 새 계약을 작성합니다. 표준 OLE 인터페이스 목록에는 
IclassFactory, IClassFactory2, IViewObject와 IViewObject2등이 많이 있습니다. 
물론 IFoo2를 제공할 수 있습니다. 이제는 규약에 의해 인터페이스 이름이 대문자 
I로 시작한다는 것을 안다고 확신합니다.

새 계약을 작성한다면 이전 계약에 대해서만 알고있는 소프트웨어가 여전히 구성 
요소를 사용하는 방법은 무엇입니까? 새 계약이 이전 구성 요소를 엉망으로 
만들지는 않습니까?

COM 개체는 다중 인터페이스를 지원할 수 있으며 다중 계약을 지원할 수 있습니다
대답은 역시 아니오 입니다. 이유는 간단합니다. COM에서 개체는 다중 
인터페이스를 지원할 수 있습니다. 사실 모든 유용한 COM 개체는 적어도 두 개의 
인터페이스를 지원합니다(적어도 표준 IUnknown 인터페이스와 개체가 할 작업을 
하는 인터페이스). Visual ActiveX 컨트롤은 약 12개의 인터페이스를 지원합니다. 
대부분은 표준 인터페이스입니다. 구성 요소가 인터페이스를 지원하려면 
인터페이스의 각각 모든 메서드를 이행해야 합니다. 따라서 매우 방대한 
작업입니다. 이것이 바로 ATL(Active Template Library)등과 같은 도구들이 인기가 
좋은 이유입니다. 이 도구들은 모든 인터페이스에 대한 실행을 지원합니다.

새 IFoo2 기능을 제공하려면 이 개체에도 IFoo2를 추가합니다.



그림 3. 버전 2.0, IFoo와 IFoo2를 지원하지만 아직 COM 개체는 아닙니다

아직 플러그에 대해 생각하고 있다면 IFoo을 TV에 대한 안테나 입력으로 IFoo2는 
복합 비디오 입력으로 생각하십시오. 안테나 케이블을 복합 비디오 입력에 끼울 
수는 없습니다. 물론 반대로도 마찬가지 입니다. 다시 말해서 각 인터페이스는 
논리적으로 고유합니다.

다시 말해서 인터페이스에는 공통점이 있습니다. 이전 것과 거의 같은 새 
인터페이스를 추가하려면 전체 지원 기능을 다시 써야 됩니까? 아닙니다. COM이 
인터페이스의 계승을 지원하기 때문입니다. 이미 IFoo에 있는 기능을 바꾸지 않는 
한 IFoo2를 다음과 같이 정의할 수 있습니다.

class IFoo2 : public IFoo {
   // Inherited Func1, Func2
   virtual void Func2Ex(double nCount) = 0;
};

인터페이스 복습
복습. 첫째 COM은 소프트웨어 개체 상호 작용에 대한 2진 표준입니다. 2진 
표준이기 때문에 개체는 사용하는 개체의 지원 세부 사항을 모르며 알 수도 
없습니다. 따라서 개체는 블랙 박스입니다.

개체가 노출하는 인터페이스를 통해서만 블랙 박스 개체를 조작합니다. 마지막으로 
주어진 개체는 선택한 만큼의 인터페이스를 노출할 수 있습니다.

간단히 옳습니까?

많은 세부 사항들을 무시했었습니다. 개체가 작성되는 방법은? 인터페이스를 
액세스하는 방법은? 인터페이스의 메서드를 호출하는 방법은? 어떻든 개체를 
이행하는 장소는? 성가신 개체를 마지막으로 파기할 시간은?

많은 질문들이 있지만 불행하게도 Dr. GUI가 늦어서 수술을 하지 못했습니다. 
따라서 이 대답은 3부로 연기해야 합니다. 지금은 good doctor는 한 가지 문제 즉 
인터페이스 메서드가 호출되는 방법은?을 다룰 것입니다.

인터페이스 메서드 호출
다음에 오는 것들을 이미 보았을 수도 있지만 간단하게 끝이 납니다. COM 메서드 
호출은 단순히 C++ 가상 기능 호출입니다. 인터페이스를 이행하는 개체에 대한 
약간의 지침을 구할 것입니다(자세한 내용은 3부에서). 그런 다음 인터페이스의 
메서드를 호출할 것입니다.

첫째 IFoo 인터페이스를 이행하는 CFoo라는 C++ 클래스가 있다고 가정합니다. 
적절한 순서로 적절한 인터페이스를 이행함을 확인하려고 IFoo으로부터 계승함에 
유의하십시오.

class CFoo : public IFoo {
   void Func1() { /* ... */ }
   void Func2(int nCount) { /* ... */ }
};

사용하는 포인터를 인터페이스 포인터라고 부를 것입니다. 하나의 포인터를 불러올 
수 있다고 가정하면 코드는 다음과 비슷할 것입니다.

#include <IFOO.H // Don't need CFoo, just the interface
void DoFoo() {
  IFoo *pFoo = Fn_That_Gets_An_IFoo_Pointer_To_A_CFoo_Object();

  // Call the methods.
  pFoo -> Func1();
  pFoo -> Func2(5);
};

실제로 그렇게 간단합니다.

진실로 저면에 깔려있는 것은 무엇입니까? 공교롭게도 COM 2진 표준이 메서드 
호출에도 적용됩니다. 따라서 COM은 기능을 호출하기 위해 발생하는 것들을 
정의합니다. 특별히 가상 기능을 호출하는 동안에 발생하는 것과 같은 것이 
발생합니다. 

pFoo를 참조하지 않고 개체에서 vtable 포인터를 찾습니다.


vtable 포인터를 참조하지 않고 색인으로 만들어 호출할 함수의 주소를 찾습니다.


함수를 호출합니다. 
많은 단계에 대해서는 그림 4를 참조하십시오.



그림 4. 인터페이스 포인터를 통한 C++ 가상 함수 호출

C++에서 가상 함수가 있을 때는 이 함수를 나타내는 vtable이 있으며 항상 
vtable의 색인에 의해 호출됨을 명심하십시오.

"아하!" "알았다. COM은 정말 C++와 연결되어 있구나! 결코 2진 표준이 
아니구나!"할 것입니다.

Dr. GUI의 대답은 "그렇지 않습니다"입니다. 결국 함수 포인터의 배열을 지원하는 
모든 언어로 이 호출을 이행할 수 있습니다. C에서 쉽게 합니다. 예를 들어 포인터 
p를 통하여 Func2를 다음과 같이 호출합니다.

(*((*p)+1))(p, 5); // passing 5 to the 2nd function in the array

p를 첫째 매개변수로 전달해야 함을 유의하십시오. 이렇게 하면 C++를 이 포인터로 
가정합니다. (*p)는 첫째 술책(단계1)입니다. *((*p) + 1)는 vtable의 
색인(단계2)이며 다음에 p와 5가 있는 함수를 호출(단계3)합니다. 이 방법은 
쉽지만 불순한 방법입니다. Dr. GUI에 나타난 바로는 사용자가 이 방법을 알고 
있어야 하며 직접 C++를 평가해야 합니다. x86 어셈블 언어로 다음과 같이 나타낼 
수 있습니다.

MOV EAX, [pFoo]      ; step 1
MOV EAX, [EAX + 4]   ; step 2, indexed to 2nd pointer
CALL [EAX]          ; step 3

Dr. GUI는 EAX에 함수의 주소를 유지하지 않아도 되려면 둘째와 셋째 지시사항이 
CALL[EAX + 4]에 결합되어야 한다고 인식하고 있습니다.

good doctor에 모든 것을 이렇게 자세히 표시하는 이유는 무엇입니까? 어셈블어나 
C언어로 이 작업을 할 수 있다면 모든 언어로 이 작업을 할 수 있기 때문입니다. 
다른 언어(Visual Basic, Visual J++, Delphi)는 실행 모드로 또는 가상 
시스템으로 이러한 호출을 지원합니다. 종종 어셈블어나 위와 비슷한 C 코드를 
사용하여 지원하기도 합니다.

요점은 모든 COM 메서드 호출이 고유 언어가 무엇인지 또는 COM 개체가 어디에 
있는 지에 관계없이 호출을 위해 표시한 데이터 구조를 사용해야 한다는 것입니다. 
뒤에 나올 칼럼에서 COM이 위치 투명성을 획득하는 방법을 토론할 것입니다.

위치한 장소: 가야할 장소
O.k. . . . 지금까지 인터페이스, 개체, 및 인터페이스 매서드를 호출하는 방법에 
대해 토론하였습니다.

개체는 COM의 기본 단위이며 COM이 작성합니다. 개체는 일부 인터페이스 집합을 
지원합니다. 인터페이스는 메서드 집합이며 메서드 작업에 대한 계약입니다. 
C++가상 함수를 호출하는 것과 동일한 방법으로 인터페이스 메서드를 호출합니다.

3부에서 개체 작성 방법, 인터페이스 포인터를 불러오는 방법 및 개체를 제거하는 
방법을 언급할 것입니다.

3부: 개체 및 인터페이스 가져오기
2부에서 COM의 두 가지 기본 개념 개체와 인터페이스에 대해 토론하였습니다. 
개체가 하나 이상의 인터페이스를 이행하는 방법에 대해서도 알아보았습니다. 
마지막으로 COM 메서드 호출이 이루어지는 핵심 방법에 대해 토론하여 C++ 가상 
함수 호출과 동일함을 알았습니다. 그러나 지원하는 모든 언어로 이 작업을 하거나 
지원할 어셈블 언어를 호출하거나 함수 포인터의 배열을 위한 포인터를 호출할 
수도 있음도 알게되었습니다.

COM에 관한 자세한 내용: 개체 작성 및 삭제, 인터페이스 포인터 가져오기
토론에 대해 다소 불만스런 점이 있을 수도 있습니다. 즉 개체 작성 방법에 
대해서는 결코 토론한 적이 없으며 개체 인터페이스에 메서드를 불러 오기 위해 
인터페이스 개체를 불러오는 방법에 대해서도 토론한 적이 없습니다. 또한 더 이상 
필요없는 개체를 제거하는 방법이나 인터페이스를 변경하는 방법에 대해서도 
토론한 적이 없습니다.

good doctor는 이번주 토론에서 이 항목들에 대한 빛을 밝혀줄 것입니다. 하지만 
첫째 하우스키핑에 관해 약간의 관심을 기울여야 합니다. 무언가에 대해 설명을 
하게 되는 일이 얼마나 자주 있는 지를 압니다. 잃어 버렸던 중요한 일들을 갑자기 
기억해 내기도 합니다. 여기서도 마찬가지 입니다. good doctor는 개체 작성을 
위해 참고할 방법과 인터페이스를 명확히 정의할 방법이 있을 수도 있다는 사실을 
잊어버렸습니다. 따라서 잃어버렸던 부분에 대해 언급한 다음 정말 재미있는 
재료들로 채워가겠습니다. 이것이 이 칼럼이 이렇게 길어지고 늦어진 이유입니다.

COM의 식별자
미리 생각해 보았다면 COM 세계의 다양한 입력항목에 대한 식별자가 필요할 
것이라는 것을 알았을 것입니다. 첫째 개체 형식 또는 클래스에 대한 식별자가 
필요할 것입니다. 둘째 각각의 인터페이스에 대한 식별자가 필요할 것입니다. 
식별자로 무엇을 사용해야 합니까? 32-bit int? 64-bit int? 그런 것을 사용할 
수도 있을 것입니다. 하지만 문제가 있습니다. 식별자는 모든 시스템에 대해 
고유해야 합니다. 그렇기 때문에 구성 요소를 설치할 시스템이 무엇인지를 알 수가 
없습니다. 개체와 인터페이스는 클라이언트가 구성 요소를 사용할 수 있도록 모든 
시스템상에 같은 식별자를 필요로 합니다. 더욱이 다른 개체나 인터페이스는 
어디에서 왔는지에 상관없이 그 식별자를 사용할 수 없습니다. 다시 말해서 이 
식별자는 전체적으로 유일한 것이어야 합니다.

다행히도 그런 식별자들을 작성하기 위해 알고리즘과 데이터 형식이 존재합니다. 
시스템의 고유 네트워크 카드 ID, 현재 시간 및 기타 데이터를 사용하여 
GUIDGEN.EXE라는 프로그램이 GUID(globally unique identifiers ? 전역 고유 
식별자)라는 식별자를 작성합니다. GUID는 16바이트(128비트) 구조로 저장되며 
2128에 가능한 GUID를 제공합니다. GUID가 실행되지 않을 것을 걱정하지 마십시오. 
COM 개업의의 농담임에도 불구하고 비록 good doctor가 우주에 있는 정확한 원자의 
수를 알아낼 수는 없었지만 웹을 검색한 후에 2128보다는 훨씬 적어서 GUID가 
부족한 일은 없으며 GUID 유지의 필요가 없다고 믿게 되었습니다.

C++에는 GUID, CLSID(class identifier GUID ? 클래스 식별자 GUID), 및 
IID(interface identifier GUID ? 인터페이스 식별자 GUID)용 COM 헤더 파일이 
정의하는 데이터 형식이 있습니다. 이 16 바이트 구조는 값으로 전달하기에는 큰 
종류의 구조이기 때문에 REFCLSID와 REFIID를 GUID를 전달하는 매개변수의 데이터 
형식으로 사용합니다. 기록한 각 개체 형식에 대해 CLSID를 작성하고 작성한 각 
사용자 인터페이스에 대해 IID를 전달해야 합니다.

표준 인터페이스
COM은 많은 표준 인터페이스와 관련 IID를 정의합니다. 가령 모든 인터페이스의 
모든 인터페이스의 상위인 IUnknown은 
"00000000-0000-0000-c000-000000000046"(하이픈은 표준 방식으로 GUID를 작성하는 
구성 요소 입니다. 이 IID는 COM에 의해 정의되며 결코 직접 참조할 필요는 
없습니다. 대신에 헤더에서 사용자가 정의한 기호 IID_IUknown을 사용하십시오.

IUnknown 인터페이스에는 다음의 세가지 기능이 있습니다.

HRESULT QueryInterface(REFIID riid, void **ppvObject);
ULONG AddRef();
ULONG Release();

이 기능에 대해서는 나중에 명확히 토론하겠습니다.

표준 인터페이스를 사용하여 많은 COM 프로그래밍 작업을 할 것입니다. 이 작업 중 
많은 부분은 다른 COM 클라이언트와 개체가 이 개체를 사용할 수 있도록 표준 
인터페이스용 기능을 제공합니다.

그런데 기능의 반환 형식 및 호출 규정을 설명하기 위해 COM에서 사용되는 일부 
매크로가 있습니다. 거의 모든 COM 메서드는 HRESULT를 반환합니다. 따라서 매크로 
STDMETHODIMP는 HRESULT를 반환한다고 가정합니다. 매크로 STDMETHODIMP_()에는 
매개변수(매서드의 반환 형식)가 있습니다. 인터페이스를 순수 가상 기능으로 
정의할 때는 STDMETHOD 매크로만을 사용합니다. IDL 컴파일러가 이 코드를 
기록합니다. 자세한 내용은 나중에 설명하겠습니다.

이 매크로를 사용하여 위에서 설명한 내용은 다음과 같습니다.

STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();

지금부터는 이 매크로를 사용할 것입니다. 그렇게 하면 코드를 Macintosh 및 
Solaris와 같은 다른 COM 플랫폼으로 코드를 이동하기가 쉬워집니다.

사용자 정의 인터페이스
사용자 정의 인터페이스는 사용자가 작성한 인터페이스입니다. 이 인터페이스에 
대한 자신의 IID를 작성하고 기능에 대한 자신의 목록을 정의할 것입니다. IFoo 
인터페이스는 사용자 정의 인터페이스입니다. 시스템상의 GUID 작성기를 사용하여 
"13C0205C-A753-11d1-A52D-0000F8751BA7"값을 갖는 IID_IFoo라는 IID를 
정의했었습니다.

클래스 선언이 독창적임을 상기하십시오.

class IFoo {
   virtual void Func1(void) = 0;
   virtual void Func2(int nCount) = 0;
};

이것을 약간을 변경하여 COM 호환이 되도록 변경할 것입니다.

interface IFoo : IUnknown {
virtual HRESULT STDMETHODCALLTYPE Func1(void) = 0;
virtual HRESULT STDMETHODCALLTYPE Func2(int nCount) = 0;
};

위에서 설명한대로 매크로를 사용하면 다음과 같이 됩니다.

interface IFoo : IUnknown {
STDMETHOD Func1(void) PURE;
STDMETHOD Func2(int nCount) PURE;
};

단어 "interface"는 C++의 키워드가 아니라 적절한 COM 헤더에서 “struct”로 
#정의됩니다. C++클래스와 구조는 기본적으로 private가 아니라 public 계승 및 
액세스를 사용하는 같은 예외입니다. STDMETHOD는 __stdcall로 정의된 
STDMETHODCALLTYPE을 사용하며 컴파일러가 이 기능에 대한 시퀀스를 호출하는 표준 
기능을 생성해야 함을 나타냅니다. 이 매크로를 사용하는 이유는 코드를 다른 
플랫폼에 이식할 때 매크로의 정의가 바뀌기 때문임을 명심하십시오.

모든 COM 기능은 거의 예외없이 HRESULT 오류 코드를 반환합니다. HRESULT는 신호 
비트를 사용하여 성공과 실패를 표시하는 32 비트 번호이며 “설비”와 설비에 
고유한 오류 코드를 나타내는 나머지 31비트의 필드이며 일부 예비 비트입니다. 
일반적으로 성공 코드 S_OK를 반환할 것이지만 메서드에서 문제가 발생하면 오류 
코드(표준 코드 또는 만든 코드)를 반환할 수도 있습니다.

마지막으로 표준 COM 인터페이스 IUnknown로부터 IFoo를 도출하고 있음을 
유의하십시오. IFoo를 이행하는 모든 클래스는 세 함수 AddRef, Release, 
QueryInterface도 이행해야 함을 의미합니다. 또한 IFoo vtable에는 Func1과 
Func2에 대한 포인터 앞에 이 세 기능에 대한 포인터가 있을 것입니다. vtable에 
총 다섯 함수와 이행을 위한 다섯 함수가 있습니다. All COM 인터페이스는 
IUnknown로부터 도출되기 때문에 모든 COM 인터페이스에는 다른 기능 외에 이 세 
가지 기능이 포함됩니다.

MIDL에서는 무엇입니까?
실제로는 위의 선언을 직접 기록하지는 않을 것입니다. MIDL 컴파일러가 생성할 
것입니다. 이유는? 공교롭게도 C++는 인터페이스에서 표시되어야 하는 모든 것을 
표시할 수 없습니다. COM 개체는 프로세스에서 사용되는 DLL이 될 수도 있음을 
상기하십시오. 이는 같은 주소 공간에 있음을 의미합니다. 포인터를 프로세스 
서버의 다른 데이터로 전달하면 서버는 포인터를 바로 참조해제 할 수 있습니다.

COM 개체는 개별 EXE 주소 공간의 지역(프로세스 외부) 서버이거나 원격적으로 
액세스 될 수도 있음을 상기하십시오. 포인터를 그런 개체의 COM 메서드에 
전달해야 할 때마다 문제가 발생합니다. 이 문제는 포인터가 다른 주소 공간에서는 
의미가 없다는 것입니다. 의미 있는 것은 포인터가 가리키는 데이터입니다. 이 
데이터는 다른 주소 공간 및 어쩌면 뒤로 복사되어야 합니다. 올바른 데이터를 
복사하는 이 작업을 marshalling이라 합니다. 감사하게도 대부분의 경우에 COM이 
이 정렬 작업을 합니다. 하지만 그렇게 하려면 포인터가 나타낼 형식 이상의 것을 
말해줘야 합니다. COM에 포인터를 사용하는 방법을 알려줘야 합니다. 가령 
포인터가 배열을 나타냅니까? 문자열을 나타냅니까? 매개변수는 입력 매개변수만 
있습니까? 출력 매개변수입니까? 둘 다입니까? C++에서는 이것을 표현할 방법이 
없음을 알 수 있습니다.

따라서 인터페이스를 정의하려면 IDL(Interface Definition Language ? 인터페이스 
정의 언어)이라는 다른 언어가 필요합니다. IDL은 C++와 흡사해 보이지만 코드처럼 
C++에 추가된 꺾어진 괄호에 “속성”이 있습니다. MIDL.EXE는 사용자 또는 Visual 
Studio가 다양한 출력을 산출하기 위해 기록하는 IDL 파일을 컴파일합니다. 이제 
관심을 가져야 하는 유일한 출력은 인터페이스에 대한 헤더 파일이며 이것을 
코드에 포함시킬 것입니다.

예제에서는 값만을 전달하였기 대문에 차이가 없습니다. 따라서 IDL 코드가 매우 
비슷해 보입니다. 주요 차이는 단어 “가상”이 빠져있다는 것입니다. 그러나 
메서드 Func3(int *)을 다른 두 메서드에 추가하는 새 인터페이스 IFoo2를 
작성했다면 IDL는 다음과 같을 것입니다.

[ uuid(E312522F-A7B7-11D1-A52E-0000F8751BA7) ]
interface IFoo2 : IUnknown
{
   HRESULT Func1();
   HRESULT Func2(int in_only);
   HRESULT Func3([in, out] int *inout);
};

몇 가지를 유의하십시오. 첫째 꺾어진 괄호에 포함되는 IDL의 다양한 속성이 
있습니다. 속성은 항상 바로 다음 것에 적용됩니다. 따라서 uuid 속성은 
인터페이스에 적용됩니다. 이것은 인터페이스에 대한 IID입니다. UUID 또는 
보편적으로 고유 식별자는 GUID의 동의어입니다. [in, out] 속성은 포인터에 
적용되어 정렬이 필요하다면 Func3를 호출할 때 내/외에 단일 int를 정렬해야 함을 
알려줍니다. int 포인터가 배열을 참조했다면 추가 속성이 있을 것입니다(size_는 
매개변수와 함께 옵니다). 개체를 정의하기 위한 IDL 코드도 있습니다. 가령 
개체를 정의할 코드 조각은 다음과 같을 수도 있습니다.

[ uuid(E312522E-A7B7-11D1-A52E-0000F8751BA7) ]
coclass Foo
{
   [default] interface IFoo;
};

CLSID가 클래스와 관련되는 방법 즉 클래스가 지원하는 인터페이스의 집합을 
정의하는 방법입니다. 비록 이것이 몇 가지 추가 속성이 있는 C++와 매우 비슷해 
보이지만 인터페이스 정의가 응답하는 것처럼 C++코드에 정확히 응답하지 않습니다.

개체 작성
CLSID가 개체 형식과 관련되면(자세한 내용은 나중에 설명) 개체를 작성할 수 
있습니다. 공교롭게도 이 작업은 매우 간단합니다. 한 가지 기능만을 호출하면 
됩니다.

IFoo *pFoo = NULL;
HRESULT hr = CoCreateInstance(CLSID_Foo, NULL, CLSCTX_ALL,
               IID_IFoo, (void **)&pFoo);

CoCreateInstance가 성공하면 CLSID GUID, CLSID_Foo에 의해 식별되는 개체의 
인스턴스를 작성합니다. “개체에 대한 포인터”와 같은 것은 없는 대신 
인터페이스를 통하여 항상 개체를 참조함을 유의하십시오. 따라서 
원하는(IDD_IFoo) 인터페이스를 지정하여 인터페이스 포인터를 저장하도록 
포인터를 CoCreateInstance를 위한 장소로 전달해야 합니다.

아직 토론하지 않은 두 매개변수는 아직은 중요하지 않습니다.

일단 호출하면 호출이 성공되어 진행되어 개체를 사용하는 지를 확인해야 합니다.

if (SUCCEEDED(hr)) {
   pFoo->Func1();   // Call methods.
   pFoo->Func2(5);
   pFoo->Release();   // MUST release interface when done.
}
else // Creation failed...

CoCreateInstance는 HRESULT를 반환하여 성공 여부를 나타냅니다. 마이너스가 아닌 
값은 성공을 의미하므로 항상 SUCCEEDED 매크로를 사용하여 결과를 확인하십시오. 
사실 대부분의 공통 성공 코드 S_OK는 0입니다. 따라서 "if (hr) // Success"와 
같은 확인은 전혀 작동하지 않을 것입니다. 개체를 성공적으로 작성했다면 위에서 
본 것처럼 인터페이스 포인터를 사용하여 인터페이스의 메서드를 호출할 수 
있습니다.

Release를 호출하여 작업할 때 인터페이스 포인터를 해제하는 것이 매우 
중요합니다. 모든 인터페이스는 IUnknown로부터 도출되기 때문에 모든 
인터페이스는 Release를 지원합니다. COM 개체는 작업을 할 때 자신을 자유롭게 할 
책임이 있습니다. 그러나 종료한 시간을 알려주는가에 달려있습니다. 릴리스를 
호출하는 것을 잊어버렸다면 개체는 누설되어 적어도 응용 프로그램이 종료될 
때가지 어쩌면 다시 시스템을 부팅할 때까지 메모리에 잠겨있을 것입니다. 개체 
생명시간을 혼란시키는 것은 매우 일반적인 COM 프로그래밍 문제입니다. 이 문제는 
종종 발견하기가 어렵습니다. 따라서 이제부터는 주의하십시오. 실제로 작성할 수 
있었다면 인터페이스만을 릴리스했음을 유의하십시오.

그림 5는 새롭게 작성한 개체에 대한 다이어그램입니다. 규정에 의해 IUnknown은 
이름표가 없습니다. 항상 개체의 오른쪽 상단 모서리에 그림이 첨가됩니다. 다른 
모든 인터페이스는 왼쪽에 그려집니다.



그림 5. 첫째 간단한 COM 개체, 이름표가 없는 IUnknown

이제 IUnknown를 이행하면 정말로 COM 개체를 갖게 됩니다(커넥터 그림 만큼 
간단하기만 하다면).

IFoo2 인터페이스를 개체에 추가했다면 그림 6에서처럼 총 세 개의 인터페이스가 
있습니다.



그림 6. IFoo와 IFoo2를 지원하는 이론상의 버전 2.0

GUIDs 및 레지스트리
COM이 작성할 개체 코드를 찾는 방법은? 간단합니다. 레지스트리를 조사합니다. 
COM 구성 요소를 설치하면 레지스트리에 입력사항이 만들어져야 합니다. 
Foo클래스에 대한 입력사항은 다음과 같습니다.

HKEY_CLASSES_ROOT
  CLSID
    {E312522E-A7B7-11D1-A52E-0000F8751BA7}="Foo Class"
      InprocServer32="D:\\ATL Examples\Foo\\Debug\\Foo.dll"

대부분의 개체에는 몇 가지 추가 입력사항이 있지만 지금은 무시할 수 있습니다.

HKEY_CLASSES_ROOT\CLSID에 클래스에 대한 CLSID 입력사항이 있습니다. 이것은 
CoCreateInstance가 구성 요소에 대한 DLL 이름을 조사하는 방법입니다. 
CoCreateInstance를 CLSID에 제공할 때 DLL 이름을 찾아서 DLL을 로드하여 구성 
요소를 작성합니다(자세한 내용은 나중에).

레지스트리 입력사항은 서버가 프로세스 밖에 있거나 원격 서버일 때는 약간 달라 
보이겠지만 주요 요지는 COM이 서버를 시작하여 개체를 작성할 정보가 있다는 
것입니다.

개체의 이름(ProgID)은 알지만 개체의 CLSID를 모른다면 레지스트리에서 CLSID를 
찾습니다. 개체에 대한 입력사항은 다음에 있습니다.

HKEY_CLASSES_ROOT
  Foo.Foo="Foo Class"
    CURVER="Foo.Foo.1"
    CLSID="{E312522E-A7B7-11D1-A52E-0000F8751BA7}"
  Foo.Foo.1="Foo Class"
    CLSID="{E312522E-A7B7-11D1-A52E-0000F8751BA7}"

"Foo.Foo"는 버전입니다-independent ProgID와 Foo.Foo.1은 ProgID입니다. Visual 
Basic으로 Foo개체를 작성하면 ProgID중 하나를 사용하여 CLSID를 조사합니다. 
현재 버전에서는 ATL 마법사가 레지스트리 입력사항을 정확히 작성하지는 않고 
위에서처럼 처음 두 CLSID 키를 남겨둠을 유의하십시오. 버전-독립 ProgID에 대해 
CLSID를 복사하는 것을 잊지마십시오.

모듈, 구성 요소 클래스 및 인터페이스
하나의 모듈(DLL 또는 EXE)이 하나 이상의 COM 구성 요소 클래스를 이행할 수 
있음을 유의하십시오. 이 때는 같은 모듈을 참조하는 하나 이상의 CLSID 
입력사항이 있을 것입니다.

따라서 이제 모듈, 클래스 및 인터페이스 사이의 관계를 정의할 수 있습니다. 작성 
및 설치의 기본 단위인 모듈은 하나 이상의 구성 요소를 이행할 수 있습니다. 각 
구성 요소에는 모듈의 파일 이름을 나타내는 레지스트리에 자신의 CLSID와 
입력사항이 있을 것입니다. 그리고 각 구성 요소는 적어도 두 개의 인터페이스 
IUnknown와 구성 요소의 기능을 노출하는 인터페이스를 이행합니다. 그림 7에서 
나타냅니다.



그림 7. 모듈 Oo.DLL에는 세 개체 Foo, Goo 및 Hoo에 대한 지원 기능이 있습니다. 
각 개체는 IUnknown와 하나 이상의 추가 인터페이스를 이행합니다

QueryInterface로 다른 인터페이스 가져오기
두 개의 사용자 정의 인터페이스 IFoo와 IFoo2를 이행하는 새로 개선한 Foo2 
개체가 있다고 가정합시다. CoCreateInstance를 사용하여 그런 개체를 작성하는 
방법과 세 개의 인터페이스(IUnknown를 잊지 말 것)중 하나에 포인터를 불러오는 
방법에 대해서는 이미 알고 있습니다.

인터페이스 포인터를 불러왔을 때 개체의 다른 인터페이스 중 하나에 인터페이스 
포인터를 불러오는 방법은? CoCreateInstance를 다시 불러올 수 없습니다. 새 
개체를 작성할 것입니다. 기존의 개체상의 다른 인터페이스를 불러오기만을 
원합니다.

이것이 IUnknown::QueryInterface가 해결하는 문제입니다. 모든 인터페이스가 
IUnknown로부터 계승되었기 때문에 모든 인터페이스가 QueryInterface를 이행함을 
기억하십시오. 따라서 첫째 인터페이스 포인터만을 사용하여 둘째 인터페이스 
포인터를 불러오도록 QueryInterface를 호출할 것입니다.

IFoo *pFoo = NULL;
HRESULT hr = CoCreateInstance(CLSID_Foo2, NULL, CLSCTX_ALL,
               IID_IFoo, (void **)&pFoo);
if (SUCCEEDED(hr)) {
   pFoo->Func1();   // call IFoo::Func1
   IFoo2 *pFoo2 = NULL;
   hr = pFoo->QueryInterface(IID_IFoo2, (void **)&pFoo2);
   if (SUCCEEDED(hr)) {
      int inoutval = 5;
      pFoo2->Func3(&inoutval);   // IFoo2::Func3
      pFoo2->Release();
   }
   pFoo->Release();
}

QueryInterface에 원하는 인터페이스의 IID와 QueryInterface를 위한 장소에 
포인터를 전달하여 새 인터페이스 포인터를 저장합니다. QueryInterface가 
성공적으로 반환하면 인터페이스 포인터를 사용하여 인터페이스의 기능을 호출할 
수 있습니다.

작업을 완료하면 두 인터페이스 포인터를 해제해야 함을 유의하십시오. 둘 중 
하나를 해제하지 않으면 개체가 누출되어 인터페이스 포인터를 통하지 않고는 결코 
개체를 참고하지 못하기 때문에 대체로 해제될 개체에 대한 순서로 불러온 모든 
인터페이스 포인터를 해제해야 합니다.

IUnknown의 다른 기능
IUnknown에는 두 가지 다른 기능이 있습니다. AddRef와 Release입니다. 릴리스를 
사용하여 개체에 인터페이스 포인터로 작업을 완료하였음을 알려줌을 이미 알아 
보았습니다. 그렇다면 언제 AddRef를 사용할 수 있습니까?

참조 계수와 개체가 자유롭게 되는 시기
대부분의 COM 개체는 참조 계수를 가지고 있습니다. 다시 말해서 개체에 
인터페이스 포인터가 사용중인 계수에 대한 트랙이 있어야 합니다. 모든 개체의 
인터페이스에 관한 참조 계수가 0일 때 자유로워 질 수 있습니다. 개체를 
명시적으로 자유롭게 하지는 못합니다. 단지 모든 개체의 인터페이스 포인터를 
해제하여 개체가 적절한 때에 스스로 자유로워지는 것입니다.

AddRef는 참조 계수를 증가시키고 Release는 감소 시킵니다. 따라서 AddRef를 
호출하지 않았을 때 Release를 호출해야 하는 이유는?

QueryInterface가 새 포인터를 개체에 나눠줄 때마다 포인터를 반환하기 전에 
QueryInterface가 AddRef를 호출해야 합니다. 이것이 바로 QueryInterface가 
불러온 포인터를 위해 AddRef를 호출했던 이유입니다. 개체의 첫째 인터페이스 
포인터에 대해서 조차도 참이 되도록 CoCreateInstance가 AddRef를 호출하는 
QueryInterface를 호출한다는 사실을 유의하십시오.

원한다면 호출한 AddRef가 인터페이스 대 인터페이스 기본으로 참조 트랙을 유지할 
수 있는 같은 인터페이스 포인터상에서 Release를 호출해야 함을 유의하십시오. 
위의 코드에서 주의 깊게 하였습니다. 함축적 AddRef를 올바르게 쌍을 맺으면 
적절한 Release 호출로 호출됩니다. 하나의 Release가 각 인터페이스 포인터를 
요구합니다.

인터페이스에 대한 참조 계수가 정확해 지도록 인터페이스 포인터를 복사하고 
있다면 AddRef를 호출해야 합니다. 이 작업을 해야 하거나 하지 않아야 할 때 
약간의 어려움이 따르지만 다양한 COM 참조에 잘 다루어 지고 있습니다. 
세부사항은 참조를 검토해보십시오.

다양한 스마트 포인터 클래스가 대부분의 IUnknown을 훨씬 더 쉽게 처리할 수 있게 
해줍니다(사실 자동으로). ATL과 Visual C++ 5.0에는 그런 클래스가 몇 가지 
있습니다. Visual Basic이나 Java와 같은 다른 언어를 사용하고 있다면 COM에 대한 
언어 지원 기능이 IUnknown 메서드를 올바르게 처리해 줍니다.

위치할 장소: 가야할 장소
개체를 작성하여 삭제하는 방법(언급한 것이 아니라 모든 인터페이스 포인터를 
릴리스 하기만 하였음) 과 인터페이스 메서드를 호출하여 인터페이스를 변경하는 
방법에 대해 이미 언급하였습니다. 그 방법을 따라서 개체와 인터페이스의 식별에 
사용되는 다양한 GUID의 개념과 COM이 개체를 작성하는 방법을 이해하기 위해 
필요한 레지스트리 입력사항에 대해서도 소개하였습니다.

4부에서 프로세스 개체를 작성하는 방법과 좀 더 효과적으로 작성하는 방법에 
관하여 더 자세히 언급할 것입니다. 시간이 있다면 개체와 IUnknown의 작성을 위해 
필요한 코드를 포함하여 개체 이행의 핵심사항에 대해서도 언급하겠습니다.

4부: Class 개체 및 Class 요소
3부에서 개체의 작성 및 삭제 방법(삭제는 하지 않았고 모든 인터페이스 포인터를 
해제하기만 하였음)과 인터페이스 메서드를 호출하여 인터페이스를 변경하는 
방법에 대하여 언급하였습니다. 그 방법을 따라서 개체와 인터페이스의 식별에 
사용되는 다양한 GUID의 개념과 COM이 개체를 작성하는 방법을 이해하기 위해 
필요한 레지스트리 입력사항에 대해서도 소개하였습니다.

이 번에는 프로세스 개체를 작성하는 방법과 좀 더 효과적으로 작성하는 방법에 
관하여 더 자세히 언급할 것입니다. Class 요소로도 알려진 class 개체와 이행 
방법에 대해서도 언급할 것입니다. 이번 주에는 실제 개체를 이행하는 것에 관하여 
언급할 시간이 없지만 다음주 톱 의제가 될 것입니다.

CoCreateInstance를 호출할 때 발생되는 것은?
이미 CoCreateInstance를 호출하는 방법과 시간에 대하여 언급하였습니다. COM은 
CLSID를 찾기 위해 레지스트리를 검색하여 개체를 이행하는 DLL 또는 EXE를 찾을 
수 있습니다. 하지만 이러한 것들이 발생하는 방법에 관하여는 자세히 설명하지 
않았습니다. CoCreateInstance는 다음의 기능을 요약합니다.

IClassFactory *pCF;
CoGetClassObject(rclsid, dwClsContext, NULL,
      IID_IClassFactory, (void **)&pCF);
hresult = pCF->CreateInstance(pUnkOuter, riid, ppvObj)
pCF->Release();

볼 수 있는 것처럼 세 단계가 있습니다. 첫째 단계는 IID_IClassFactory 
인터페이스를 경유하여 class 개체를 불러오는 것이고 다음 단계에서 이 클래스 
개체의 IClassFactory::CreateInstance를 호출합니다. 이 호출을 위한 매개변수는 
CoCreateInstance 호출로부터 전달됩니다. 매개변수 pUnkOuter는 기호군을 
호출하는 재사용 메서드를 위해 사용됩니다. 이것에 대해서는 나중에 
언급하겠습니다. 지금은 NULL이라고 가정합니다. 이제 *ppvObj의 개체 인스턴스에 
포인터가 있습니다. 마지막으로 class 개체를 해제합니다.

이 class 개체는 무엇입니까? 이렇게 성가신 이유는 무엇입니까?

class 개체
class 개체는 IClassFactory 인터페이스의 이행이 주 목적인 특별 COM 개체입니다. 
종종 이 개체를 “class 요소” 또는 “class factory 개체”라고 하는 것을 들게 
되겠지만 class 개체라고 부르는 것이 더 정확합니다.

Java를 광범위하게 사용하였다면 COM class 개체를 class의 Java 개체 “Class”와 
거의 같다고 생각할 것입니다. Java의 Class.newInstance는 
IClassFactory::CreateInstance과 유사하고 COM의 CoGetClassObject는 
Class.forName 정적 메서드와 유사함을 알 수 있을 것입니다.

이 개체는 대부분의 COM 개체들 처럼 CoCreateInstance 또는 
IClassFactory::CreateInstance를 호출하여 작성되지 않기 때문에 특별합니다. 
오히려 항상 CoGetClassObject를 호출하여 작성됩니다. 이 기사의 마지막에 특별 
COM 개체에 대한 다른 예제들을 알아보겠습니다. 공교롭게도 CoGetClassObject가 
class 개체를 항상 작성하는 것은 아닙니다. 사용할 수 있는 클래스에 대한 class 
개체가 COM에 있다면 인터페이스 포인터를 반환할 수 있습니다.

CoGetClassObject를 호출한 후에 코드가 작성중인 개체의 종류에 대해 걱정할 
필요가 없습니다. 예를 들어 프로세스 서버인지 지역 서버인지 여부는 문제가 되지 
않습니다. Class 개체가 모든 차이점들을 처리합니다. 그러나 CoGetClassObject는 
요청한 CLSID에 대하여 class 개체를 작성하거나 찾는 방법을 이해하기 위해 
레지스트리와 기존의 등록 class 개체의 목록으로 검색 작업을 해야 합니다.

class 개체는 강력한 다형성에 대한 훌륭한 예제입니다. 개체를 불러오기 위해 COM 
API를 호출합니다. 일단 COM API를 불러오면 필요한(IClassFactory) 표준 
인터페이스를 지원하도록 결정한 다음 인터페이스의 메서드를 호출할 수 있습니다. 
이 경우에는 IClassFactory::CreateInstance입니다. Class 개체의 
CreateInstance가 작동되는 방법에 대해 모른다는 사실을 유의하십시오. 알고있는 
것은 성공되면 개체를 참고하는 인터페이스 포인터를 반환한다는 것이 전부입니다. 
그 밖에는(요약) 알 필요도 없고 알고 싶지도 않습니다. 정확히 같은 
기능(다형성)을 호출하여 특정 class 개체의 식별에 대한 올바른 동작을 얻습니다. 
정확한 동작을 결정하는 class 개체의 식별입니다.

각 class 개체 인스턴스는 특정 CLSID와 관련됩니다. 
IClassFactory::CreateInstance는 매개변수중 하나로 CLSID를 갖고 있지 않음을 
유의하십시오. 오히려 class 개체는 작성해야 할 CLSID가 무엇인지를 압니다. 
이것은 작성할 수 있기를 원하는 개별 CLSID에 대해 적어도 하나의 class 개체가 
필요하다는 의미입니다.

IClassFactory외에도 class 개체는 연결하는 모든 인터페이스를 이행할 수 
있습니다. 예를 들어 특정 class 개체로부터 작성되는 개체 인스턴스에 대해 
기본값을 설정할 수 있는 인터페이스를 정의할 수 있습니다. 그러나 주어진 
CLSID에 대해 하나의 class 개체만 있어서 CoGetClassObject를 한 번 이상 
호출하면 다른 class 개체에 인터페이스 포인터를 잘 불러 올 수 있다는 보장이 
없음을 유의하십시오. Class 개체의 작성을 처리한 후에 지원 기능에 이것을 
정의할 수 있습니다.

class 개체가 있는 이유
토론한 것처럼 COM이 class 개체의 이행을 필요로 하는 가장 중요한 이유는 
클라이언트가 작성에 대한 정확한 세부 사항을 알 필요 없이 COM이 표준 다형 
방식으로 모든 형식의 개체를 작성할 수 있도록 하기 위해서 입니다. class 개체가 
그런 지식을 요약해 주기 때문에 클라이언트가 알 필요가 없습니다. 이것은 class 
개체와 “real” 개체가 매우 밀접한 관계가 있으며 서로 잘 알고 있음을 
의미합니다.

더 단순한 스키마는 없습니까? 예를 들어 CLSID를 받아들여서 새 인스턴스를 
작성하는 DLLCreateInstance를 호출하는 COM DLL의 기능을 생각할 수 있습니다. 
이와 같은 함수는 COM 개체와 IClassFactory보다는 분명히 더 단순합니다.

하지만 EXE에 대해서는 작동하지 않습니다. EXE로부터 함수를 내보내지 않습니다. 
원격 개체에 대해서는 명확히 잘 작동하지 않습니다. 따라서 class 개체를 COM 
개체로 만들 때 COM이 모든 프로세스 내/외의 문제를 관리합니다. 이것이 
장점입니다.

class 개체가 target 개체의 인스턴스를 작성하기 위한 “올바른 작업” 방법을 
아는 COM 개체이기 때문에 일단 class 개체를 작성하면 COM이 인스턴스의 
작성이라는 점에서 그림의 바깥에 오게 됨을 유의하십시오. 따라서 작성된 특정 
형식의 첫째 개체에 대해 COM은 많은 작업을 하여야 합니다. 첫째 등록된 class 
개체의 목록 또는 class 개체가 없다면 레지스트리에서 CLSID를 조사해야 합니다. 
Class 개체를 작성해야 한다면 COM이 작성합니다. 아마도 DLL을 불러오기 또는 EXE 
시작이 포함될 것입니다. 마지막으로 COM은 인스턴스 작성을 위해 올바른 class 
개체상의 IClassFactory::CreateInstance를 호출합니다.

그러나 class 개체를 유지하려면 일련의 인스턴스에 대한 대부분의 작업을 무시할 
수 있습니다.추가 개체를 작성하려면 IClassFactory::CreateInstance를 직접 
호출하기만 하십시오. 이것은 운영자를 새로 직접 호출하는 것 만큼 빠를 수 
있으며 COM이 개체를 작성하는 것보다 훨씬 더 빠를 수도 있습니다.

중요   class 개체를 유지하려면 메모리에 서버를 유지하도록 COM에 알려주기 위해 
IClassFactory::LockServer를 호출해야 합니다. Class 개체에 대한 참조는 
자동으로 메모리에 서버를 유지하지 않습니다. 이 동작은 일반 COM 동작의 
예외입니다. 서버를 잠그는데 실패하면 서버를 불러오기 해제한 후에 class 개체를 
액세스하려고 시도할 때 보호 위반을 일으킬 수도 있습니다.

마지막으로 class 개체는 추가의 방법으로 개체의 작성을 지원할 수 있습니다. 
허가된 컨트롤을 작성하기 위해 IClassFactory 대신 사용하는 IClassFactory2 
인터페이스와 같은 것이 있습니다. 허간된 컨트롤은 컨트롤을 작성하기 전에 
사용자가 올바른 license ID를 받는데 필요한 컨트롤입니다.

개체 작성을 위한 다른 방식 및 사용 시기
한 개체에 하나의 인스턴스만을 작성하고 있고 개체 작성에 IClassFactory를 
사용할 수 있다면 CoCreateInstance 또는 원격 개체를 작성할 수 있는 
CoCreateInstanceEx도 사용할 수 있을 것입니다. 그러나 한 개체에 하나 이상의 
인스턴스를 작성 중이거나 개체 작성을 위해 IClassFactory외에 인터페이스 하나를 
사용해야 한다면 class 개체를 불러와야 합니다.

class 개체를 불러오는 것은 쉽습니다. CoCreateInstance가 하는 것처럼 
CoGetClassObject를 호출하기만 하면 됩니다. Class 개체에 인터페이스 포인터가 
있으면 메모리의 서버를 잠그기 위해 IClassFactory::LockServer(TRUE)를 
호출합니다. 그런 다음 class 개체의 인터페이스 포인터에 담아서 새 인터페이스가 
필요할 때마다 IClassFactory::CreateInstance를 호출 할 수 있습니다. 마지막으로 
개체 작성을 완료하면 IClassFactory::LockServer(FALSE)를 호출하여 서버를 
해제하고 Release를 호출하여 인터페이스 포인터를 해제합니다. 인터페이스 해제는 
인터페이스에 해야 하는 마지막 작업이어야 함을 기억하십시오.

class 개체 이행
class 개체의 모양은? 이 개체는 간단한 COM 개체입니다. 이것은 적어도 하나의 
인터페이스(IUnknown)에서 실행함을 의미합니다. 거의 모든 class 개체가 인스턴스 
작성을 위해 IClassFactory를 실행합니다.

class 개체를 다음과 같이 선언할 수 있습니다.

class CMyClassObject : public IClassFactory
{
protected:
   ULONG m_cRef;
public:
   CMyClassObject() : m_cRef(0) { };
      //IUnknown members
      STDMETHODIMP QueryInterface(REFIID, void **);
      STDMETHODIMP_(ULONG) AddRef(void);
      STDMETHODIMP_(ULONG) Release(void);

      //IClassFactory members
      STDMETHODIMP CreateInstance(IUnknown *, REFIID iid, void **ppv);
      STDMETHODIMP LockServer(BOOL);
};

물론 이 클래스에는 IclassFactory::CreateInstance와 LockServer의 각 함수에 
대한 선언이 있습니다. 놀랍게도 추가로 IUnknown 함수가 있습니다. 모든 COM 
인터페이스와 마찬가지로 IClassFactory는 IUnknown로부터 도출됨을 명심하십시오. 
이 개체에 대한 참조 계수를 담았던 기억이 있으며 생성자에서 0으로 
초기화하였음을 유의하십시오. 메서드 이행을 선언하기 위해 공식 COM 매크로를 
사용하고 있음도 유의하십시오.

이 class 개체를 작성하는 방법
class 개체를 작성하는 다양한 방법이 있습니다. 어떤 것도 CoCreateInstance에 
포함되지는 않습니다. 이 개체에 하나의 인스턴스만 필요하고 생성자가 없는 작은 
개체이기 때문에 코드에 global 개체로 선언하겠습니다.

CMyClassObject g_cfMyClassObject;

이것은 이 개체가 DLL을 로드할 때 항상 존재한다는 것을 의미합니다.

IClassFactory::LockServer를 실행하려면 모든 nonclass 개체 인스턴스의 전역 
계수와 LockServer를 호출하는 횟수도 있어야 합니다.

LONG g_cObjectsAndLocks = 0;

CoGetClassObject가 class 개체를 로드하는 방법
in-process DLL 서버에 대해서는 간단합니다. COM이 DLL에서 DllGetClassObject를 
호출하는 함수를 호출합니다. DLL에 COM 작성 가능 COM 개체가 있으면 이 함수를 
내보내야 합니다. DllGetClassObject의 원형은 다음과 같습니다.

STDAPI DllGetClassObject(const CLSID &rclsid, const IID &riid,
void ** ppv);

COM은 CLSID와 IID에 전달하고 DllGetClassObject는 *ppy의 요청한 인터페이스에 
포인터를 반환합니다. Class 개체를 작성할 수 없거나 요청한 인터페이스가 없으면 
HRESULT 반환 값으로 오류를 반환합니다. HRESULT 반환을 위해 STDAPI가 #정의됨을 
유의하십시오.

EXE 서버에 대한 프로세스는 다릅니다. 각 class 개체에 대해 
CoRegisterClassObject를 호출하여 COM이 작성할 수 있는 각 클래스에 대해 class 
개체를 등록합니다. Class 개체를 등록된 class 개체의 목록에 기록하여 
등록합니다. EXE 프로세스가 종료되면 class 개체마다 한 번씩 
CoRevokeClassObject를 호출하여 등록 목록에서 개체를 삭제합니다. 이것에 관한 
더 자세한 내용은 COM 문서 또는 다양한 COM 북을 검토하십시오. in-process (DLL) 
서버를 강조하고 싶습니다.

그런 다음 COM이 CoGetClassObject를 호출할 때 실제로 class 개체를 로드하는 
방법은 개체가 DLL에 의해 실행되는지 EXE에 의해 실행되는 지에 따라 다릅니다. 
DLL에 의해 실행된다면 아직 로드하지 않았다면 DLL을 로드하여 
DllGetClassObject를 호출합니다. EXE에 대해서는 아직 로드하지 않았다면 EXE를 
로드하여 찾고 있는 class 개체를 등록하거나 시간종료가 발생할 때까지 
기다립니다.

DllGetClassObject는 다음과 같습니다.

STDAPI DllGetClassObject(REFCLSID clsid, REFIID iid, void **ppv) {
   if (clsid != CLSID_MyObject) // Right CLSID?
      return CLASS_E_CLASSNOTAVAILABLE;

   // Get the interface from the global object.
   HRESULT hr = g_cfMyClassObject.QueryInterface(iid, ppv);
   if (FAILED(hr))
      *ppv = NULL;
   return hr;
}

요청한 CLSID가 지원되는 것인 지를 확인해야 합니다. 지원되지 않는다면 E_FAIL을 
반환합니다. 다음 요청한 인터페이스에 대한 QueryInterface를 확인합니다. 확인이 
실패하면 출력 포인터를 NULL로 설정하여 E_NOINTERFACE를 반환합니다. 성공하면 
S_OK와 인터페이스 포인터를 반환합니다.

class 개체의 메서드 실행
IUnknown::AddRef와 IUnknown::Release
class 개체는 전역적입니다. 이 개체는 항상 존재하며 적어도 DLL이 해제될 
때까지는 삭제될 수 없습니다. 결코 이 개체를 삭제하지 않고 class 개체에 대한 
참조가 서버를 로드한 채로 유지하지 않기 때문에 전혀 계수 참조를 이행할 필요가 
없습니다. 그러나 참조 계수는 디버깅하여 다루어질 수 있습니다. 따라서 어쨌든 
이 개체에서 이 작업을 수행합니다.

AddRef와 Release는 개체에 관한 참조 계수 유지의 책임이 있습니다. 0으로 
초기화되는 인스턴스 변수 m_cRef에 유의하십시오. AddRef와 Release는 참조 
카운터를 늘이고 줄여 새 값의 참조 카운터를 반환합니다.

개체를 동적으로 작성하였다면 참조 계수가 0이 되면 개체를 삭제하도록 
Release해야 합니다. 개체가 전역적으로 할당되기 때문에 그렇게 할 수 있습니다.

STDMETHODIMP_(ULONG) CMyClassObject::AddRef() {
   return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CMyClassObject::Release() {
   return InterlockedDecrement(&m_cRef);
}

++m_cRef와 --m_cRef를 사용하는 것보다 안전하게 함수를 증가/감소시키는 
스레드를 사용하여 다중 스레드 작업에 대한 사고 방식을 사로잡았습니다.

AddRef와 Release를 정말 간단하게 만들려면 간단히 0이 아닌 값으로 
반환하십시오. Class 개체데 대한 참조 계수에 대한 변수 구성원을 삭제할 수도 
있습니다(개체와 계수 전역 변수 잠금이 아님).

IUnknown::QueryInterface
QueryInterface 실행은 이 개체에 대해서는 100퍼센트 표준입니다. Class 개체이기 
때문에 특별한 자료가 아닙니다. 요청한 인터페이스가 지원하는 두 인터페이스 
IUnknown와 IClassFactory중 하나 인지만 보면 됩니다. 하나가 맞다면 인터페이스 
포인터를 개체에 적절히 반환하여 참조 계수에 정확히 맞는 포인터에 AddRef를 
호출합니다. 둘 중 어느 것도 아니라면 적절한 오류 코드 E_NOINTERFACE를 
반환합니다.

STDMETHODIMP CMyClassObject::QueryInterface(REFIID iid, void ** ppv) {
   *ppv = NULL;
   if (iid == IID_IUnknown==iid || iid == IID_IClassFactory) {
      *ppv = static_castthis;
      (static_cast*ppv)->AddRef();
      return S_OK;
   else {
      *ppv = NULL; // COM Spec requires NULL if failure
      return E_NOINTERFACE;
   }
}

파격적인 새 static_cast 연산자에 유의하십시오. ANSI C++에서 다른 연산자를 
사용하여 캐스트의 세 가지 다른 의미론적 사용을 구별할 수 있습니다. 
static_cast 연산자는 필요한 포인터의 값을 바꾸어서 포인터 사이에서 다른 
클래스 형식으로 적절히 보냅니다(이 경우에는 다중 전승을 사용하지 않기 
때문입니다).

IClassFactory::CreateInstance
class 개체의 핵심 즉 인스턴스를 작성하는 기능입니다.

STDMETHODIMP CMyClassObject::CreateInstance (IUnknown *pUnkOuter,
   REFIID iid, void ** ppv)
{
   *ppv=NULL;

// Just say no to aggregation.
   if (pUnkOuter != NULL)
      return CLASS_E_NOAGGREGATION;

   //Create the object.
   CMyObject *pObj = new CMyObject();
   if (pObj == NULL)
      return E_OUTOFMEMORY;

   //Obtain the first interface pointer (which does an AddRef).
   HRESULT hr = pObj->QueryInterface(iid, ppv);

   // Delete the object if the interface is not available.
   //Assume the initial reference count was zero.
   if (FAILED(hr))
      delete pObj;
  
   return hr;
}

첫째 기호군을 지원하지 않기 때문에 포인터가 non-NULL이면 기호군을 지원하라는 
요청을 받기 때문에 개체를 작성할 수 없습니다. 다음으로 개체를 할당하고 개체를 
할당할 수 없으면 E_OUTOFMEMORY를 반환합니다.

다음 새로 작성된 개체에 QueryInterface를 호출하여 반환할 인터페이스 포인터를 
불러옵니다. 이것이 실패하면 개체를 삭제하고 오류 코드를 반환합니다. 성공하면 
QueryInterface로부터 성공 코드를 반환합니다. 성공하면 개체에 대한 올바른 참조 
계수를 제공하여 QueryInterface가 AddRef를 호출합니다.

개체와 잠금 계수 g_cObjectsAndLocks를 증가시키지 않았음을 유의하십시오. 
작성이 성공하였다면 그렇게 하였을 수도 있었지만 인스턴스 개체의 Release 또는 
삭제자에서 증가시켜야 합니다. Part 5에서 개체의 자체 삭제자에 입력한 다음 
이곳이 아닌 생성자에서 증가 시켜야 합니다.

개체에 초기 QueryInterface를 작성하는 많은 다른 유형이 있습니다. 개체 자체가 
초기 참조를 계산하는 방법에 따라 다릅니다. 발생하는 문제는 몇 가지 경우에 
개체가 AddRef와 Release 쌍이 실행되도록 호출하는 QueryInterface 작업 중에 
무언가를 한다는 것입니다. 개체의 초기 참조 계수가 0이면 Release를 호출하면 
개체가 자유로워 집니다. CreateInstance가 반환하기 전에도 그렇습니다. 좋지 
않습니다.

하나의 공통 기술은 개체의 초기 참조 계수를 0이 아닌 다른 값으로 설정하는 
것입니다. 개체 생성자(Part 5 참조)에서 이 작업을 하기는 쉽습니다. 그렇게 
하더라도 참조 계수를 올바르게 설정하도록 QueryInterface를 호출한 후에 
Release를 호출하도록 CreateInstance를 변경해야 합니다.

이 작업을 한다면 개체 삭제를 생략합니다. QueryInterface가 실패하면 AddRef를 
호출하지 않기 때문에 개체 참조 계수는 둘이 아니라 하나가 될 것입니다. 그 
위치에서 Release를 호출하면 개체의 참조 계수가 0으로 되고 개체는 스스로 
삭제될 것입니다. QueryInterface가 성공하면 참조 계수를 둘로 증가시킨 다음 
Release가 참조 계수를 하나로 감소시킬 것입니다. 건강한 개체를 위해 존재해야 
합니다.

최초 참조 계수가 하나라고 가정하면 QueryInterface에 대한 CreateInstance 
코드로 종료하여 다음과 같이 종료합니다.

// ...

//Obtain the first interface pointer (which does an AddRef).
   HRESULT hr = pObj->QueryInterface(iid, ppv);

   // Delete object if interface not available.
   // Assume the initial referece count was one, not zero.
    pObj->Release(); // Back to one if QI OK, deletes if not

   return hr;
}

Part 5에서는 개체에 대해 이 코드를 사용할 것입니다. 간단하고 항상 작동됩니다. 
good doctor는 이 점을 CreateInstance가 개체에 대한 실행 세부 사항을 알아야 
하는 이점으로 간주하지 않습니다. 결국 CreateInstance가 무엇인가에 대한 것이며 
그런 세부사항을 요약하려고 클라이언트가 걱정하지 않아도 됩니다.

IClassFactory::LockServer
LockServer는 단순히 전역 잠금 및 개체 계수를 증가 및 감소시킵니다. 계수가 0이 
될 때 DLL의 해제를 시도하지는 않습니다. EXE 서버라면 계수가 0이 되면 대화형 
사용자가 없는 것으로 간주하여 서버를 종료할 것입니다.

STDMETHODIMP CMyClassObject::LockServer(BOOL fLock) {
   if (fLock)
      InterlockedIncrement(&g_cObjectsAndLocks);
   else
      InterlockedDecrement(&g_cObjectsAndLocks);
   return NOERROR;
}

또 이 코드 스레드를 안전하게 하도록 선택했습니다. 계수가 0이 되면 개체가 
삭제될 수 있습니다.

DllCanUnloadNow
COM이 DllCanUnloadNow를 호출하여 DLL을 해제할 것인 지 여부를 결정할 것입니다. 
해제를 확인하면 간단히 S_OK를 반환합니다. 확인하지 않으면 S_FALSE를 
반환합니다. 서버에 개체 또는 잠금이 없으면 해제해도 좋습니다.

STDAPI DllCanUnloadNow() {
   if (g_cObjectsAndLocks == 0)
     return S_OK;
   else
      return S_FALSE;
}

위치할 장소: 가야할 장소
in-process 개체를 작성하는 방법과 더 효과적으로 작성하는 방법에 대해 
토론하였습니다. Class 요소라고도 하는 class 개체와 이 개체를 실행하는 방법에 
대해서도 언급하였습니다. 그러나 실제로 개체를 실행하는 것에 대해서는 살펴보지 
않았습니다.

다음에 인스턴스 개체 실행에 대한 핵심을 언급하겠습니다. 여기에는 IUnknown에 
필요한 코드와 사용자 정의 인터페이스가 포함됩니다. 또 COM을 사용하지 않고 
작성하는 특별히 고 효율의 COM 개체에 대해서도 언급할 것입니다.

메모: C++를 사용하여 실행을 하였지만 C를 사용할 수도 있습니다. good doctor는 
특별히 C와 C++가 혼합된 프로그램이 좋은 이유를 모르겠습니다. 하지만 그 이유를 
정말로 알고자 한다면 Inside OLE의 제 2장 "RectEnumerator in C: ENUMC.C,"의 
항목에 MSDN에 관한 예제가 있습니다. 

사용자에게

good doctor 주소를 알고자 하는 항목이 있습니까? drgui@microsoft.com에 doc를 
입력하십시오. 비록 good doctor의 처방에 개별 응답을 배제하고 있지만 Dr. GUI가 
모든 우편을 읽고 검토합니다.
 
[알림판목록 I] [알림판목록 II] [글 목록][이 전][다 음]
키 즈 는 열 린 사 람 들 의 모 임 입 니 다.