WPF 및 Win32 상호 운용성

이 항목에서는 WPF(Windows Presentation Foundation) 및 Win32 코드를 상호 운용하는 방법을 간략하게 설명합니다. WPF에서는 애플리케이션을 만들기 위한 다양한 환경을 제공합니다. 그러나 Win32 코드에 상당한 투자를 한 경우 해당 코드 중 일부를 재사용하는 것이 더욱 효과적일 수 있습니다.

WPF 및 Win32 상호 운용성 기본 사항

WPF와 Win32 코드 간에는 상호 운용성에 대한 두 가지 기본 기법이 있습니다.

  • Win32 창에서 WPF 콘텐츠를 호스트합니다. 이 기법을 사용하면 표준 Win32 창 및 애플리케이션의 프레임워크 내에서 WPF의 고급 그래픽 기능을 사용할 수 있습니다.

  • WPF 콘텐츠에서 Win32 창을 호스트합니다. 이 기법을 사용하면 다른 WPF 콘텐츠의 컨텍스트에서 기존 사용자 지정 Win32 컨트롤을 사용하고 경계 간에 데이터를 전달할 수 있습니다.

이 항목에서는 이러한 각 기법을 개념적인 측면에서 소개합니다. Win32에서 WPF를 호스트하는 작업에 대한 자세한 코드 기반 설명은 연습: Win32에서 WPF 콘텐츠 호스팅을 참조하세요. WPF에서 Win32를 호스트하는 자세한 코드 중심 그림은 연습: WPF에서 Win32 컨트롤 호스팅을 참조하세요.

WPF 상호 운용 프로젝트

WPF API는 관리 코드이지만, 대부분의 기존 Win32 프로그램은 관리되지 않는 C++로 작성됩니다. 완전히 관리되지 않는 프로그램에서는 WPF API를 호출할 수 없습니다. 그러나 /clr 옵션을 Microsoft Visual C++ 컴파일러와 함께 사용하면 혼합 관리-비관리 프로그램을 만들 수 있습니다. 이 프로그램에서는 관리 및 비관리 API 호출을 원활하게 혼합할 수 있습니다.

프로젝트 수준의 복잡성 중 하나는 XAML(Extensible Application Markup Language) 파일을 C++ 프로젝트로 컴파일할 수 없다는 것입니다. 이 문제는 다음과 같은 몇 가지 프로젝트 분할 기법을 통해 해결할 수 있습니다.

  • 모든 XAML 페이지를 포함하는 C# DLL을 컴파일된 어셈블리로 만든 다음, C++ 실행 파일에 해당 DLL을 참조로 포함합니다.

  • WPF 콘텐츠에 대한 C# 실행 파일을 만들고 Win32 콘텐츠가 포함된 C++ DLL을 참조하도록 합니다.

  • XAML을 컴파일하는 대신 Load를 사용하여 런타임에 XAML을 로드합니다.

  • XAML은 절대 사용하지 마세요. 모든 WPF를 코드에서 작성하여 Application에서 요소 트리를 구성합니다.

가장 적합한 방법을 사용하세요.

참고

전에 C++/CLI을 사용한 적이 없는 경우 상호 운용 코드 예에 gcnewnullptr과 같은 “새” 키워드가 표시됩니다. 이러한 키워드는 밑줄이 두 개인 이전 구문(__gc)을 대체하고 C++의 관리 코드에 대해 좀 더 자연스러운 구문을 제공합니다. C++/CLI 관리 기능에 대해 자세히 알아보려면 런타임 플랫폼의 구성 요소 확장을 참조하세요.

WPF에서 HWND를 사용하는 방법

WPF "HWND interop"를 최대한 활용하려면 WPF에서 HWND를 사용하는 방법을 이해해야 합니다. HWND의 경우 WPF 렌더링을 DirectX 렌더링 또는 GDI/GDI+ 렌더링과 혼합할 수 없습니다. 여기에는 여러 가지 의미가 내포되어 있습니다. 기본적으로 이러한 렌더링 모델을 혼합하려면 상호 운용 솔루션을 만들고, 사용하도록 선택하는 각 렌더링 모델에 지정된 상호 운용 세그먼트를 사용해야 합니다. 또한, 렌더링 동작은 상호 운용 솔루션에서 수행할 수 있는 작업에 대한 “에어스페이스” 제한 사항도 만듭니다. “에어스페이스” 개념은 기술 영역 개요 항목에 자세히 설명되어 있습니다.

화면의 모든 WPF 요소는 궁극적으로 HWND를 통해 지원됩니다. WPF Window를 만들 때 WPF에서 최상위 HWND를 만들고 HwndSource를 사용하여 Window와 해당 WPF 콘텐츠를 HWND에 둡니다. 애플리케이션의 나머지 WPF 콘텐츠에서 이 단일 HWND를 공유합니다. 예외는 메뉴, 콤보 상자 드롭다운 및 기타 팝업입니다. 이러한 요소는 고유한 최상위 창을 만듭니다. 따라서 WPF 메뉴가 포함된 창 HWND의 가장자리를 넘어 이동할 수 있습니다. HwndHost를 사용하여 HWND를 WPF 내부에 두면 WPF에서는 WPF Window HWND와 관련된 새 자식 HWND의 위치를 지정하는 방법을 Win32에 알립니다.

HWND와 관련된 개념은 각 HWND 내에서, 그리고 각 HWND 간에 투명합니다. 이 내용도 기술 영역 개요 항목에서 설명되어 있습니다.

Microsoft Win32 창에서 WPF 콘텐츠 호스팅

Win32 창에서 WPF를 호스트하는 키는 HwndSource 클래스입니다. 이 클래스는 WPF 콘텐츠를 자식 창으로 UI에 통합할 수 있도록 Win32 창에서 WPF 콘텐츠를 래핑합니다. 다음 접근 방식은 Win32 및 WPF를 단일 애플리케이션에 결합합니다.

  1. WPF 콘텐츠(콘텐츠 루트 요소)를 관리형 클래스로 구현합니다. 일반적으로 클래스는 여러 자식 요소를 포함하거나 DockPanel 또는 Page와 같이 루트 요소로 사용할 수 있는 클래스 중 하나에서 상속됩니다. 후속 단계에서는 이 클래스를 WPF 콘텐츠 클래스라고 하며, 클래스 인스턴스는 WPF 콘텐츠 개체라고 합니다.

  2. C++/CLI를 사용하여 Windows 애플리케이션을 구현합니다. 기존의 관리되지 않는 C++ 애플리케이션으로 시작하는 경우, 일반적으로 /clr 컴파일러 플래그를 포함하도록 프로젝트 설정을 변경하여 이 애플리케이션에서 관리 코드를 호출할 수 있습니다. 이 항목에서는 /clr 컴파일을 지원하는 데 필요한 작업에 대해 자세히 다루지 않습니다.

  3. 스레딩 모델을 STA(단일 스레드 아파트)로 설정합니다. WPF에서는 이 스레딩 모델을 사용합니다.

  4. 창 프로시저에서 WM_CREATE 알림을 처리합니다.

  5. 처리기(또는 처리기에서 호출하는 함수)에서 다음을 수행합니다.

    1. 부모 창 HWND를 해당 parent 매개 변수로 사용하여 새 HwndSource 개체를 만듭니다.

    2. WPF 콘텐츠 클래스의 인스턴스를 만듭니다.

    3. WPF 콘텐츠 개체에 대한 참조를 HwndSource 개체 RootVisual 속성에 할당합니다.

    4. HwndSource 개체 Handle 속성에는 창 핸들(HWND)이 포함됩니다. 애플리케이션의 관리되지 않는 부분에서 사용할 수 있는 HWND를 가져오려면 Handle.ToPointer()를 HWND로 캐스팅합니다.

  6. WPF 콘텐츠 개체에 대한 참조를 보유하는 정적 필드가 포함된 관리 클래스를 구현합니다. 이 클래스를 사용하면 Win32 코드에서 WPF 콘텐츠 개체에 대한 참조를 가져올 수 있지만, 더 중요한 것은 HwndSource가 실수로 가비지 수집되지 않는다는 점입니다.

  7. WPF 콘텐츠 개체 이벤트 중 하나 이상에 처리기를 연결하여 WPF 콘텐츠 개체에서 알림을 받습니다.

  8. 속성, 호출 메서드 등을 설정하기 위해 정적 필드에 저장한 참조를 사용하여 WPF 콘텐츠 개체와 통신합니다.

참고

별도의 어셈블리를 생성한 다음, 이를 참조하는 경우 콘텐츠 클래스의 기본 부분 클래스를 사용하여 XAML에서 1단계용 WPF 콘텐츠 클래스 정의 중 일부 또는 모두를 수행할 수 있습니다. 일반적으로 XAML을 어셈블리로 컴파일하는 작업의 일부로 Application 개체를 포함하지만 상호 운용의 일부로 해당 Application을 사용하지 않는 경우, 애플리케이션에서 참조하는 XAML 파일의 루트 클래스를 하나 이상 사용하고 부분 클래스를 참조합니다. 프로시저의 나머지 부분은 기본적으로 위에서 설명한 것과 비슷합니다.

이러한 각 단계는 연습: Win32에서 WPF 콘텐츠 호스팅 항목에서 코드를 통해 설명되어 있습니다.

WPF에서 Microsoft Win32 창 호스팅

다른 WPF 콘텐츠 내에서 Win32 창을 호스팅하는 키는 HwndHost 클래스입니다. 이 클래스는 WPF 요소 트리에 추가될 수 있는 WPF 요소에 창을 래핑합니다. HwndHost는 호스팅된 창의 메시지를 처리하는 등의 작업을 수행하는 API를 지원합니다. 기본 절차는 다음과 같습니다.

  1. 코드 또는 태그를 통해 WPF 애플리케이션의 요소 트리를 만듭니다. HwndHost 구현을 자식 요소로 추가할 수 있는 요소 트리에서 허용 가능하고 적절한 지점을 찾습니다. 이 단계의 나머지 부분에서는 이 요소를 예약 요소라고 합니다.

  2. HwndHost에서 파생하여 Win32 콘텐츠를 보유하는 개체를 만듭니다.

  3. 해당 호스트 클래스에서 HwndHost 메서드 BuildWindowCore를 재정의합니다. 호스팅된 창의 HWND를 반환합니다. 실제 컨트롤을 반환된 창의 자식 창으로 래핑해야 할 수도 있습니다. 호스트 창에서 컨트롤을 래핑하면 WPF 콘텐츠가 간단한 방식으로 컨트롤에서 알림을 받을 수 있습니다. 이 기법을 사용하면 호스팅된 컨트롤 경계에서 메시지 처리에 관한 일부 Win32 문제를 정정할 수 있습니다.

  4. HwndHost 메서드 DestroyWindowCoreWndProc를 재정의합니다. 이렇게 하는 이유는 프로세스를 정리하고 호스팅된 콘텐츠에 대한 참조를 정리하기 위해서입니다. 특히 관리되지 않는 개체의 참조를 만든 경우에 필요합니다.

  5. 코드 숨김 파일에서 컨트롤 호스팅 클래스의 인스턴스를 만들고 예약 요소의 하위로 설정합니다. 일반적으로 Loaded와 같은 이벤트 처리기를 사용하거나 부분 클래스 생성자를 사용합니다. 런타임 동작을 통해 상호 운용 콘텐츠도 추가할 수 있습니다.

  6. 컨트롤 알림과 같은 선택된 창 메시지를 처리합니다. 다음과 같이 두 가지 방법이 있습니다. 두 방법 모두 메시지 스트림에 대해 동일한 액세스 권한을 제공하므로, 프로그래밍 편리성에 따라 선택하면 됩니다.

    • 재정의한 HwndHost 메서드 WndProc에서 모든 메시지(표시된 메시지만이 아님)의 메시지 처리를 구현합니다.

    • MessageHook 이벤트를 처리하여 호스팅 WPF 요소가 메시지를 처리하도록 합니다. 이 이벤트는 호스팅된 창의 기본 창 프로시저에 전송된 모든 메시지에서 발생합니다.

    • 처리되지 않는 창의 메시지는 WndProc을 사용하여 처리할 수 없습니다.

  7. 관리되지 않는 SendMessage 함수를 호출하는 플랫폼 호출을 사용하여 호스팅된 창과 통신합니다.

다음 단계에 따라 마우스 입력을 사용하는 애플리케이션을 만듭니다. IKeyboardInputSink 인터페이스를 구현하면 호스팅된 창의 탭 이동 지원을 추가할 수 있습니다.

이러한 각 단계는 연습: WPF에서 Win32 컨트롤 호스팅 항목에서 코드를 통해 설명되어 있습니다.

WPF 내의 HWND

HwndHost를 특별한 컨트롤로 간주할 수 있습니다. (기술적으로 HwndHost은(는) FrameworkElement 유래 클래스이며 Control 유래 클래스가 아닙니다. 그러나 상호 운용 목적을 위한 컨트롤로 간주될 수 있습니다.) HwndHost은(는) 호스트된 콘텐츠의 기본 Win32 특성을 추상화하므로 WPF의 나머지 부분에서는 호스트된 콘텐츠를 다른 컨트롤과 유사한 개체로 간주하여 입력을 렌더링하고 처리해야 합니다. 기본 HWND에서 지원할 수 있는 제한 사항에 따라 출력(그리기 및 그래픽)과 입력(마우스 및 키보드)에는 몇 가지 중요한 차이점이 있지만, 일반적으로 HwndHost는 다른 WPF FrameworkElement처럼 동작합니다.

출력 동작의 주요 차이점

  • HwndHost 기본 클래스인 FrameworkElement에는 UI를 변경할 수 있는 몇 가지 속성이 있습니다. 이러한 속성에는 부모로서 해당 요소 내에 있는 요소의 레이아웃을 변경하는 FrameworkElement.FlowDirection과 같은 속성이 있습니다. 그러나 이러한 속성의 대부분은 Win32에 대응되는 속성이 있더라도 해당 속성에 매핑되지 않습니다. 이러한 속성 및 해당 의미는 렌더링 기술에 따라 많이 달라지므로 매핑이 실용적이지 않게 됩니다. 따라서 HwndHostFlowDirection과 같은 속성을 설정해도 아무런 효과가 없습니다.

  • HwndHost는 회전하거나, 배율을 조정하거나 기울일 수 없으며 변환을 통해 달리 변경할 수 없습니다.

  • HwndHostOpacity 속성(알파 혼합)을 지원하지 않습니다. HwndHost 내의 콘텐츠가 알파 정보를 포함하는 System.Drawing 작업을 수행하는 경우 그 자체는 위반이 아니지만, HwndHost는 전체적으로 불투명 = 1.0(100%)만 지원합니다.

  • HwndHost는 동일한 최상위 창의 다른 WPF 요소 위에 표시됩니다. 그러나 ToolTip 또는 ContextMenu에서 생성된 메뉴는 별도의 최상위 창이므로 HwndHost와 올바르게 동작합니다.

  • HwndHost는 부모 UIElement의 클리핑 영역을 무시합니다. 따라서 스크롤 영역 또는 Canvas 내에 HwndHost 클래스를 두려고 하면 문제가 될 수 있습니다.

입력 동작의 주요 차이점

  • 일반적으로 입력 디바이스는 HwndHost 호스트된 Win32 영역 내로 범위가 지정되지만 입력 이벤트는 Win32로 직접 이동합니다.

  • 마우스가 HwndHost 위에 있는 동안에는 애플리케이션에서 WPF 마우스 이벤트를 받지 못하며 WPF 속성 IsMouseOver의 값이 false가 됩니다.

  • HwndHost에 키보드 포커스가 있는 동안에는 애플리케이션에서 WPF 키보드 이벤트를 받지 못하며 WPF 속성 IsKeyboardFocusWithin의 값이 false가 됩니다.

  • 포커스가 HwndHost 내에 있다가 HwndHost 내의 다른 컨트롤로 변경되면 애플리케이션이 WPF 이벤트 GotFocus 또는 LostFocus를 받지 않습니다.

  • 관련 스타일러스 속성과 이벤트는 유사하며 스타일러스가 HwndHost 위에 있는 동안에는 정보를 보고하지 않습니다.

탭 이동, 니모닉 및 액셀러레이터 키

IKeyboardInputSinkIKeyboardInputSite 인터페이스를 사용하면 혼합된 WPF 및 Win32 애플리케이션을 위한 원활한 키보드 환경을 만들 수 있습니다.

  • Win32와 WPF 구성 요소 간 탭 이동

  • 포커스가 Win32 구성 요소 내에 있을 때와 WPF 구성 요소에 있을 때 모두 작동하는 니모닉 및 액셀러레이터 키.

HwndHostHwndSource 클래스는 모두 IKeyboardInputSink의 구현을 제공하지만, 고급 시나리오에 필요한 입력 메시지를 모두 처리하지 못할 수 있습니다. 원하는 키보드 동작을 얻기 위해 적절한 메서드를 재정의합니다.

인터페이스는 WPF와 Win32 영역 간에 전환할 때 발생하는 작업에 대해서만 지원합니다. Win32 영역 내에서의 탭 이동 동작은 전적으로 Win32에 구현된 탭 이동 논리를 통해서만 제어됩니다.

참고 항목