
Marcus Heege (영문)
목차
C++ 상호운용성은 당신의 친구입니다
C++ 상호운용성을 사용해야 하는 시기
MFC 응용프로그램에서의 윈도우 폼 컨트롤
컨트롤 이벤트 다루기
다이얼로그로서의 컨트롤 (Controls as Dialog Boxes)
윈도우 폼 뷰 (Windows Form View)
아발론을 향한 브릿지(Bridge to Avalon)
결론
마이크로 소프트 .NET 프레임웍이 5년 전에 공개가 되고, 버전 2.0이 최근에 출시되었지만, 아직도 많은 C++ 어플리케이션이 아직도 순수한 네이티브(Native) 코드로 작성되고 있습니다. 그러나 C++개발자들 사이에 .NET 프레임웍에 대한 관심은 크게 높아져 가고 있고, 이것은 향후에 출시될 많은 Windows® API가 .NET에 기반을 두고 있고, 현재 윈도우 비스타에 기본으로 포함되어 있는 Windows Presentation Foundation, Windows Communication Foundation, 그리도 Windows Workflow Foundation 등과 같은 WinFX® 컴포넌트 들이 .NET을 기반으로 작성되어 있기 때문에 C++ 개발자들이 .NET에 대한 관심이 많아지는 것은 아주 좋은 현상이라고 생각합니다.
그러나, 아직도 많은 C++ 개발자들은 API의 래퍼(wrapper)를 이용하기 보다는, 순수 네이티브 API를 직접 쓰고 싶어합니다. 그 이유로는 이러한 래퍼들이 버그가 많고, 속도가 느리고, 유연하지 못한 것으로 인식되어 왔기 때문입니다. 그러한 인식을 제외하더라도, WinFX같은 덩치가 큰 API를 파악해서 네이티브 코드에 사용하는 것은 쉬운 일은 아니었기 때문입니다.
거의 대부분의 경우에, C++ 네이티브 응용프로그램에 .NET 프레임웍에서 제공하는 기능을 이용하여 확장시키는 것은 개발자가 상상하는 것보다 쉬운 편입니다. WinFX® 은 C++ 상호운용성이라는 기능을 집어 넣었고, 때때로 이 기능은 IJW(It Just Works)로 불리기도 합니다. 개발자는 이 기능을 사용해서 기존에 존재하는 C++ 소스코드를 .NET에 기반을 둔 소스코드와 문제없이 통합할 수 있습니다.
C++ 상호운용성은 두 개의 큰 기능에 기반합니다. 첫 번째는, 기존의 C++ 코드를 C++ 컴파일러의 /clr 스위치를 사용해서 MSIL(Microsoft Intermediate Language)로 컴파일 할 수 있습니다. 그렇게 하고 나면, 여러분의 코드는 가비지 콜렉터(Garbage Collecting), .NET 프레임웍 기본 라이브러리의 수많은 자료형들과 같은 .NET에 기반을 둔 기능들을 사용할 수 있습니다.
다른 기능 중의 하나는 혼합모드(Mixed Mode)라고 불리는 것 이고, 위에서 말씀 드린 내용과 동일하게 중요합니다. 이 혼합모드는 매니지드(Managed) 그리고 언매니지드(Unmanaged) 코드 두 개를 혼합해서 사용하는 것을 가리키는 것 입니다. C++ 코드를 MSIL로 컴파일 할 때, 컴파일러는 네이티브 어셈블리 코드를 가지고 있는 언매니지드 목적 파일(object file)을 생성하는 대신, MSIL 코드를 가지고 있는 매니지드 목적 파일들을 생성합니다. 이 때, 링커는 매니지드 그리고 언매니지드 목적 파일들을 입력으로 받아들일 수 있습니다. 링커가 적어도 하나 이상의 언매니지드 입력 파일을 발견 시에, 링커가 매니지드 코드와 언매니지드 코드 둘 다를 가지고 있는 .NET 어셈블리를 생성합니다. 이것이 이 모드가 혼합모드라고 불리는 이유입니다. (그림 1을 참고해 주시기 바랍니다). 이 기능은 매니지드와 언매니즈드 공간 사이의 전환하는 빈도를 줄여주는 것과 관련이 있기 때문에, 효율 면에 있어서의 최적화를 위해서는 아주 중요합니다.

그림 1 MTS
C++ 상호운용성은 당신의 친구입니다
제 경험으로는 몇 가지 제약사항이 있기는 하지만, 이러한 C++ 상호운용성은 아주 신뢰할 만 해서, 개발자의 개발 도구에 추가할 만한 가치가 있었습니다. 이러한 기능이 어떻게 가능한지 이해하기 위해서는, 먼저 .NET 프레임웍을 설계할 때 C++ 상호운용성을 염두에 두고 먼저 설계했다는 사실을 이해하셔야 합니다. .NET 프레임웍의 기본이 되는 CLI(Common Language Infrastructure)는 처음부터 이것을 염두에 두고 디자인되었습니다. 실제로는 CLI의 많은 부분들이 단지 C++ 과의 상호운용성을 위해 필요하기 때문에 디자인된 부분도 많이 있습니다.
예를 들어서, 매니지드 메타데이타는 전역 함수를 지원합니다. C#을 비롯한 .NET에 기반을 둔 거의 모든 언어들은 메써드가 클래스 안에서만 정의되어 있어야 합니다. 이 기능은 전역 함수를 가진 C++ 코드가 .NET에 기반을 둔 코드와 일대일로 매핑이 되어야 필요가 있기 때문에 생겨났으며, 그리고 C++ 코드와 매니지드 코드 사이에 매핑을 하기 위해서 메타데이타 역시 동일한 개념이 필요합니다.
MSIL 명령어 집합들 역시 C++ 과의 상호운용성을 염두에 두고 디자인 되었습니다. C++코드를 MSIL 코드로 매핑 하기 위해서는, MSIL은 전형적인 불린(boolean) 연산, 수치 연산(arithmetic operation), 그리고 부동 소수점 연산(floating point operation) 같은 모든 공통된 데이터 조작 연산을 지원하도록 되어 있습니다. 또한, MSIL은 가상메모리에 포인터를 이용한 접근도 지원합니다. 이 기능은 매니지드 코드에서 네이티브 자료형을 지원하기 위해서 절대적으로 필요한 기능입니다. 만약 여러분의 C++ 코드가 네이티브 객체의 특정 필드에 접근한다면, C++/CLI 컴파일러는 네이티브 객체의 주소를 스택에 푸쉬(push)해서, 필드의 오프셋(offset)을 더한 다음, 그 결과로 나오는 주소 값을 이용하여 필드에 접근하는 MSIL 코드를 생성할 것 입니다. 이 모든 것이 MSIL 명령어가 주소를 통한 가상메모리의 접근을 지원하기 때문입니다.
IL 언어에서 C++에 특화된 또 다른 특징 중의 하나는 CALLI 명령어 입니다. 이것은 함수포인터를 통하여 네이티브 함수를 호출하는데 사용할 수 있습니다. 이 기능은 네이티브 자료형의 가상 함수를 호출하기 위해서 사용됩니다. 이 기능은 COM 인터페이스가 가상 함수를 가지고 있는 네이티브 자료형이기 때문에 중요합니다. 이 방법을 사용한 COM 상호운용성은, 다른 언어에서 사용되는 COM 객체와의 상호운용성에 비해서 많은 장점이 있습니다.
마지막으로 .NET을 사용하는 모든 개발자들이 너무나 명백하게 알 수 있는 .NET의 디자인 측면에서의 이점에 대해서 언급할 필요가 있습니다: 컴포넌트를 위한 파일 포맷이 바로 그것입니다. 자바가 컴포넌트를 위한 파일 포맷으로 고유한 파일 포맷 (.class 파일)을 사용하는 것과 대조적은 .NET 프레임웍은 일반적인 PE 파일 포맷을 가지고 있습니다. 그럼으로, .NET 기반의 컴포넌트들은 DLL이나 EXE같은 흔히 볼 수 있어서, 익숙한 파일 포맷을 가지고 있습니다. PE 파일의 사용은 혼합모드 어셈블리를 지원 하는데 꼭 필요한 것 입니다.
C++ 상호운용성의 사용시기
C++ 상호운용성이 개발자가 진행하고 있는 프로젝트에서 필요한지 아닌지의 여부를 평가하기 위해서는, 먼저 이 기능이 개발자의 과정에서 산출되는 결과물은 물론이고 프로젝트의 빌드에 미치는 영향-예를 들어서, 컴파일러 설정-을 알고 있어야 합니다.
개발자가 /clr 옵션을 가지고 컴파일을 한다면, 개발자의 코드는 암시적으로 멀티쓰레드 DLL 버전의 CRT(C runtime libraries)를 참조하게 됩니다. 이것의 의미는 /clr로 컴파일 되지 않은 파일들을 포함해서 모든 파일들이 반드시 멀티 쓰레드(multithreaded) DLL 버전의 CRT를 사용해야 한다는 의미입니다. 이 경우 정적 CRT 라이브러리에 의존하는 MFC 프로젝트의 경우는 /clr 컴파일을 할 수 없게 됩니다. 그럼으로, 개발자는 반드시 자신의 MFC 프로젝트가 멀티쓰레드 DLL 버전의 CRT를 사용하도록 컴파일 속성을 설정해야 합니다.
그리고 여기에는 실행시간에 대한 논란이 있을 수 있습니다. CLR을 초기화 하는 것은 어느 정도 일정 시간이 필요하고, JIT 컴파일 역시 일정 정도의 오버헤드가 있습니다. 그럼으로 프로그램의 처음 구동 시에 약간의 지연이 발생할 수 있습니다. 그러나, 이러한 지연은 거의 대부분의 프로젝트에서는 크게 문제가 되지 않습니다.
매니지드에서 언매니지드로의 전환을 하는 함수 호출은 일반적인 함수 호출보다는 시간이 길게 걸릴 수 있습니다. 이전에 이미 언급했듯이, 여러분의 응용 프로그램에서 어느 특정 부분이 매니지드 코드로 컴파일 되어야 하는 지를 결정해서 이러한 전환의 수를 크게 줄일 수 있습니다. 또, _clrcall 이라는 콜링 컨벤션 역시 이러한 전환의 수를 크게 줄여줄 수 있는 중요한 최적화 기능 중의 하나입니다. 그러나, 이 기능은 이 글의 범위에서 벗어나기 때문에, 여기서는 다루지 않겠습니다.
거의 모든 경우에서, JIT(just-in-time) 컴파일 된 코드 자체는 효율의 저하를 가진 이유가 아닙니다. 실제로는 JIT 컴파일러는 C++ 컴파일러와 링커가 할 수 없는 최적화 도구를 몇 개 가지고 있습니다. 예를 들어서, JIT 컴파일러는 해당 시스템에 존재하는 프로세서에 맞게 코드를 최적화 할 수 있습니다. C++ 컴파일러와는 대조적으로, JIT 컴파일러는 크로스 컴퍼넌트 인라이닝(cross-component inlining)을 제공할 수 있습니다. 이 방법에 의해서 컴파일 된 코드는 다른 최적화 방법에 노출될 수 있기 때문에, 아주 효과적인 최적화 입니다.
제가 위에서 언급했던 것들 말고도, 이러한 성능에 강한 영향을 주는 몇 개의 부분들이 존재하지만, 지면관계상 여기서 자세하게 다 다룰 수는 없을 것 같습니다. 거의 대부분의 최적화에 관련된 부분들은 컴파일러와 링커 설정을 바르게 설정함으로써 해결할 수 있습니다. 그런, 최상의 컴파일러 설정은 언제나 간단 명료한 것은 아닙니다. 몇 개의 설정들은 강제적으로 C++ 상호운용성 컴파일을 하도록 할 것 이고, 반면에 /clr 설정을 적용할 수 없는 부분에 대해서는 이 기능을 사용하지 말거나 혹은 다른 방법을 이용해서 해결해야 합니다.
MFC 응용 프로그램에서의 윈도우 폼 컨트롤
/clr 옵션을 가지고 컴파일 된 모든 소스 파일들은 .NET 프레임웍 기본 클래스 라이브러리(Base Class Library)에 있는 자료형 들을 사용할 수 있습니다. 개발자는 항상 써왔던 것처럼 기본 클래스 라이브러리의 많은 부분을 사용할 수 있습니다. 그러나, 어떤 클래스들은 특별한 주의가 필요하기도 합니다. MFC 응용 프로그램에 윈도우 폼 컨트롤을 통합시키는 것이 그러한 것의 대표적인 예라고 할 수 있습니다.
예를 들어, 개발자는 CWnd*가 필요한 곳에 System::Windows::Forms::Control^을 직접 전달할 수 없습니다. 이 문제를 해결하기 위해서는, 윈도우 폼 API와 MFC는 공통적인 부분을 가지고 사실을 먼저 명심하셔야 합니다. 바로 오래되었지만 아직도 유용한 User32.dll 입니다. 이 두 개의 API들은 HWND을 얻어올 수 있는 메써드들을 제공하고 있습니다. MFC의 CWnd 클래스가 HWND을 얻을 수 있는 변환 연산자를 제공하는 반면에, 윈도우 폼 컨트롤은 윈도우 핸들을 노출하기 위해서 System::Windows::Forms에 있는 IWin32Window 인터페이스를 구현하고 있습니다:
public interface IWin32Window {
property IntPtr Handle {
IntPtr get ();
}
};
그러나, 윈도우 객체로부터 윈도우 핸들을 얻어오는 것만으로는 충분하지 않을 때도 있습니다. MFC는 윈도우 폼 컨트롤을 지원하기 위해서 몇몇 클래스를 제공하고 있고, 이 클래스들은 afxwinforms.h에서 찾아볼 수 있습니다.
이 중에서 가장 중요한 클래스는 Microsoft::VisualC::MFC에서 제공하는 CWinFormsControl 클래스입니다. 이 클래스는 개발자로 하여금 MFC 다이얼로그 혹은 CDialog 에서 상속 받은 클래스로 하여금 윈도우 폼 컨트롤을 사용할 수 있게 해 줍니다. 이 클래스는 템플릿 클래스이며, 여러분이 사용하기를 원하는 윈도우 폼 컨트롤 형(Windows Form Control Type)을 전달하기 위해서 단 하나의 템플릿 인자를 필요로 합니다. 이 인자의 형은 여러분이 향후에 인스턴화하기를 원하는 특정한 자료형 혹은 특정한 자료형의 기본 클래스가 될 수 있습니다. CWinFormsControl 템플릿은 아주 간단한 편입니다. 이 클래스는 특히 두 가지 중요한 특징을 가지고 있는데, 첫 번째는 CWnd를 상속받는 점이고, 두 번째는 이 클래스가 CreateManagedControl이라는 함수를 제공한다는 점 입니다.
만약 여러분이 MFC 다이얼로그 혹은 CDialog에 기반을 둔 클래스에서 윈도우 폼 컨트롤을 사용하기 원한다면, 먼저 CWinFormsControl<TWinFormsCtrl> 형의 멤버변수를 여러분의 클래스 안에 선언해서 여러 개의 CreateManagedControl 의 오버로드(Overloading)된 함수들 중 적절한 하나를 선택해야 합니다. 만약 여러분이 CDialog에 기반을 둔 클래스를 구현한다면, 매니지된 컨트롤을 생성하는데 가장 좋은 곳은 OnInitDialog 함수입니다. 기본 클래스가 CDialog가 아닌 경우에는 OnCreate (WM_CREATE 의 메시지 핸들러 함수입니다.) 함수가 이런 일을 하기에 가장 좋은 선택이 될 수 있습니다. 위의 CreateManagedControl의 오버로드된 여러 함수들 중에서 하나는 특별히 다이얼로그 클래스에 초점을 맞추고 구현되었습니다. 이 함수는 개발자로 하여금 다이얼로그 리소스에 스태틱 컨트롤(static control)을 추가해서, 윈도우 폼 컨트롤의 크기만큼, 스태틱 컨트롤의 크기를 변형한 후에, 스태틱 컨트롤에 그림 4와 같이 고유 ID를 부여해 놓기를 요구합니다. 이렇게 한 후에, 폼 컨트롤을 인스턴스화 하기 위해서는, 그림 2에서와 같이 CreateManagedControl을 호출해야 합니다. 이 방법을 이용하지 않는 경우에는, 그림 3에서와 같이 DDX(Dialog Data Exchange)를 이용해서 CreateManagedControl 함수를 사용하지 않고서도 인스턴스화 할 수 있습니다.

그림 4 ActiveX 컨트롤 사용하기
이러한 방법을 이용해서 MFC 응용프로그램에서 윈도우 폼 컨트롤을 사용하는 것은 MFC: ActiveX® 컨트롤을 사용하기 위해서 MFC 응용프로그램에서 사용하는 방법과 동일합니다. 그림 5에서 볼 수 있듯이, System::Windows::Forms::Control은 OLE와 ActiveX 컨트롤에 관련된 많은 인터페이스를 구현하고 있습니다. CreateManagedControl의 안에서는, 윈도우 폼 컨트롤은 gcnew 오퍼레이터를 이용해서 인스턴스화 되어, 일반 ActiveX 컨트롤처럼 제 자리에서(in-place) 활성화됩니다.
CWinFormsControl이 -> 오퍼레이터를 오버로드해서 사용되고 있는 컨트롤의 포인터가 반환될 수 있게 하고 있다는 사실도 알아두시기 바랍니다. 이 구현은 개발자가 button”의 텍스트 속성을 아래와 같은 간단한 대입문(assignment)으로 초기화할 수 있게 해줍니다:
m_wfBtn.Text = "클릭해 주세요!";
이러한 것들은 속성 창에서 컨트롤의 속성들을 직접 입력하는 것에 비해서는 그렇게 깔끔하지 못하지만, 이러한 제한된 디자인 모드 지원에 대해서 크게 문제가 없으시다면, 오래된 MFC 다이얼로그 박스에서도 윈도우 폼 컨트롤의 쓰실 수 있게 됩니다.
컨트롤 이벤트 핸들링
여러분이 사용하고 있는 윈도우 폼 컨트롤이 ActiveX 컨트롤처럼 다루어지고 있기 때문에, 버튼의 이벤트 핸들링을 하기 위해서는 두 가지 방법이 있습니다: ActiveX 컨트롤을 통한 Connection Point와 윈도우 폼 컨트롤의 이벤트 멤버 활용이 바로 그것들 입니다. Connection Point를 통한 방법은 윈도우 폼 컨트롤이 COM 에 기반을 둔 이벤트를 지원할 준비가 되어 있어야만 사용할 수 있습니다. 거의 모든 윈도우 폼 컨트롤의 경우에는, 안타깝지만 이 방법은 가능하지 않기 때문에, 이벤트를 .NET에서 처럼 다루는 것만이 여기서의 유일한 방법이 됩니다. 매니지드(Managed) 클래스들의 이벤트들은 이벤트 핸들러 대리자(delegate)의 등록과 등록 해제를 허용하는 특별한 종류의 자료형입니다. 아래의 코드는 어떻게 버튼 클릭 이벤트를 등록하는 지를 보여줍니다:
m_wfBtn.Click += <클릭이벤트를 위한 이벤트 핸들러 대리자>
이러한 이벤트 핸들러를 위해서는, 아래와 같은 구문을 가지는 함수가 요구됩니다:
void EventHandler(Object^ sender, EventArgs^ e);
유감스럽게도, CDialog에 기반을 둔 클래스에는 위의 방법만으로는 충분하지 못합니다. 대리자 대상 함수(delegate target function)는 반드시 매니지드 클래스(managed class)의 멤버 함수여야 하지만, CDialog에서 상속 받은 클래스는 매니지드 클래스가 아닙니다. 이 문제를 해결하기 위해서, Visual C++은 \msclr\event.h라는 헤더 파일을 제공하고 있습니다. 이 파일은 이러한 문제에 대한 일반적인 해결책을 주고 있습니다. 이 헤더파일의 정의를 이용하시면 .NET 이벤트를 순수 C++ 클래스의 메써드로 처리하고 싶을 때 사용할 수 있습니다. 제가 나중에 말씀 드리겠지만, 개발자 여러분은 이 헤더파일을 Windows Presentation Foundation의 시각적인 기능과 통합 시에도 사용할 수 있습니다. 이 헤더 파일은 많은 경우에 도움이 될 수 있기 때문에, 이 해결 방법에 대해서 좀 더 자세히 다루어 보기로 하겠습니다.
윈도우 폼 컨트롤의 이벤트를 처리하기 위해서, 개발자는 메시지 핸들러를 가지고 있는 매니지드 클래스를 구현할 수도 있습니다. 매니지드 클래스 자체는 CDialog에 기반한 클래스에 중첩될 수 있기 때문에, C++ 상호운용성은 개발자로 하여금 이 방법을 이용할 수 있는 아주 우아한 방법을 제공해 줍니다:
class CMyDialog : public CDialog {
ref class nested_managed_class {
void OnWFBtnClick(Object^ sender, EventArgs^ e) {
// button"의 클릭 이벤트를 여기에서 처리합니다
}
};
// other members
};
button”의 클릭 이벤트를 처리할 수 있는 가장 편리한 방법은 이 함수 호출을 CDialog에서 상속 받은 클래스의 멤버 함수에 전달하는 것 입니다. 최종으로 클릭 이벤트를 처리하는 메써드는 쉽게 상속 받은 클래스의 다른 멤버에 접근할 수 있습니다. 대리자 대상(delegate target)을 가지고 있는 매니지드 클래스는 결국 간단한 프락시(Proxy)가 되는 것과 동일한 역할을 하게 되는 것 입니다. 이러한 프락시 자료형과 이벤트 핸들러가 Event.h에서 정의된 매크로에 의해 생성될 수 있지만, 수동으로는 어떻게 정의했는지 그림 6을 참고해 보시기 바랍니다.
여러분이 볼 수 있듯이, 대리자 프락시(delegate proxy)는 CMyDialog*를 인자로 받는 생성자(Constructor)를 가지고 있습니다. 이 생성자는 향후에 이벤트 함수 호출을 CDialog를 상속 받은 클래스에 전달하기 위해서 꼭 필요합니다. 이 자료형은 다이얼로그 클래스의 모든 윈도우 폼 컨트롤의 이벤트 핸들러에서 사용될 수 있습니다. 네이티브 클래스에서 필드(Field)와 같이 가비지 콜렉팅(Garbage Collecting)이 되는 핸들을 가지는 것은 불가능하기 때문에, 상속 받은 클래스에서 delegate_proxy_type 형의 멤버 변수를 직접 추가하는 것이 가능하지 않습니다. 이 때, gcroot 템플릿을 사용하면, 개발자는 이러한 제한을 피해갈 수도 있습니다. 그래서 delegate_proxy_type 형의 멤버 변수를 추가하기 위해서는 위에서 이야기한 템플릿을 사용해서 gcroot<delegate_proxy_type> 같은 형식으로 멤버를 선언해야 합니다. 그러나, 이러한 멤버 변수에 이용에 다 해결되지 못한 문제가 남아 있습니다. 어떻게 delegate proxy type 에 대한 레퍼런스가 적절하게 초기화될 수 있을까? 이것은 delegate_proxy_type의 핸들이 필요할 때 마다 호출되는 각각 다른 메써드를 통하여 처리될 수 있습니다.
그림 6의 코드와 같이, 대리자는 아래의 예와 같이 쉽게 등록될 수 있습니다:
virtual BOOL OnInitDialog() {
CDialog::OnInitDialog();
this->m_wfBtn->Click +=
gcnew System::EventHandler(get_proxy(this),
&delegate_proxy_type::OnWFBtnClick);
return TRUE;
}
이 모든 코드를 작성하는 것은 단지 CDialog을 상속 받은 클래스에서 윈도우 폼 컨트롤의 이벤트를 다루기 위한 것 치고는 많은 양의 일이 될 수 있습니다. 그림 7에서 보여지는 것과 같이 Event.h 에는 위에서 보여드렸던 동일한 종류의 일들 혹은 더 많은 일들을 좀 더 적은 코드로 얻을 수 있는 매크로와 템플릿을 가지고 있습니다.
일반적으로 MFC의 매번 새로운 버전마다 적어도 하나 이상의 새로운 맵이 추가됩니다. 여러분께서 위의 그림 7에서 보시는 것과 같이, 현재 버전에서는 대리자 맵이 추가되었습니다. 이 맵에 대해서 좀 더 자세하게 설명을 드리자면, BEGIN_DELEGATE_MAP 매크로는 대리자 대상(delegate target)을 가지고 있는 delegate_proxy_type를 정의합니다. 모든 EVENT_DELEGATE_ENTRY 라인은 매니지드 클래스에 대상 메써드를 추가하고, END_DELEGATE_MAP은 매니지드 클래스를 종료합니다. 마지막으로, MAKE_DELEGATE는 delegate_proxy_type의 대상 함수를 가리키고 있는 대리자를 인스턴스화 합니다. 사실상, 이 매크로들은 제가 말씀 드렸던 것보다 훨씬 더 많은 일들을 합니다. 이 매크로들은 매니지드 클래스가 더 이상 존재하지 않는 네이티브 객체로 이벤트를 보내는 경우까지도 대비되어 있습니다.
다이얼로그로서의 컨트롤 (Control as Dialog Boxes)
또 다른 간단하면서도 유용한 클래스는 CWinFormsDialog 템플릿 클래스입니다. 개발자는 이 클래스를 이용해서 윈도우 폼 컨트롤을 모달 혹은 모달리스 다이얼로그 박스로 보여줄 수 있습니다. 이 클래스 자체는 상당히 간단합니다. 이 클래스는 CDialog를 확장해서 MFC 다이얼로그 박스의 표준 기능을 제공합니다. 그것과는 별개로, 이 클래스는 CWinFormsControl 멤버 변수를 사용해서 윈도우 폼 컨트롤을 사용합니다. 사용되는 컨트롤의 Text 속성을 이용해서 다이얼로그의 캡션에 설정하고, 컨트롤이 완전히 꼭 맞도록 하기 위해서 다이얼로그의 초기 크기를 설정합니다. 그리고 부모 윈도우의 크기가 변할 때 마다, 사용되는 컨트롤의 크기를 변화합니다.
아래 한 줄의 코드는 YourWinFormsDlgControl를 호스팅해서 임시 다이얼로그 박스를 인스턴스화 하고, 이것을 모달 다이얼로그 박스로 보여 줍니다:
CWinFormsDialog<YourWinFormsDlgControl>().DoModal();
위와 같은, DoModal를 통해서 다이얼로그의 리턴 값을 받을 수 있는 길이 없기 때문에, 위와 같은 식으로 이용하는 것은 피해야 합니다. 좀 더 현실적인 시나리오는, 여러분이 CWinFormsDialog<YourWinFormsDlgControl>로부터 여러분 자신의 클래스를 상속받는 경우를 고려해 보시기 바랍니다. 이렇게 함으로써, 여러분은 중첩된 윈도우 폼 컨트롤의 속성을 설정하거나 혹은 사용되는 컨트롤의 이벤트를 처리할 수 있게 됩니다. 제가 위에서 말씀드렸던 Event.h에 있는 매크로들은 이러한 경우에 아주 유용하게 쓸 수 있습니다. 좀 더 자세한 정보는, http://msdn2.microsoft.com/ko-kr/library/ahdd1h97(vs.80).aspx를 참고해 보시기 바랍니다.
윈도우 폼 뷰 (Windows Forms Views)
개발자는 윈도우 폼 컨트롤을 뷰(view)로 호스팅할 수도 있습니다. CWinFormsView 클래스가 이러한 일을 하기 위해서 필요한 클래스입니다. 앞에서의, CWinFormsDialog와는 다르게, 이 클래스는 템플릿 클래스가 아닙니다. 그럼에도 불구하고, 이 두 클래스 사이에는 매우 중요한 공통점을 가지고 있습니다. 둘 다 CWinFormsControl 템플릿에게 컨트롤의 호스팅을 하도록 하고 있고, 두 개의 클래스 모두 사용되는 컨트롤의 크기 변경을 알아서 처리해 줍니다.
MFC 다이얼로그 박스에서 윈도우 폼 컨트롤을 호스팅하기 위해서는, 여러분은 CWinFormsView로부터 여러분의 뷰 클래스를 상속 받아야 합니다. 아래의 코드는 CWinFormsView에서 상속 받은 간단한 뷰 클래스의 선언 부분을 보여줍니다:
class CMyWinFormsBasedView : public CWinFormsView
{
CMyWinFormsBasedView ();
DECLARE_DYNCREATE(CMyWinFormsBasedView)
};
만약 여러분의 뷰 클래스가 위자드에서 의해서 생성되었다면, 여러분은 CWinFormsView를 뷰 클래스의 선언 부에 있는 베이스 클래스 리스트뿐만 아니라, 구현 파일에서도 바꾸는 것을 잊지 마시기 바랍니다. 이것은 여러분으로 하여금 IMPLEMENT_DYNAMICE 과 BEGIN_MESSAGE_MP 과 같은 매크로의 인자들도 바꾸도록 할 것 입니다. 호스팅되는 윈도우 폼 컨트롤을 인스턴스화 하기 위해서는, CWinFormsView의 생성자가 윈도우 폼 컨트롤 System::Type 객체의 핸들도 얻어와야 합니다. 이 뷰 클래스의 구현 파일은 아래에서 보여지는 것과 같이 아주 간단합니다:
IMPLEMENT_DYNCREATE(CMyWinFormsBasedView, CWinFormsView)
CMyWinFormsBasedView:: CMyWinFormsBasedView ()
: CWinFormsView(WinFormsViewControl::typeid) {}
처음에 보기에는, 이 코드는 실제 세계의 일들을 구현하기에는 너무 많이 간단하게 보이지만, 많은 경우에 위의 코드가 정말로 여러분이 원하는 코드일 것 입니다. 이 뷰 클래스는 view” 클래스 안에 있는 실제의 구현에 대한 간단한 프락시 역할을 합니다.
많은 뷰 구현이 MFC의 CView를 기반으로 해서 가상 함수를 오버라이드 합니다. 네이티브 뷰 클래스를 간단한 프락시처럼 작용하게 하기 위해서는, 여러분은 네이티브 뷰 클래스에 있는 이 메써드들을 오버라이드 해서 view” 윈도우 폼 컨트롤에 있는 동일한 메써드로 함수 호출을 전달해 주어야 합니다. CView의 가장 중요한 세 가지 함수의 오버라이드는, 이미 CWinFormsView 기본 클래스에서 구현이 되었습니다. 이 세 함수들은 OnInitialUpdate, OnUpdate, 그리고 OnActiveView 입니다. 이러한 함수 호출의 전달은 Microsoft::VisualC::MFC::IView라고 명명된 매니지드 인터페이스에 기반을 두고 있고, 이 인터페이스는 Mfcmifc80.dll 어셈블리 안에 정의되어 있습니다. 이러한 함수들을 오버라이드 하기 위해서는, 그림 8 에서 볼 수 있는 view” 윈도우 폼 컨트롤 클래스에 있는 인터페이스를 구현하기 바랍니다.
가상 함수의 오버라이딩에 덧붙여서, 뷰들은 또한 커맨드 핸들러 역시 구현할 수 있습니다. 또 다시, 위와 같은 커맨드 핸들러를 네이티브 뷰 클래스에서 구현해서 윈도우 폼 컨트롤에 함수 호출을 전달할 수도 있습니다. 그러나, 마이크로 소프트는 이런 일이 흔히 발생하는 상황이기 때문에 좀 더 편리하게 이런 일을 할 수 있는 해결책을 제공하고 있습니다. Mfcmifc80.dll 어셈블리에서 이런 경우를 위해서 몇 가지 매니지드 헬퍼 타입을 제공하고 있습니다. MFC”의 명령어 전달 하부구조를 통해서 명령어 메시지를 받기 위해서는, 윈도우 폼 컨트롤은 반드시 Microsoft::VisualC::MFC::ICommandTarget 인터페이스를 구현해야 합니다:
interface class ICommandTarget
{
void Initialize(ICommandSource^ commandSource);
};
ICommandTarget을 구현하는 뷰가 생성되었을 때, ICommandTarget::Initialize가 뷰에서 호출되고 새로운 커맨드 소스 객체가 인자로 전달됩니다. 이 인자는 ICommandSource^ 형을 가지고 있습니다. 이러한 핸들러는 MFC 개발자들에게는 익숙하게 들릴 것 입니다. 이 커맨드 핸들러는 커맨드가 발생해서 컨트롤의 UI가 활성화되었는지, 체크되었는지 등을 커맨드 UI 핸들러가 조정할 수 있게 해줍니다.
특정 핸들러를 등록하기 위해서, AddCommandHandler 그리고 AddCommandUIHandler같은 메써드들은 커맨드 소스에서 호출될 수 있습니다. 이러한 인자들은 두 가지 인자를 받아야 하는데, 커맨드 ID를 대표하는 부호 없는 정수, 그리고 컨트롤의 핸들러 함수를 전달할 때 쓰이는 대리자(delegate)가 바로 그것입니다. 커맨드 핸들러와 커맨드 UI 핸들러가 서로 다른 함수 시그니쳐를 가지고 있기 때문에, 두 개의 다른 대리자 형(delegate type)이 아래와 같이 Microsoft::VisualC::MFC 네임 스페이스 안에 정의되어 있습니다:
delegate void CommandHandler(unsigned int);
delegate void CommandUIHandler(unsigned int,
Microsoft::VisualC::MFC::ICommandUI^);
그림 9 는 ID_EDIT_PASTE 커맨드에 대해서 커맨드 핸들러를 어떻게 등록하는지 보여주고 있습니다.
뷰가 특정한 커맨드를 처리하도록 되어 있다면, 대리자(delegate)는 뷰의 멤버 함수를 호출하는데 쓰입니다. 커맨드 ID가 인자로 전달되고, 만약 모든 커맨드가 그 커맨드 만을 담당하는 핸들러 함수를 가지고 있다면, 이 인자는 중요하지 않지만, 실제로는 하나의 메써드가 하나 이상의 커맨드를 위한 핸들러로 사용될 수 있습니다. AddCommand[UI]RangeHandler는 여러분으로 하여금 여러 개의 커맨드에 대한 하나의 대리자를 등록하는 것을 허영해 줍니다.
커맨드 UI 핸들러에 대한 대리자는 ICommandUI^ 형의 추가적인 인자를 가지고 있습니다. 이러한 파라미터를 이용하여 전달되는 핸들러는 메뉴 아이템 혹은 툴바 버튼들의 외양과 사용성을 조정할 수 있게 해주는 MFC CCmdUI 클래스의 래퍼(wrapper) 클래스 입니다.
ICommandSource::Initialize 안에서 커맨드 핸들러를 추가하는 것에 추가해서, 윈도우 폼 컨트롤의 멤버 변수 안에 ICommandTarget 핸들을 저장해 놓는 것이 유용할 수도 있습니다. 이렇게 하는 것은 개발자로 하여금 나중에 추가적인 커맨드 핸들러를 추가 혹은 삭제할 수 있는 옵션을 제공해 주고, 개발자는 ICommandTarget::SendMessage 혹은 ICommandTarget::PostMessage를 통해서 동기 혹은 비동기적으로 명령어 이벤트를 보낼 수도 있습니다.
아발론을 향한 브릿지 (Bridge to Avalon)
Windows Presentation Foundation (이하 WPF)이 아직 정식으로 발표가 되지 않았기 때문에, MFC의 현재 버전은 CWnds와 CDialogs 안에서 WPF의 Visual과 통합할 수 있는 헬퍼 클래스와 템플릿을 가지고 있지 않습니다. 그러나, 이것이 MFC 응용프로그램에서 Visual과 통합하는 것이 불가능함을 의미하는 것은 아닙니다. 실제로는, 이러한 헬퍼 클래스들이 없어도, CWnd 혹은 CDialog에서 Visual을 호스트하기 위해서 코드 몇 줄이 더 추가될 뿐 입니다.
이러한 통합의 핵심적인 기능은 Windows Presentation Foundation 자체에서 옵니다. Windows.PresentationCore.dll 어셈블리는 System::Windows::Interop라는 네임스페이스를 제공하고 HwndSource라고 불리우는 아주 강력한 클래스를 제공하고 있습니다. 이 클래스는 HWND와 Visual 사이에 다리 역할을 제공하고 있습니다. USER32윈도우 객체를 호스팅하는 것은, 정상적인 HWND를 가진 자식 윈도우를 사용하는 것과 동일합니다. 하지만, 그 윈도우의 RootVisual 속성을 사용하게 되면, 여러분은 Windows Presentation Foundation의 새로운 세계로 스위치한 것과 같습니다. (여기서의 정보가 Windows Presentation Foundation의 정식 이전 버젼에 기초해서 쓰여졌다는 것을 알아 두시고, 이 정보는 향후의 정식 릴리즈에서 변할 수도 있습니다.)
HwndSource 생성자는 HwndSourceParameters 형의 값을 인자로 요구합니다. 그림 10에서 볼 수 있는 것과 같이, 이러한 값 형은 Win32 함수인 CreateWindowEx를 호출하기 위해서 인자로 전달하는 것과 유사한 정보를 가지고 있습니다. 그림 11 은 OnInitDialog 구현에서 HwndSource를 어떻게 사용할 수 있는지 보여 줍니다.
결론
C++ 상호운용성을 이용해서, 매니지드 코드는 아무 문제없이 기존의 C++ 소스와 통합이 될 수 있습니다. 윈도우 폼에 대한 MFC 지원은 이러한 통합이 Visual C++에서 의도되고 지원되는 기능이라는 것을 보여주는 막강한 증거로 불 수 있습니다. 이 통합을 간략히 하기 위해서, 몇몇 헬퍼 클래스와 템플릿이 존재하지만, 통합 계층 자체는 그렇게 복잡하지도 않고, 사용하기에도 어렵지 않습니다.
한 가지 빠진 부분이 있다면 이러한 기능들과 Visual Studio의 위져드(Wizard)와의 통합입니다. 거의 모든 경우에서, 많은 구현이 Visual Studio에서 디자이너 지원이 잘 되는 윈도우 폼 컨트롤에서 행하여지기 때문에 큰 문제가 아닙니다. 또한, Windows Presentation Foundation이 정식으로 발표되면, Visual과 MFC 응용프로그램을 통합하는 데에도 동등한 지원이 제공될 확률이 큽니다. 그런 지원이 없다고 해도, MFC 응용 프로그램에서 Visual을 통합하기는 쉽습니다.
