WinRT는 COM이다 - IInspectable, .winmd, 언어 프로젝션, 그리고 WinUI가 지금도 바이너리 계약 위에 서 있는 이유

· 업데이트: · · Windows, WinRT, COM, WinUI, Windows App SDK, Windows 개발

수정 이력(초판, 2026년 08월 29일 공개)
최초 공개

WPF나 WinForms 앱에 Windows의 새 기능을 더하고 싶다. 그런데 WinRT의 피커를 호출하면 예외가 난다. WinUI 이야기를 읽으면 이번에는 UI를 다시 만들어야 할 것처럼도 보인다. 이런 당혹감은 WinRT의 구조와 UI 프레임워크의 선택을 나누어 보면 정리됩니다.

이 글의 출발점은 WinRT도 COM의 바이너리 계약 위에 있다는 점입니다. Microsoft 자신도 「The Windows Runtime is based on COM(Windows Runtime은 COM에 기반한다)」이라고 명시하고 있습니다.1 지난 OLE 객체 글에서 본 Word에 Excel 표를 끼워 넣는 일도, 지금의 WinRT나 WinUI도, 뿌리에는 같은 IUnknown이 있습니다.

여기서는 먼저 WinRT를 이루는 세 가지 요소를 짚고, 다음으로 데스크톱 앱에서 쓸 때 유의할 점을, 마지막으로 기존 자산을 어떻게 다룰지를 생각합니다. 대상 독자는 COM 또는 Windows 데스크톱 개발 경험이 있는 개발자, 전제 환경은 Windows 10/11과 .NET 6 이상(C#) 또는 C++17(C++/WinRT), 난이도는 중급입니다.

1. 먼저 결론

짚어 둘 결론은 세 가지입니다.

  • WinRT는 매니지드 런타임이 아니라, COM을 기반으로 한 ABI(바이너리 계약)입니다.그 계약에, 형식 정보를 전달하는 .winmd와, 각 언어에서 자연스럽게 호출하기 위한 언어 프로젝션이 더해져 있습니다.1234
  • WinRT API의 대부분은 기존 WPF, WinForms, Win32 앱에서 쓸 수 있습니다.다만 HWND 전달, package identity, 스레드 초기화라는 세 가지 전제를 확인해야 합니다.5678
  • WinUI로의 UI 전면 이행과, WinRT API의 부분 사용은 별개의 판단입니다.WinUI도 WinRT ABI 위에 있고, 기존 COM/ActiveX 자산과 WinRT는 같은 기반에서 공존할 수 있습니다.921

이후는 다음 순서로 읽으면 연결 고리를 따라가기 쉬워집니다.

알고 싶은 것 읽을 장
왜 「WinRT는 COM」이라고 말할 수 있는가 2~3장: COM과의 공통점과 IInspectable
왜 C#이나 C++에서 자연스럽게 호출할 수 있는가 4~5장: .winmd와 언어 프로젝션
기존 앱에서 쓰려면 무엇을 확인하는가 6장: HWND, package identity, 아파트먼트
WinUI로 이행해야 하는가 7~9장: WinUI의 발밑, 이행 판단, 알림의 등록 조건

아래 지식 맵은 각 요소의 관계를 되짚어 보기 위한 것입니다. 먼저 설명을 읽고 싶다면 2장부터 순서대로 진행해 주세요.

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 20건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. OLE도 WinRT도, 뿌리는 같은 바이너리 계약

이 블로그에서는 지금까지 COM의 설계 사상, STA/MTA의 스레드 모델, ActiveX/OCX를 다루는 법, OLE 복합 문서처럼 고전 COM의 세계를 쫓아왔습니다. 이들은 모두 1990년대부터의 기술입니다.

한편 WinRT는 Windows 8(2012년)에서 도입된 API 기반입니다. 현재는 Windows.* 네임스페이스의 API군으로서 토스트 알림, 공유, 블루투스, OCR 등을 제공하며, WinUI와 Windows App SDK의 발밑이기도 합니다.10 신구가 전혀 다른 세계처럼 보이지만, 실체는 이어져 있습니다.

공통되는 것은, 인터페이스를 통해 호출한다는 약속

COM 컴포넌트와 WinRT 클래스는 둘 다 인터페이스를 통해 기능을 공개합니다. 기반이 되는 인터페이스를 비교하면 관계는 다음과 같습니다.1

고전 COM WinRT
인터페이스의 기반은 IUnknown 인터페이스의 기반은 IInspectable. 그 기반이 IUnknown

즉 WinRT는 COM과 무관한 구조로 갈아탄 것이 아니라, IUnknown 위에 한 단 더 쌓아 올린 것입니다. 참조 카운트도 QueryInterfaceHRESULT도 그대로 살아 있습니다.

C++/WinRT 문서도 WinRT API를 「COM의 진화(an evolution of COM)」라고 부르며, COM에 기반한 API를 언어 프로젝션을 통해 사용하는 설계라고 설명합니다.211

고전 COM과 WinRT의 계보IUnknown과 vtable에 의한 COM의 바이너리 계약이라는 공통의 기반 위에, 1990년대부터의 OLE, ActiveX 등 고전 COM의 세계와, 2012년 이후의 WinRT와 그 위의 WinUI, Windows App SDK의 세계가 나란히 서 있으며, 양자는 배타적이 아니라 이어져 있다COM의 바이너리 계약(IUnknown, vtable)고전 COM(OLE, ActiveX, 직접 만든 COM)WinRT(IInspectable, .winmd)WinUI / Windows App SDK같은 기반 위에서 공존할 수 있다

그림 1: 고전 COM과 WinRT는 다른 세계가 아니라, 같은 바이너리 계약 위의 신구 두 세대다.

그래서 이 글은 단순한 「새 API 소개」가 아닙니다. 이미 가지고 있는 COM 지식이 2026년의 Windows 개발 어디에 효력을 발휘하는지를 확인하는 글입니다.

3. IUnknown 위의 IInspectable

변하지 않는 기반과, 추가된 세 메서드

WinRT 형식 시스템의 공식 사양에서는 모든 WinRT 인터페이스는 암묵적으로 IInspectable을 요구하고, IInspectableIUnknown을 요구한다고 정하고 있습니다. IUnknown이 정의하는 것은 종전대로 QueryInterface, AddRef, Release의 세 메서드입니다.12

그 위에 IInspectable이 다음 세 메서드를 추가합니다.13

메서드 역할
GetIids 이 객체가 구현하는 인터페이스의 IID 목록을 반환한다
GetRuntimeClassName 정규화된 WinRT 형식 이름(Windows.Storage.StorageFile 등)을 HSTRING으로 반환한다
GetTrustLevel 객체의 신뢰 수준을 반환한다
IUnknown 위에 얹히는 IInspectable모든 WinRT 인터페이스는 IInspectable을 요구하고, IInspectable은 IUnknown을 요구한다. IUnknown이 QueryInterface, AddRef, Release를, IInspectable이 GetIids, GetRuntimeClassName, GetTrustLevel을 제공하고, 그 위에 각 WinRT 인터페이스의 메서드가 얹힌다IUnknown(QI, AddRef, Release)IInspectable(GetIids, 형식 이름, 신뢰 수준)각 WinRT 인터페이스의 메서드

그림 2: WinRT 객체는 IUnknown의 세 메서드 위에 IInspectable의 세 메서드를 쌓고, 그 위에 개별 API가 얹힌다.

형식 이름에서 메서드, 속성, 이벤트의 정의를 끌어낼 수 있다

중요한 것은 추가된 메서드의 개수보다도, 형식 이름과 메타데이터를 이어 붙일 수 있다는 점입니다.

고전 COM에서는 실행 시에 객체의 정체를 아는 표준적인 방법이 「IID를 알고 있고 QueryInterface로 물어보는」 것이었습니다. 스크립트 언어용으로는 IDispatch라는 별도 경로가 있었습니다.

WinRT에서는 GetRuntimeClassName으로 얻은 형식 이름을 다음 장에서 다룰 메타데이터 .winmd로 해석할 수 있습니다. 거기서 메서드, 속성, 이벤트의 완전한 정의를 얻을 수 있습니다. 사양 자체가 메타데이터로 해석 가능한 WinRT 형식 이름을 얻을 수 있다는 점이 「언어 프로젝션을 가능하게 한다(enables language projection)」고 서술하고 있습니다.12

GetRuntimeClassName에서 언어 프로젝션으로호출하는 쪽이 객체의 GetRuntimeClassName을 호출하면 정규화된 WinRT 형식 이름이 반환되고, 그 형식 이름을 Windows Metadata로 해석하면 형식의 완전한 정의를 얻을 수 있으며, 이것이 각 언어로의 프로젝션을 가능하게 한다WinRT 객체형식 이름(GetRuntimeClassName).winmd로 형식 정의를 해석언어 프로젝션이 가능해진다

그림 3: 「실행 시에 형식 이름을 얻을 수 있고, 형식 이름에서 메타데이터를 끌어낼 수 있다」는 점이 WinRT라는 장치의 중심에 있다.

사용자 정의 인터페이스의 「상속」과 「요구」는 나누어 본다

COM에 익숙한 사람이 유의해야 할 차이도 있습니다. WinRT의 형식 시스템에는 사용자 정의 인터페이스끼리의 상속이 없습니다.고전 COM의 IFileSystemBindData2 : IFileSystemBindData 같은 파생을 의도적으로 두지 않고, 대신 「인터페이스 A는 인터페이스 B를 요구한다(requires)」는 선언으로 표현합니다.121

이는 지금까지 본 IUnknownIInspectable이라는 기반 ABI 체인과는 별개의 이야기입니다. 이 기반은 모든 WinRT 인터페이스의 토대로 남아 있습니다.

사용자 정의 계약은 vtable의 상속 레이아웃에 의존하지 않는, 더 느슨한 형태로 옮겨 갔습니다. 그 한편으로 호출의 실체는 지금도 vtable을 거칩니다. 계약을 쓰는 방식의 차이와 호출 구조를 혼동하지 않는 것이 중요합니다.

4. .winmd가 해결한 것 - 형식 정보의 바인딩 지옥

고전 COM의 어려움은 「형식 정보를 배포하는 방식」에 있었다

고전 COM의 실무에서는 인터페이스 구현 자체보다 형식 정보를 어떻게 배포할 것인가에 품이 많이 들었습니다.

사용하는 쪽 형식 정보를 전달하는 경로
C++ IDL로 계약을 쓰고, MIDL로 헤더와 프록시/스텁을 생성한다
VB6, 스크립트 형식 라이브러리(TLB)를 배포한다
.NET 상호 운용 어셈블리를 따로 만든다

TLB에는 자동화에 치우친 형식 제약이 있어, IDL에 쓸 수 있어도 TLB에 들어가지 않는 정보가 있습니다. 게다가 언어마다 경로가 갈라져 있으므로, 어느 하나가 낡으면 형식 불일치로 이어집니다. 이 고생은 형식 라이브러리와 dscom 글이나 DLL, COM 인터페이스의 하위 호환성 글에서 다룬 그대로입니다.

.winmd는, 모든 언어가 읽는 공통의 계약서

WinRT의 답이 Windows Metadata(.winmd)입니다. API를 기계 판독 가능한 메타데이터로 기술하고, 도구와 언어 프로젝션이 그것을 읽어 각 언어용 프로젝션을 생성합니다.3

Windows는 시스템이 제공하는 전체 WinRT API의 메타데이터를 함께 담고 있으며, 실행 시에 네임스페이스나 형식을 해석하는 API도 제공합니다. Windows SDK에는 컴파일 시점용 사본이 들어 있습니다. 서드파티도 자신의 WinRT 컴포넌트에 .winmd를 붙이면 시스템 API와 같은 구조로 언어 프로젝션에 참여할 수 있습니다.3

여기서 바뀐 것은 배포되는 형식 정보의 형태입니다. 언어마다 흩어져 있던 TLB, 헤더, 상호 운용 어셈블리 대신, 하나의 .winmd를 모든 언어의 프로젝션이 읽게 되었습니다.

다만 IDL이 필요 없어진 것은 아닙니다. WinRT 컴포넌트를 만들 때는 지금도 IDL(WinRT용으로 새로 다듬은 MIDL 3.0)로 계약을 기술하고, MIDL 컴파일러가 .winmd를 생성합니다.14

WinRT 컴포넌트의 제작 파이프라인WinRT 컴포넌트의 계약은 지금도 IDL 즉 MIDL 3.0으로 기술하고, MIDL 컴파일러가 그것을 .winmd로 컴파일한다. 배포되는 것은 이 .winmd이며, cppwinrt.exe나 cswinrt.exe 등 각 언어의 프로젝션이 이것을 읽어 프로젝션을 생성한다. 바뀐 것은 IDL이 아니라 배포되는 형식 정보의 형태다계약을 기술(IDL, MIDL 3.0)MIDL 컴파일러.winmd(배포되는 형식 정보)각 언어의 프로젝션을 생성

그림 4: 계약의 입구(IDL)는 현역 그대로이고, 배포되는 형식 정보라는 출구가 .winmd로 통일되었다.

.NET과 같은 파일 형식이어도, 매니지드 런타임은 아니다

.winmd의 물리 형식은 CLR 어셈블리와 같은 ECMA-335 사양을 사용합니다. 다만 유효한 데이터 조합의 규칙은 CLR 어셈블리와 다릅니다. 형식을 빌린 것과, 실행에 CLR이 필요한 것은 별개입니다.3

이 부분은 시스템 API와 서드파티 컴포넌트를 나누어 읽어야 합니다.

대상 .winmd와 구현의 관계
시스템이 제공하는 WinRT API .winmd는 실행 코드를 포함하지 않는 순수한 메타데이터. 구현은 OS의 네이티브 DLL에 있고, 실행에 CLR은 불필요
서드파티의 WinRT 컴포넌트 .winmd가 구현 코드를 포함하는 경우도 있다. 매니지드한(C#으로 쓴) 컴포넌트가 MSIL을 포함하면 실행에 해당하는 .NET 런타임이 필요

.winmd를 도구로 열면 .NET 어셈블리처럼 보이기 때문에 「WinRT는 매니지드」라고 오해하기 쉽습니다. 그러나 시스템이 제공하는 .winmd의 내용물은 COM 인터페이스의 계약서입니다.3

.winmd와 구현의 분리.winmd는 ECMA-335의 물리 형식을 빌린 메타데이터이며, 시스템이 제공하는 것은 실행 코드를 포함하지 않는 계약서이고, 시스템이 제공하는 WinRT API의 구현은 OS의 네이티브 DLL에 있다. 이 분리 덕분에 .winmd가 .NET 어셈블리처럼 보여도 시스템 WinRT API의 실행에 CLR은 불필요하다(서드파티의 매니지드 컴포넌트의 .winmd는 MSIL을 포함하며 .NET 런타임을 필요로 한다)시스템 제공은 코드 없음형식 정의와 구현이 대응.winmd(계약서, ECMA-335 형식)OS의 네이티브 DLL(구현)시스템 API는 CLR 불필요

그림 5: 시스템이 제공하는 WinRT API에서는 .winmd가 계약서, 구현은 OS의 네이티브 DLL. 형식이 .NET 풍이어도 실행은 네이티브 COM.

고전 COM의 형식 정보와 .winmd의 대비고전 COM에서는 IDL에서 C++ 헤더로, 형식 라이브러리에서 VB6나 스크립트로, 상호 운용 어셈블리에서 .NET으로, 언어마다 형식 정보의 경로가 갈라져 편차의 원인이 되었던 것에 비해, WinRT에서는 하나의 .winmd를 모든 언어의 프로젝션이 공통으로 읽는다고전 COM: 경로가 언어마다IDL에서 C++ 헤더로TLB에서 VB6, 스크립트로상호 운용 어셈블리에서 .NET으로.winmd(단일 메타데이터)모든 언어의 프로젝션이 공통으로 읽는다

그림 6: 언어마다 흩어져 있던 형식 정보의 배포 방식을, .winmd는 「하나의 메타데이터를 모두가 읽는」 형태로 접어 넣었다.

제약이 사라진 것이 아니라, 프로젝션하기 쉬움을 축으로 바뀌었다

고전 COM을 아는 사람에게 한마디로 정리하면, .winmd는 「형식 라이브러리의 다시 만들기」입니다. TLB가 하려던 역할을, ECMA-335라는 검증된 형식 위에서, 처음부터 모든 언어 공통의 정본으로 다시 설계한 것이라고 보면 위치가 깔끔해집니다.

다만 제약이 없어진 것은 아닙니다. TLB의 자동화에 치우친 제약이, 「모든 언어로 안전하게 프로젝션할 수 있을 것」을 축으로 한 WinRT 고유 형식 시스템의 제약으로 바뀐 것입니다. 3장에서 본, 사용자 정의 인터페이스끼리의 상속이 없다는 점도 그 한 예입니다.12

따라서 기존 COM/IDL 계약을 그대로 WinRT로 가져올 수 있다고는 할 수 없습니다. API의 재설계가 필요해지는 경우가 있습니다.

형식 라이브러리의 제약에서 WinRT 형식 시스템의 제약으로 교체TLB(형식 라이브러리)가 가지고 있던 자동화에 치우친 표현력의 제약은 .winmd에서 걷힌 것이 아니라, 모든 언어로 안전하게 프로젝션할 수 있을 것을 축으로 한 WinRT 고유 형식 시스템의 제약으로 바뀌었다. 그 한 예가 사용자 정의 인터페이스 상속의 부재이며, 기존 COM/IDL 계약을 그대로 가져올 수 있다고는 할 수 없고 API의 재설계가 필요해지는 경우가 있다교체TLB의 제약(자동화에 치우침)WinRT 형식 시스템의 제약(프로젝션 가능성 축)예: 사용자 정의 상속은 불가기존 COM 계약은 설계 재검토가 필요한 경우 있음

그림 7: TLB의 제약은 「사라진」 것이 아니라, 모든 언어로의 프로젝션 가능성을 축으로 한 다른 제약으로 바뀌었다.

5. C++/WinRT, C#/WinRT는 「래퍼」가 아니라 프로젝션

공통의 계약을, 각 언어에 자연스러운 형태로 보여 준다

.winmd라는 공통의 계약서가 있으면, 각 언어에서의 표현 방식을 도구로 자동 생성할 수 있습니다. 이것이 언어 프로젝션(language projection)입니다. WinRT API를 각 언어의 관용에 맞게 공개하고 COM의 세부 사항을 감춤으로써, 그 언어에 자연스러운 프로그래밍 경험을 제공합니다.411

Microsoft가 현재 지원하는 프로젝션은 다음 두 가지입니다.4

프로젝션 생성하는 것 특징
C++/WinRT cppwinrt.exe.winmd에서 C++용 프로젝션 헤더를 생성 표준 C++17의 헤더 파일 기반 프로젝션. C++/CX 같은 언어 확장이 필요 없고, C++/CX와 WRL의 후속11
C#/WinRT(CsWinRT) cswinrt.exe.winmd에서 C# 코드를 생성해 상호 운용 어셈블리로 만든다 .NET용 프로젝션. 런타임에서 독립한 툴체인1516

C# 쪽 경위는 조금 헷갈리므로 구분해 둡니다. .NET Core 3.x까지는 WinRT/winmd의 사용을 .NET 런타임이 기본 제공으로 지원했습니다. .NET 5에서 그 기본 제공 지원이 제거되고 C#/WinRT로 옮겨 갔습니다.16 WinRT API를 쓸 수 없게 된 것이 아니라, 프로젝션을 담당하는 곳이 바뀐 것입니다.

지금 C#에서 net8.0-windows10.0.19041.0 같은 TFM을 지정하면 Windows SDK의 프로젝션 어셈블리가 자동 참조됩니다.10

.winmd에서 각 언어로의 프로젝션 생성하나의 .winmd를 cppwinrt.exe가 읽으면 C++17용 프로젝션 헤더가, cswinrt.exe가 읽으면 C#용 상호 운용 어셈블리가 생성되어, 각 언어의 관용에 맞는 형태로 WinRT API가 공개된다.winmd(API의 계약서)cppwinrt.exe에서 C++17 헤더로cswinrt.exe에서 C# 상호 운용 어셈블리로C++의 관용으로 호출할 수 있다C#의 관용으로 호출할 수 있다

그림 8: 프로젝션은 손으로 쓴 래퍼가 아니라, 계약서(.winmd)에서 도구가 기계적으로 생성한다.

자동 생성되어도, 호출의 실체는 COM 그대로

이 글에서 「래퍼」가 아니라 「프로젝션」이라고 부르는 것은, 사람이 API마다 뒤따라가는 번역 계층이 아니라 메타데이터에서 기계적으로 도출하는 구조임을 강조하기 위해서입니다. .winmd에 실려 있는 API를 처음부터 모든 지원 언어에서 쓸 수 있는 형태로 만들 수 있습니다.

그리고 어느 언어에서 호출하든, 프로젝션 아래에서 일어나는 일은 같은 COM 호출입니다. 예를 들어 C#에서 await picker.PickSingleFolderAsync()라고 쓰면, 프로젝션은 WinRT의 IAsyncOperation을 .NET의 Task 세계로 이어 줍니다. 그래도 ABI 쪽에서는 vtable을 거치는 메서드 호출과 HRESULT가 사용됩니다. 오류가 COM 예외(0x80070005 같은 HRESULT)의 형태로 나타나는 것은 이 때문입니다.

C# 코드에서 OS의 WinRT API까지의 계층앱의 C#이나 C++ 코드는 언어 프로젝션을 통해 WinRT의 ABI 즉 IInspectable의 vtable 호출로 변환되어 OS가 구현하는 WinRT API에 도달한다. 프로젝션이 COM의 세부 사항을 감추고 있을 뿐, 호출의 실체는 COM이다앱의 코드(C#, C++)언어 프로젝션WinRT ABI(IInspectable의 vtable)OS의 WinRT API 구현

그림 9: 프로젝션이 감추고 있는 것은 COM의 「세부 사항」이지, COM 그 자체가 아니다.

이 구조를 알고 있으면 문제를 두 계층으로 나눌 수 있습니다. 생성 코드의 버전 불일치나 TFM 설정 누락이라면 프로젝션 계층, HRESULT, 아파트먼트, 참조 카운트라면 ABI 계층입니다. 후자에서는 COM 개발 경험이 그대로 무기가 됩니다.

6. 데스크톱 앱에서 막히는 지점 - HWND, identity, 아파트먼트

먼저 「API를 참조할 수 있는 것」과 「동작 조건」을 나눈다

WinRT API의 대부분은 WPF, WinForms, Win32 데스크톱 앱에서 호출할 수 있습니다.5 호출하기 위한 입구는 C#과 C++에서 다음과 같이 다릅니다.10

환경 최초의 설정
C#/.NET 6 이상 TargetFrameworknet8.0-windows10.0.19041.0 같은 Windows OS 버전이 붙은 TFM으로 한다
C++ Microsoft.Windows.CppWinRT NuGet 패키지를 도입하고, C++17 이상에서 C++/WinRT를 사용한다

다만 참조할 수 있게 되었다고 해서 모든 API가 그대로 동작하는 것은 아닙니다. 다음 세 가지를 확인합니다. 토스트 알림의 등록 조건은 9장에서 따로 정리합니다.

확인할 것 주된 대처
표시 대상 창을 필요로 하는가 UI에 대응하는 COM interop으로 HWND를 넘긴다
package identity가 필요한가 MSIX 또는 외부 위치를 가리키는 패키지로 identity를 부여한다
스레드가 WinRT용으로 초기화되어 있는가 네이티브 코드에서는 STA/MTA를 지정해 초기화한다. C#에서는 보통 런타임 쪽이 처리한다

막히는 지점 1: 피커 등의 UI에는 HWND를 넘긴다

일부 피커나 대화 상자, 공유 UI는 UWP의 CoreWindow를 표시 대상으로 상정하고 있습니다. 데스크톱 앱에는 CoreWindow가 없으므로, 표시하기 전에 소유자 창의 HWND를 명시적으로 넘겨야 합니다.6

피커 등에서 사용하는 입구가 IInitializeWithWindow라는 COM 인터페이스입니다. 이것은 IUnknown을 상속하며, 데스크톱 앱에서 쓰는 WinRT 객체에 소유자 창을 제공합니다.17

C#에서는 먼저 사용 중인 UI 프레임워크에 따라 HWND를 얻습니다.186

소유자 창 HWND를 얻는 방법
WinUI의 Window WinRT.Interop.WindowNative.GetWindowHandle
WPF의 창 WindowInteropHelper
WinForms의 폼 폼의 Handle 속성

다음으로 WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd)로 피커에 넘기고, 그 후에 표시합니다. C++/WinRT라면 객체를 as<IInitializeWithWindow>()로 얻은 뒤 Initialize(hwnd)를 호출합니다. 초기화를 생략하면 예외가 나거나, 조용히 실패합니다.1819

모던한 WinRT 객체에 고전 COM의 인터페이스를 QueryInterface 해서 초기화한다. 이 다리 놓기가 공식적인 작법이 되어 있다는 점에서도, WinRT가 COM이라는 사실이 드러납니다.

공유 UI는 다른 인터페이스입니다.DataTransferManager에 대해서는 IInitializeWithWindow를 쓰지 않습니다. 전용 IDataTransferManagerInterop을 사용해 ShowShareUIForWindow에 HWND를 넘깁니다.6

새로운 Windows App SDK의 피커도 별도 경로입니다.Microsoft.Windows.Storage.Pickers는 생성자에서 WindowId를 받으므로 InitializeWithWindow 패턴은 필요 없습니다. 다만 TFM 설정만으로 쓸 수 있는 API는 아닙니다. Windows App SDK 도입에 더해, unpackaged 앱에서는 배포 대상에 런타임을 배치하고 초기화하는 것이 전제입니다.19

데스크톱 앱에서 피커를 표시하는 순서데스크톱 앱이 피커를 생성한 뒤 그대로 PickSingleFolderAsync를 호출하면 예외나 조용한 실패가 되므로, 먼저 소유자 창의 HWND를 얻어 IInitializeWithWindow의 Initialize로 넘긴 다음에 표시해야 한다피커(WinRT)데스크톱 앱피커(WinRT)데스크톱 앱alt[HWND를 넘기지 않고 표시][먼저 HWND를 넘긴다]생성PickSingleFolderAsync예외 또는 조용한 실패IInitializeWithWindow로 HWND를 설정PickSingleFolderAsync피커가 표시된다

그림 10: 「데스크톱에는 CoreWindow가 없다」는 점을 HWND의 명시적 전달로 메우는 것이 공식 작법.

막히는 지점 2: package identity가 필요한 API가 있다

토스트 알림의 기록(ToastNotificationHistory), 점프 목록, 공유 대상 등 일부 WinRT API는 package identity를 가진 앱(패키징된 앱)에서만 동작합니다. 기존형 설치 관리자로 배포하는 unpackaged 앱에서 호출하면 실패합니다.7

대처는 두 가지입니다. MSIX로 패키징하거나, 기존 설치 관리자를 유지한 채 식별자를 부여하는 「외부 위치를 가리키는 패키지(이른바 sparse package)」를 사용합니다.20 어떤 API가 identity를 요구하는지는 공식 목록에서 확인할 수 있습니다.7

package identity가 필요한 API로 가는 두 경로package identity가 필수인 WinRT API를 unpackaged 앱에서 호출하면 실패하므로, MSIX로 패키징하거나 기존 설치 관리자를 유지한 채 식별자만 부여하는 외부 위치를 가리키는 패키지 중 하나로 package identity를 갖게 한다identity가 필수인 API를 쓰고 싶다MSIX로 패키징외부 위치를 가리키는 패키지package identity를 획득알림 기록 등이 동작한다

그림 11: identity가 필수인 API에 대한 대처는 「MSIX」냐 「기존 설치 관리자 + 식별자 부여」냐의 둘 중 하나.

막히는 지점 3: 스레드 초기화와 STA/MTA를 확인한다

WinRT 객체를 다루는 스레드는 사전에 WinRT용 초기화가 필요합니다. 네이티브 코드에서는 RoInitializewinrt::init_apartment를 사용해 STA/MTA라는 동시성 모델을 지정합니다. C#의 WPF/WinForms 앱에서는 보통 런타임 쪽이 초기화를 처리합니다.8

RoInitialize는 COM의 CoInitializeEx와 같은 틀에 속하는, WinRT 세대의 입구입니다. CoInitialize의 문서 자체도 Windows Runtime을 사용하는 경우에는 대신 RoInitialize 또는 Windows::Foundation::Initialize를 호출하라고 안내하고 있습니다.21

WPF/WinForms의 UI 스레드는 STA일 것, UI 객체는 UI 스레드에서 다룰 것, 블로킹 대기가 STA에서 데드락을 부른다는 것. STA/MTA 글에서 다룬 사고방식은 WinRT API에서도 그대로 살아 있습니다.

HWND나 identity를 갖춰도 쓸 수 없는 API는 있다

여기까지의 대처는 데스크톱 사용을 위한 입구가 있는 API의 이야기입니다. CoreWindowApplicationView 자체에 의존하는 API는 데스크톱 앱에서는 쓸 수 없습니다.이 경우에는 초기화를 더할 것이 아니라 대체 API를 찾습니다.57

데스크톱에서 WinRT API를 호출할 때 막히는 지점의 분기먼저 스레드가 WinRT용으로 초기화되어 있는지 확인하고(네이티브 코드에서는 RoInitialize 등으로 STA/MTA를 명시적으로 지정한다. C#에서는 보통 런타임 쪽이 처리한다), 그 위에서 호출하려는 WinRT API가 CoreWindow를 전제로 하는 UI 계열이면 IInitializeWithWindow로 HWND를 넘기거나 새 피커를 쓰고, package identity가 필수이면 MSIX 또는 외부 위치를 가리키는 패키지로 식별자를 부여하며, CoreWindow나 ApplicationView 자체에 의존하는 API는 데스크톱에서는 쓸 수 없으므로 대체 API를 찾고, 그 밖의 대부분의 API는 TFM이나 C++/WinRT 설정만으로 그대로 호출할 수 있다UI 계열identity 필수CoreWindow 자체에 의존그 외스레드 초기화호출하려는 API의 성질은?네이티브는 명시 지정, C#은 보통 자동HWND 또는 새 피커MSIX 또는 식별자 부여대체 API를 찾는다그대로 호출할 수 있다

그림 12: 스레드 초기화(네이티브 코드에서는 명시적으로, C#에서는 보통 런타임에 맡김)를 전제로, 막히는 지점은 세 계통으로 분류할 수 있고 각각 정형화된 대처가 있다.

COM과 WinRT의 스레드 초기화 대응고전 COM에서는 CoInitializeEx로 STA 또는 MTA를 지정해 스레드를 초기화하는 데 비해, WinRT에서는 RoInitialize로 마찬가지로 STA 또는 MTA의 동시성 모델을 지정해 초기화한다. CoInitialize의 문서도 WinRT 사용 시에는 RoInitialize를 호출하라고 안내하고 있으며, 아파트먼트라는 사고방식은 공통이다고전 COM: CoInitializeEx아파트먼트(STA / MTA)WinRT: RoInitializeUI 스레드는 STA, 대기에 주의

그림 13: 초기화 API의 이름은 바뀌어도, 아파트먼트라는 개념은 같은 것이 계속 사용되고 있다.

7. WinUI의 발밑 - 공개 개발이 되어도 계약은 변하지 않는다

공개 개발로의 이행은, 바이너리 계약의 변경이 아니다

Microsoft는 WinUI의 본류 개발을 GitHub의 공개된 장으로 옮기는 방침을 2025년 여름에 단계적 접근으로 공식 발표했습니다. 미러의 갱신 빈도를 높이고, 로컬 빌드를 가능하게 하고, 테스트를 갖춘 뒤 커뮤니티의 기여를 받아들이고, 최종적으로 GitHub를 개발의 주 거점으로 삼는다는 네 단계입니다.22

이 글의 공개 시점(2026년 8월 말)에는 공식 문서도 「WinUI는 공개된 장에서 개발되고 있다(built in the open)」고 명시하고 있습니다. 공개 리포지토리에서 나날의 엔지니어링 진척을 따라갈 수 있게 되어 있습니다.9

WinForms에서 WPF, UWP, WinUI로 세대 교체를 쫓아온 개발자가 「또 프레임워크가 바뀌는 건가」라고 느끼는 것은 자연스럽습니다. 그러나 바뀌어 온 UI 프레임워크와, 그 아래의 기반은 나누어 볼 필요가 있습니다. Win32와 COM, 그리고 2012년 이후의 WinRT ABI는 같은 자리에 계속 있어 왔습니다.

WinUI의 창도, HWND에 뒷받침되어 있다

WinUI는 Windows App SDK의 일부로 제공되는 WinRT의 API군입니다.923Microsoft.UI.Xaml.Window는 UWP 세대의 CoreWindow 기반 창 모델을 대체하는, HWND에 뒷받침된 창입니다. 공식 상호 운용 튜토리얼도 먼저 창 핸들을 얻는 데서 시작합니다.24

즉 WinUI 앱은 HWND의 창 위에서, IInspectable의 계약을 따르는 객체 트리가 동작하고 있는 Win32 앱입니다. COM에서 익힌 QueryInterface, 참조 카운트, 아파트먼트의 지식도, Win32에서 익힌 HWND와 메시지 루프의 지식도, WinUI의 문제 해결에 그대로 통용됩니다.

UI 프레임워크의 세대 교체와 변하지 않는 기반WinForms, WPF, UWP의 XAML, WinUI로 UI 프레임워크는 세대 교체를 거듭해 왔지만, WinForms와 WPF는 Win32와 COM의 기반에 직접 얹히고, UWP의 XAML과 WinUI는 WinRT ABI를 거쳐 같은 Win32와 COM의 기반에 얹힌다. 세대 교체를 해 온 것은 위의 계층이고, 발밑의 계약은 변하지 않았다UI 프레임워크의 세대 교체WinForms, WPFUWP의 XAMLWinUI(현재)WinRT ABI(2012년~)변하지 않는 기반(Win32 + COM)

그림 14: 부침을 거듭해 온 것은 프레임워크 계층. WinForms, WPF는 Win32+COM에 직접, UWP, WinUI는 WinRT ABI를 거쳐, 같은 기반에 얹혀 있다.

WinUI 앱을 떠받치는 계층WinUI의 XAML과 컨트롤은 Windows App SDK의 일부로 제공되어 WinRT의 ABI 즉 IInspectable의 계약 위에서 동작하며, 그 아래는 COM과 Win32의 HWND라는 기반이다. UI 프레임워크의 세대 교체 아래에서 이 발밑의 계약은 변하지 않았다WinUI(XAML, 컨트롤)Windows App SDKWinRT ABI(IInspectable)COM + Win32(HWND)

그림 15: WinUI 아래에는 WinRT ABI가 있고, 그 아래는 고전 COM과 Win32다. 쌓는 방식이 바뀌었을 뿐 기반은 같다.

XAML Islands에 의한 부분 혼재는, 세대별 제약을 확인한다

「기존 WPF/WinForms 화면에 WinUI 컨트롤만 섞는다」는 전략은 XAML Islands의 세대를 나누어 평가해야 합니다.

세대 WPF/WinForms에서 쓸 때의 상황
UWP 세대의 XAML Islands Windows Community Toolkit의 래퍼 컨트롤이 있다. 다만 WPF/WinForms용은 .NET Core 3.x 세대까지이고, 현행 .NET에서는 지원되지 않는다25
WinUI 3 세대 Windows App SDK의 DesktopWindowXamlSource로 WPF, WinForms, Win32에서 호스팅할 수 있다. 다만 UWP 세대 같은 편리한 래퍼 컨트롤이 없어, 호스팅 API를 직접 다루는 구현과 검증의 부담이 있다26

단계적 혼재를 계획한다면, 컨트롤 단위의 혼재만을 전제로 하지 않는 편이 안전합니다. 기능 단위의 WinRT API 사용(6장)이나, 화면, 프로세스 단위의 분리를 축으로 생각하는 편이 2026년 시점에서는 견실합니다.

8. 업무 앱에 대한 함의 - 전면 이행과 부분 사용은 별개의 문제

「WinRT는 새로운 다른 세계이므로, 쓰려면 기존 자산을 버리고 다시 만드는 수밖에 없다」. 수탁 개발 현장에서 만나는 이 오해는 둘로 나누면 풀립니다.

기존 COM 자산과 WinRT는 공존할 수 있다

Excel COM 자동화를 쓰고, ActiveX 컨트롤을 얹고, 자사 COM 컴포넌트를 호출하고 있는 WPF 앱에 WinRT API를 더할 수 있습니다. 특별한 곡예가 아니라, 같은 COM 기반 위에서 기능을 조합하고 있기 때문입니다.

예를 들어 토스트 알림이라면, TFM을 설정해 API를 참조하고, 알림을 내보내기 위한 등록 조건을 충족합니다. 후자를 잊지 않도록 9장에서 경로별로 정리합니다. C++/WinRT에서는 WinRT와 고전 COM 양쪽 인터페이스를 winrt::com_ptrwinrt::implements라는 같은 구조로 다룰 수 있습니다.21

UI의 쇄신과, 기능의 추가는 따로따로 견적을 낸다

「UI를 WinUI로 전면 이행할 것인가」와 「WinRT API를 필요한 곳에만 쓸 것인가」는 규모도 기간도 위험도 다른 판단입니다.

UI 전면 이행은 프레임워크 선정의 문제로, 화면 자산, 서드파티 컨트롤, 개발 체제에 좌우됩니다. 이 판단은 WinForms/WPF/WinUI 고르는 법에서 다루었습니다. 한편 WinRT API의 부분 사용은 기존 앱에 오늘부터 착수할 수 있는 작은 개선입니다.

토스트 알림만 원하는 안건에서 UI 전면 이행을 견적 낼 필요는 없습니다. 반대로 「WinUI로 이행하지 않으니까」라는 이유로 WinRT API의 사용까지 보류할 필요도 없습니다.

전면 이행과 부분 사용은 별개의 판단WinUI로의 UI 전면 이행은 프레임워크 선정의 큰 판단으로 화면 자산이나 체제에 좌우되는 데 비해, WinRT API의 부분 사용은 TFM 설정 등으로 기존 WPF나 WinForms 앱에 오늘부터 더할 수 있는 작은 판단이며, 이 둘을 혼동하지 말고 나누어 검토한다「새로운 Windows 기능을 쓰고 싶다」UI 전면 이행(큰 판단)WinRT API 부분 사용(작은 판단)화면 자산, 체제에 좌우기존 앱에 오늘부터 더할 수 있다

그림 16: 「새로운 기능」으로 가는 길은 두 갈래이며, 혼동하면 견적도 판단도 어긋난다.

9. 판단표 - 남기기, 감싸기, 교체하기의 WinRT판

상황별로, 필요한 변경만 고른다

ActiveX 판단표의 연장으로서, WinRT 쪽의 상황별 판단을 정리합니다.

상황 권장 이유
WPF/WinForms에서 토스트, 공유, 블루투스 등 WinRT API를 쓰고 싶다 TFM 설정(또는 C++/WinRT 도입)으로 부분 사용 UI 이행 없이 오늘부터 호출할 수 있다. 다만 공유 UI는 IDataTransferManagerInterop 경유(6장), 토스트는 표 아래의 보충을 참조106
피커, 대화 상자에서 예외나 조용한 실패 IInitializeWithWindow로 HWND를 넘긴다. 신규는 WindowId를 지원하는 새 피커(Windows App SDK 도입이 전제) CoreWindow를 전제로 한 설계를 HWND로 메우는 공식 작법619
알림 기록, 점프 목록 등이 동작하지 않는다 MSIX 또는 외부 위치를 가리키는 패키지로 package identity를 부여 identity가 필수인 API는 패키징이 전제720
기존 COM/ActiveX/OLE 자산 버리지 않는다. 남기기/감싸기/교체하기는 자산별로 판단 WinRT와 배타적이지 않고 같은 기반에서 공존할 수 있다(8장)
신규 데스크톱 앱의 UI WinUI를 제1 후보로 평가(WPF도 현역) 공개 개발로 투자 방향이 명확해졌다. 발밑은 WinRT ABI229
기존 WPF/WinForms 화면에 WinUI 컨트롤을 부분 혼재 래퍼 부재로 인한 구현 부담을 감안해 신중하게 평가 UWP 세대 Islands는 .NET Core 3.x까지, WinUI 3 세대는 호스팅 API뿐2526
「WinRT는 UWP와 함께 끝났다」는 전제의 계획 전제를 수정한다 WinRT는 데스크톱에서 호출할 수 있는 현행 API 기반5

토스트 알림은, TFM 설정만으로는 표시되지 않는다

API를 호출할 수 있게 하는 설정과, 알림을 표시하기 위한 등록은 별개입니다.「빌드는 통과하는데 알림이 뜨지 않는」 때에는 호출하는 쪽뿐 아니라, 사용하는 경로, 패키지 형태, 권한 상승 여부를 확인합니다.

기존의 ToastNotificationManager를 쓰는 경우

package identity를 갖지 않는 unpackaged 앱에서는, AppUserModelID(AUMID)를 할당한 시작 메뉴 바로 가기의 등록이 전제입니다. 이것이 없으면 토스트를 내보낼 수 없습니다.27

MSIX 등으로 패키징한 앱은 패키지의 identity가 AUMID를 주므로, 이 수동 등록은 필요 없습니다.

Windows App SDK의 AppNotificationManager를 쓰는 경우

현재 권장되는 이 경로에서는 Windows App SDK 도입과, 시작 시의 Register() 호출이 필요합니다. 그 위에서 패키지 형태에 따라 조건이 갈립니다.28

패키지 형태 추가로 확인할 것
unpackaged 배포 대상 각 PC에 Windows App SDK 런타임의 배치가 필요. Register()가 COM 서버 등록을 수행한다
MSIX로 패키징 Register()에 의한 자동 등록은 동작하지 않는다. Package.appxmanifest에 COM 활성자를 선언한다

나아가 Windows App SDK 경로에서는 관리자로 권한이 상승된 프로세스에서의 알림이 미지원입니다. Show는 예외를 던지지 않고 조용히 실패합니다. 권한 상승이 필요한 앱에서는 알림만 비상승 프로세스에 맡기는 분리를 검토해 주세요.28

등록 관련 구현의 세부 사항은 트레이 상주와 알림 구현 가이드에서 다루고 있습니다.

토스트 알림의 두 경로와 필요한 등록데스크톱 앱에서 토스트 알림을 내보내려면, 기존의 ToastNotificationManager 경로에서는 packaged인지 여부로 분기하며, unpackaged 앱은 AppUserModelID를 할당한 시작 메뉴 바로 가기의 등록을 거쳐야 하고, packaged 앱은 패키지의 identity가 AppUserModelID를 준다. Windows App SDK의 AppNotificationManager 경로에서는 SDK 도입과 시작 시의 Register 호출에 더해, unpackaged 앱은 배포 대상 각 PC로의 런타임 배치를, MSIX로 패키징한 앱은 매니페스트로의 COM 활성자 선언을 충족해야 비로소 앞으로 나아갈 수 있다. 나아가 Windows App SDK 경로에는 권한 상승 프로세스인지 여부의 분기가 있어, 관리자로 권한이 상승된 프로세스에서의 알림은 미지원이고 Show는 예외를 던지지 않고 조용히 실패하므로, 이 경로에서 알림이 표시되는 것은 비상승 프로세스인 경우로 한정되며, 권한 상승이 필요한 앱은 알림을 비상승 프로세스로 분리한다아니오아니오아니오토스트를 내보내고 싶다기존: ToastNotificationManagerWASDK: AppNotificationManagerpackaged?AUMID 바로 가기 등록identity가 AUMID를 제공SDK 도입 + Register()packaged?런타임 배치매니페스트에 COM 선언알림이 표시된다권한 상승 프로세스?미지원: Show가 조용히 실패알림은 비상승 프로세스로 분리

그림 17: 어느 경로든 패키징 형태에 따른 전제를 충족해야 비로소 표시에 도달할 수 있다. Windows App SDK 경로는 전제가 모두 갖춰져도 권한 상승 프로세스에서는 표시되지 않는다.

마지막으로, 남기기, 감싸기, 교체하기로 돌아간다

새로운 기능이 필요한 것인가, UI 자체를 쇄신하고 싶은 것인가. 이 물음으로 돌아가면 WinRT를 쓰는 것과 기존 자산을 교체하는 것을 떼어 놓고 판단할 수 있습니다.

기존 자산과 WinRT를 대하는 방식의 판단 흐름기존 데스크톱 앱을 기점으로, 새로운 Windows 기능이 필요 없으면 남기고, 기능이 필요하면 WinRT API의 부분 사용으로 감싸며(그때는 6장의 HWND, package identity, 스레드 초기화라는 막히는 지점에 주의), UI 자체의 쇄신이 필요한 경우에 한해 WinUI로의 교체를 검토한다는 단계적 판단의 흐름현재 상태로 충분새로운 기능UI의 쇄신기존 데스크톱 앱무엇이 필요한가?남긴다(그대로 유지보수)감싼다(WinRT API 부분 사용)교체한다(WinUI를 평가)HWND, identity, 초기화에 주의(6장)

그림 18: ActiveX 판단표와 같은 「남기기, 감싸기, 교체하기」의 구도가 WinRT 쪽에도 그대로 들어맞는다.

10. 정리

WinRT는 COM과 분리된 새로운 실행 환경이 아닙니다. COM의 바이너리 계약에, 메타데이터와 각 언어로의 프로젝션을 조합한 API 기반입니다.

요소 이 글에서 짚은 역할
IUnknownIInspectable QueryInterface, 참조 카운트를 기반으로 하고, 형식 이름 등을 얻는 세 메서드를 추가한다
.winmd 형식 이름에서 정의를 끌어낼 수 있는, 모든 언어 공통의 계약서. ECMA-335 형식을 빌리지만 시스템이 제공하는 것에는 실행 코드가 들어 있지 않다
C++/WinRT, C#/WinRT 계약서에서 각 언어용 API를 생성한다. C#의 프로젝션은 .NET 5 이후 런타임에서 독립한 툴체인이 되었다

겉모습이 C#이나 C++의 자연스러운 API가 되어도, 그 아래에서는 vtable을 거치는 COM 호출과 HRESULT가 사용됩니다. 그래서 데스크톱에서의 HWND 전달, package identity, STA/MTA와 스레드 초기화를 이해하면 WinRT의 문제도 가려내기 쉬워집니다.

WinUI의 본류 개발을 공개된 장으로 옮기는 노력은 2025년 여름에 단계적 접근으로 발표되었습니다. 이 글의 공개 시점에는 공식 문서에도 「built in the open」이라고 되어 있지만, 그 아래의 IUnknownIInspectable이라는 계약이 바뀐 것은 아닙니다.229

업무 앱에서의 결론도 같습니다. 기존 COM/ActiveX 자산과 WinRT는 공존할 수 있습니다. UI의 전면 이행과 WinRT API의 부분 사용을 혼동하지 않는 것이 견적과 판단의 정확도를 지켜 줍니다.

OLE 글에서 본 1990년대의 복합 문서도, 현재의 WinUI도, 같은 IUnknown 위에 있습니다. COM을 안다는 것은 단순히 「레거시에 밝다」는 뜻이 아닙니다. 지금 Windows의 발밑을 읽어 낼 수 있다는 뜻입니다.

관련 글

관련 상담 영역

합동회사 고무라소프트에서는 COM 컴포넌트 개발과, COM 자산을 포함한 Windows 업무 앱의 유지보수, 수정, 교체를 다루고 있습니다. 「WPF 그대로 토스트 알림이나 피커를 쓰고 싶다」 「WinRT API를 호출했더니 예외로 멈췄다」 「WinUI로 이행해야 할지, 기존 자산을 살려야 할지 판단하고 싶다」처럼, 이 글의 내용이 그대로 논점이 되는 단계부터 상담하실 수 있습니다.

참고 링크

  1. Microsoft Learn, Author COM components with C++/WinRT. COM 컴포넌트와 WinRT 클래스가 둘 다 인터페이스로 기능을 공개한다는 점, 「The Windows Runtime is based on COM」의 명시, 고전 COM의 인터페이스가 IUnknown에서, WinRT의 인터페이스가 IInspectable에서 파생하고 IInspectable이 IUnknown에서 파생한다는 점, IFileSystemBindData2 : IFileSystemBindData 같은 사용자 정의 인터페이스끼리의 파생이 고전 COM의 기능이며 WinRT의 형식 시스템에는 의도적으로 존재하지 않는다는 점에 대하여.  2 3 4 5 6

  2. Microsoft Learn, Consume COM components with C++/WinRT. COM에서는 객체가 아니라 인터페이스를 통해 프로그래밍한다는 점, 그것이 COM의 진화(an evolution of COM)인 WinRT API의 무대 뒤에서도 성립한다는 점, winrt::com_ptr에 의한 COM 스마트 포인터로 WinRT와 고전 COM을 같은 방식으로 다룰 수 있다는 점에 대하여.  2 3 4

  3. Microsoft Learn, Windows Metadata (WinMD) files. WinRT API가 .winmd라는 기계 판독 가능 메타데이터로 기술되어 도구와 언어 프로젝션이 이용한다는 점, Windows가 시스템 제공 전체 WinRT API의 메타데이터를 함께 담고 해석용 API를 제공한다는 점, 서드파티도 같은 형식으로 언어 프로젝션에 참여할 수 있다는 점, 물리 형식이 ECMA-335 사양(CLR 어셈블리와 동일)이며 시스템 제공 WinMD가 순수한 메타데이터라는 점에 대하여.  2 3 4 5

  4. Microsoft Learn, Windows Runtime (WinRT) language projections. 언어 프로젝션이 WinRT API를 각 언어의 관용에 맞게 공개한다는 점, .winmd가 WinRT API를 정의하고 프로젝션이 그것을 읽는다는 점, Microsoft가 지원하는 것은 C++/WinRT(C++17 이상)와 C#/WinRT(.NET) 두 가지라는 점에 대하여.  2 3

  5. Microsoft Learn, WinRT APIs callable from a desktop app. 대부분의 WinRT API가 .NET 및 네이티브 C++ 데스크톱 앱에서 이용할 수 있다는 점, CoreDispatcher, CoreWindow, ApplicationView 등 UWP 전용으로 설계된 클래스가 예외라는 점에 대하여.  2 3 4

  6. Microsoft Learn, Display WinRT UI objects that depend on CoreWindow. 일부 피커, 팝업, 대화 상자가 CoreWindow에 의존한다는 점, CoreWindow가 데스크톱 앱에서 지원되지 않는다는 점, IInitializeWithWindow(또는 동등한 IDataTransferManagerInterop)를 구현하는 클래스에는 표시 전에 소유자 창의 HWND를 설정할 수 있다는 점, WinUI 3, WPF, WinForms 각각에서의 절차에 대하여.  2 3 4 5 6

  7. Microsoft Learn, WinRT APIs not supported in desktop apps. 데스크톱 앱에서 쓸 수 없는 WinRT API의 두 계통으로 UWP 전용 UI 기능에 의존하는 API와 package identity를 요구하는 API(ToastNotificationHistory, JumpList 등)가 있다는 점, 후자는 MSIX로 패키징된 앱에서만 지원된다는 점에 대하여.  2 3 4 5

  8. Microsoft Learn, RoInitialize function (roapi.h). RoInitialize가 현재 스레드를 지정한 동시성 모델(RO_INIT_SINGLETHREADED/RO_INIT_MULTITHREADED)로 Windows Runtime용으로 초기화한다는 점, WinRT 객체를 활성화하고 조작하는 모든 스레드가 사전 초기화를 필요로 한다는 점, MTA로 초기화된 스레드에서 모순되는 지정을 하면 RPC_E_CHANGED_MODE가 된다는 점에 대하여.  2

  9. Microsoft Learn, WinUI 3. WinUI가 신규 Windows 데스크톱 앱을 위해 권장되는 네이티브 UI 프레임워크라는 점, Windows App SDK의 일부로 제공된다는 점, Windows 10 버전 1809 이상에서 동작한다는 점, 공개된 장에서 개발되고 있다는 점에 대하여.  2 3 4 5

  10. Microsoft Learn, Call Windows Runtime APIs in desktop apps. .NET 6 이상에서 Windows OS 버전이 붙은 TFM(net10.0-windows10.0.22621.0 등)을 지정하면 Windows SDK targeting package가 참조되어 WinRT API를 호출할 수 있다는 점, C++는 Microsoft.Windows.CppWinRT NuGet 패키지와 C++17 이상으로 C++/WinRT를 쓴다는 점에 대하여.  2 3 4

  11. Microsoft Learn, Introduction to C++/WinRT. C++/WinRT가 완전히 표준적인 현대 C++17에 의한 언어 프로젝션이며 헤더 파일 기반 라이브러리로 구현된다는 점, C++/CX와 WRL의 권장 후속이라는 점, WinRT가 COM API에 기반하며 언어 프로젝션을 통해 접근하도록 설계되어 있다는 점, 프로젝션이 COM의 세부 사항을 감춘다는 점, cppwinrt.exe가 .winmd에서 프로젝션 헤더를 생성한다는 점에 대하여.  2 3

  12. Microsoft Learn, The Windows Runtime (WinRT) type system. 모든 WinRT 인터페이스가 암묵적으로 IInspectable을 요구하고 IInspectable이 IUnknown을 요구한다는 점, IUnknown이 QueryInterface, AddRef, Release를 정의한다는 점, IInspectable이 추가하는 GetIids, GetRuntimeClassName, GetTrustLevel의 세 메서드, GetRuntimeClassName이 메타데이터로 해석 가능한 형식 이름을 반환함으로써 언어 프로젝션을 가능하게 한다는 점, 사용자 정의 인터페이스끼리의 상속이 WinRT의 형식 시스템에 없고 requires로 표현한다는 점에 대하여.  2 3 4

  13. Microsoft Learn, IInspectable interface (inspectable.h). IInspectable이 모든 WinRT 클래스에 필요한 기능을 제공한다는 점, IUnknown을 상속한다는 점, GetIids, GetRuntimeClassName, GetTrustLevel의 세 메서드를 가진다는 점에 대하여. 

  14. Microsoft Learn, Introduction to Microsoft Interface Definition Language 3.0. MIDL 3.0이 WinRT 형식을 선언하기 위한 간결하고 현대적인 구문이라는 점, WinRT의 계약이 지금도 IDL로 기술되며 MIDL 컴파일러가 Windows 메타데이터(.winmd)를 생성한다는 점에 대하여. 

  15. Microsoft Learn, C#/WinRT. C#/WinRT의 NuGet 패키지에 포함된 cswinrt.exe가 .winmd를 처리해 C# 코드를 생성하고 상호 운용 어셈블리로 컴파일한다는 점, C++/WinRT가 C++용 헤더를 생성하는 것과 같은 위치라는 점에 대하여. 

  16. Microsoft Learn, Built-in support for WinRT is removed from .NET. .NET 5에서 Windows Runtime의 기본 제공 지원이 .NET에서 제거되고 CsWinRT 툴체인으로 이행했다는 점에 대하여.  2

  17. Microsoft Learn, IInitializeWithWindow interface (shobjidl_core.h). 데스크톱 앱에서 사용되는 WinRT 객체에 소유자 창을 제공하기 위한 인터페이스라는 점, IUnknown을 상속하고 Initialize(HWND) 메서드를 가진다는 점에 대하여. 

  18. Microsoft Learn, Use WinRT COM interop classes in .NET. 파일 피커나 대화 상자 등 일부 WinRT 객체가 데스크톱 앱에서 동작하기 전에 HWND를 필요로 한다는 점, WinRT.Interop.WindowNative와 WinRT.Interop.InitializeWithWindow라는 형식 안전한 C# 클래스로 손수 QueryInterface를 호출하지 않고 초기화할 수 있다는 점에 대하여.  2

  19. Microsoft Learn, Tutorial: Open files and folders with pickers in WinUI. 기존의 Windows.Storage.Pickers를 데스크톱(WinUI 3) 앱에서 쓸 경우 표시 전에 HWND로 초기화하지 않으면 예외 또는 조용한 실패가 된다는 점, WinUI 3 데스크톱 앱에 CoreWindow가 없다는 점, 새로운 Windows App SDK의 피커(Microsoft.Windows.Storage.Pickers)는 생성자에서 WindowId를 받아 InitializeWithWindow 패턴이 필요 없다는 점에 대하여.  2 3

  20. Microsoft Learn, Features that require package identity. 일부 Windows 기능과 WinRT API가 실행 시에 package identity를 요구한다는 점, MSIX 패키지로의 배포에 더해 외부 위치를 가리키는 패키지(packaged with external location)로도 식별자를 얻을 수 있다는 점에 대하여.  2

  21. Microsoft Learn, CoInitialize function (objbase.h). CoInitialize가 COM 라이브러리를 STA로 초기화한다는 점, 새로운 앱은 CoInitializeEx를 호출해야 한다는 점, Windows Runtime을 사용하는 경우에는 대신 RoInitialize 또는 Windows::Foundation::Initialize를 호출해야 한다는 점에 대하여. 

  22. GitHub, WinUI: Now Developing in the Open (microsoft/microsoft-ui-xaml Discussion #10700). 2025년 7월 말의 공식 발표. WinUI의 리포지토리를 공개해 나가기 위한 단계적 접근(미러 갱신 빈도의 향상, 로컬 빌드의 실현, 테스트를 갖춘 뒤 커뮤니티 기여의 수용, 최종적으로 GitHub를 개발의 주 거점으로 삼는 것)이 제시되어 있다는 점에 대하여.  2 3

  23. Microsoft Learn, Windows App SDK. Windows App SDK가 WinUI를 포함한 현행 Windows 앱 개발 라이브러리군이라는 점에 대하여. 

  24. Microsoft Learn, Walkthrough: WinUI 3 app with Win32 interop. WinUI의 Window 클래스가 데스크톱 창을 지원하도록 확장되었다는 점, WinUI 3의 데스크톱 앱에서는 Window가 Win32의 창 핸들(HWND)에 뒷받침되어 있어 창 핸들을 얻어 Win32 API로 조작할 수 있다는 점에 대하여. 

  25. Microsoft Learn, Host UWP XAML controls in desktop apps (UWP XAML Islands). UWP 세대의 XAML Islands가 WPF, WinForms, C++ 데스크톱 앱에 UWP XAML 컨트롤을 얹는 구조라는 점, WPF/WinForms에서의 이용이 .NET Core 3.x를 대상으로 하는 앱에 한정되며 현행 .NET이나 .NET Framework에서는 지원되지 않는다는 점에 대하여.  2

  26. Microsoft Learn, DesktopWindowXamlSource Class (Microsoft.UI.Xaml.Hosting). Windows App SDK의 XAML 호스팅 API의 핵심 클래스이며, HWND에 연결된 임의의 UI 요소에 WinUI 컨트롤을 호스팅할 수 있다는 점, WPF, Windows Forms, Win32(Windows API)로 만든 데스크톱 앱에서 이용할 수 있다는 점에 대하여.  2

  27. Microsoft Learn, Quickstart: Sending a toast notification from the desktop. 데스크톱 앱에서의 토스트 전송에는 System.AppUserModel.ID를 설정한 시작 메뉴의 바로 가기가 전제라는 점, CreateToastNotifier 호출에 그 AppUserModelID를 넘겨야 하며 없으면 토스트가 표시되지 않는다는 점에 대하여. 

  28. Microsoft Learn, Use app notifications with a .NET app. WPF/WinForms 앱에서 Windows App SDK의 AppNotificationManager를 쓰려면 NotificationInvoked 핸들러 등록 후에 Register()를 호출해야 한다는 점, unpackaged 앱에서는 Register()가 알림 클릭 시 앱을 시작하기 위한 COM 서버 등록을 자동으로 수행한다는 점, 전제로 Windows App SDK 도입과 WinRT API 호출 구성이 필요하다는 점에 대하여.  2

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

ActiveX 이관

COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

WinRT는 .NET 같은 매니지드 런타임인가요?
아닙니다. WinRT(Windows Runtime)는 가상 머신이나 가비지 컬렉터를 가진 실행 환경이 아니라, COM을 기반으로 한 ABI(바이너리 계약)입니다. Microsoft 자신의 문서가 「The Windows Runtime is based on COM」이라고 명시하고 있으며, 모든 WinRT 인터페이스는 IUnknown에서 유래한 IInspectable을 요구합니다. 호출의 실체는 지금도 vtable을 거치는 COM 호출이며, 참조 카운트(AddRef/Release)와 QueryInterface 위에 성립합니다. 「.winmd」라는 이름이나 .NET과의 높은 친화성 때문에 매니지드 환경으로 오해하기 쉽지만, .winmd는 ECMA-335와 같은 물리 형식을 빌린 메타데이터 파일이며, 시스템이 제공하는 .winmd에는 실행 코드가 들어 있지 않습니다. C#에서 자연스럽게 호출할 수 있는 것은 언어 프로젝션이 메타데이터로부터 C#용 프로젝션을 생성하기 때문입니다.
UWP는 이제 주류가 아니라고 들었습니다. 지금 와서 WinRT를 배울 의미가 있나요?
있습니다. UWP라는 앱 모델과 WinRT라는 API 기반은 별개입니다. UWP가 축소된 뒤에도 WinRT API의 대부분은 WPF, WinForms, Win32 데스크톱 앱에서 호출할 수 있는 형태로 계속 제공되고 있으며, 토스트 알림, 공유, 블루투스, OCR 등 「지금의 Windows가 가진 기능」의 상당수가 WinRT API로 공개되어 있습니다. 게다가 Microsoft가 현재 권장하는 네이티브 UI 프레임워크인 WinUI(Windows App SDK)는 WinRT의 ABI 위에 세워져 있습니다. 즉 WinRT의 구조(IInspectable, .winmd, 언어 프로젝션)는 UWP의 유산이 아니라, 현행 Windows 앱 개발의 발밑 그 자체입니다.
WPF나 WinForms 앱에서 WinRT API를 호출할 수 있나요?
호출할 수 있습니다. .NET 6 이상이라면 프로젝트 파일의 TargetFramework를 net8.0-windows10.0.19041.0 처럼 Windows OS 버전이 붙은 TFM으로 바꾸기만 하면 Windows SDK의 프로젝션 어셈블리가 참조되어, Windows.* 네임스페이스의 WinRT API를 C#에서 직접 호출할 수 있습니다. C++라면 Microsoft.Windows.CppWinRT NuGet 패키지를 도입하고 C++17 이상에서 C++/WinRT를 사용합니다. 다만 막히는 지점이 세 가지 있습니다. 첫째, 피커나 대화 상자처럼 CoreWindow를 전제로 하는 UI 계열 클래스는 표시하기 전에 IInitializeWithWindow로 소유자 창의 HWND를 넘겨야 합니다(다만 공유 UI의 DataTransferManager는 예외로, IInitializeWithWindow가 아니라 전용 IDataTransferManagerInterop을 사용해 ShowShareUIForWindow에 HWND를 넘기는 별도 경로입니다). 둘째, 알림 기록이나 점프 목록 같은 일부 API는 package identity(MSIX 패키징 또는 외부 위치를 가리키는 패키지)를 요구합니다. 셋째, WinRT 객체를 다루는 스레드에는 사전 초기화가 필요합니다. 네이티브 코드에서는 winrt::init_apartment나 RoInitialize로 STA/MTA의 동시성 모델을 지정합니다(C#의 WPF/WinForms 앱에서는 보통 런타임 쪽이 처리해 줍니다). 참고로 CoreWindow나 ApplicationView 자체에 의존하는 API는 애초에 데스크톱 앱에서는 쓸 수 없습니다.
데스크톱 앱에서 FolderPicker 같은 피커를 호출하면 예외가 납니다. 왜 그런가요?
피커나 대화 상자 중 일부 WinRT 클래스가 표시 대상으로 UWP의 CoreWindow를 전제로 설계되어 있기 때문입니다. 데스크톱 앱에는 CoreWindow가 없으므로, 표시하기 전에 소유자 창을 명시적으로 알려 주어야 합니다. 구체적으로는 먼저 소유자 창의 HWND를 얻고(WinUI의 Window라면 WinRT.Interop.WindowNative.GetWindowHandle, WPF라면 WindowInteropHelper, WinForms라면 폼의 Handle 속성), C#이라면 WinRT.Interop.InitializeWithWindow.Initialize로 피커에 넘깁니다. C++/WinRT라면 객체를 IInitializeWithWindow로 QueryInterface 해서(as<IInitializeWithWindow>()) Initialize(hwnd)를 호출합니다. 이 초기화를 하지 않으면 예외가 나거나 조용히 실패합니다. 참고로 새로운 Windows App SDK의 피커(Microsoft.Windows.Storage.Pickers)는 생성자에서 WindowId를 받는 설계로 바뀌어 이 초기화 패턴 자체가 필요 없습니다(다만 Windows App SDK의 API이므로 TFM 설정만으로는 쓸 수 없고, SDK 도입과, unpackaged 앱이라면 배포 대상에 런타임을 배치하고 초기화하는 것이 전제입니다).
WinUI 개발이 GitHub에서 공개되었다는데, 기존 WPF/WinForms 앱은 버려야 하나요?
서둘러 버릴 필요는 없습니다. WinUI 본류 개발의 공개는 「Microsoft가 네이티브 프레임워크에 본격적으로 투자한다」는 방향의 표명이지, 기존 프레임워크의 중단 선언이 아닙니다. WPF와 WinForms는 지금도 .NET의 일부로 계속 지원되고 있습니다. 판단을 두 가지로 나누어 주세요. 하나는 UI 프레임워크를 이행할 것인가 하는 판단으로, 이는 화면 자산의 규모, 서드파티 컨트롤, 개발 체제에 좌우되는 큰 이야기입니다. 다른 하나는 WinRT API를 기존 앱에서 부분적으로 사용할 것인가 하는 판단으로, 이쪽은 TFM 설정으로 오늘부터 시작할 수 있습니다. 다만 TFM은 API를 호출할 수 있게 해 줄 뿐이고, 예를 들어 토스트 알림에는 호출에 더해 알림 등록이 필요합니다(기존 경로는 unpackaged 앱의 경우 AppUserModelID가 붙은 바로 가기 등록 - 패키징되어 있다면 패키지의 identity가 AppUserModelID를 주므로 불필요합니다 - 이고, Windows App SDK 경로는 SDK 도입 - unpackaged 앱이라면 배포 대상 각 PC에 Windows App SDK 런타임 도입도 - 과 AppNotificationManager의 Register() 호출이며, MSIX로 패키징한 앱에서는 Register()에 의한 자동 등록이 동작하지 않고 나아가 Package.appxmanifest에 COM 활성자 선언이 필요합니다). 또한 Windows App SDK 경로에서는 관리자로 권한이 상승된 프로세스에서의 알림이 미지원이며, Show는 예외를 던지지 않고 조용히 실패합니다 - 권한 상승이 필요한 앱은 알림만 비상승 프로세스에 맡기는 분리를 검토해 주세요. 그래도 토스트 알림만 원한다면 WinUI로의 이행은 필요 없습니다. 참고로 기존 WPF/WinForms 화면에 WinUI 컨트롤을 부분적으로 섞는 것(XAML Islands)은 세대를 구별해야 합니다. UWP 세대의 Islands는 WPF/WinForms 지원이 .NET Core 3.x까지이고, WinUI 3 세대에는 Windows App SDK의 XAML 호스팅 API(DesktopWindowXamlSource)를 WPF/WinForms에서도 쓸 수 있지만 편리한 래퍼 컨트롤이 없어 구현 부담이 크므로, 현시점에서는 신중하게 평가하는 편이 안전합니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기