[Slog] MAUI 선택, 괜찮은 판단이었을까 🤔

:hammer_and_wrench: 모니터링 앱 프로젝트: 왜 MAUI인가?

안녕하세요! 이광석입니다. :blush:

이번 회사 프로젝트로 WindowsAndroid 환경에서 동작하는 설비 모니터링 앱을 개발하게 되었습니다.

회사 내부 프로젝트인 만큼 자세한 기술 사양은 공유하지 못하지만,
진행 중 느낀 문제점들과 고민들, 그리고 해결 방향에 대한 내용을 Slog로 남겨두고자 합니다.
(왠지… 문제가 생길 것 같은 싸~한 느낌이 들기도 하네요 :sweat_smile:)


처음에는 React Native가 자연스러워 보였다

처음 이 프로젝트를 시작할 때, 가장 먼저 떠오른 선택지는 React Native였습니다.

  • 회사 전체 프론트엔드는 점차 React 기반으로 통합되고 있고,
  • 메인 프로그램도 ASP.NET Core API(백엔드) + React(프론트엔드)로 리뉴얼 중이며,
  • 모바일 앱도 JavaScript 기반으로 통일하면 유지보수 측면에서 유리해 보였습니다.

하지만 실제로 적용을 고민해보면서, 몇 가지 현실적인 벽을 마주했습니다:

  • React Native 경험자가 팀 내에 전무했고,
  • 웹 React와는 전혀 다른 개발 환경과 네이티브 연동 방식에 대한 러닝 커브가 컸습니다.
  • 특히, 디바이스 연동 및 커스터마이징이 많은 UI를 빠르게 구현하려면,
    React Native보다는 .NET 기반에서의 개발 경험을 살리는 방식이 더 적절하다고 판단했습니다.

Avalonia, Uno도 후보였다 — 그러나 제외한 이유는?

React Native 외에도 AvaloniaUno Platform도 진지하게 검토했습니다.

1. Avalonia :cross_mark:

  • Avalonia는 XAML 계열의 AXAML을 사용하는 프레임워크입니다.
  • 하지만 저희 팀은 앞으로 대부분의 UI를 React 기반 웹으로 전환하려는 방향이라,
  • 새로운 XAML 문법까지 학습하는 것은 부담이 컸습니다.
  • 게다가, Android 지원은 여전히 실험적 수준이어서 안정성 면에서도 우려가 있었습니다.

2. Uno Platform :cross_mark:

  • Uno는 MAUI와 마지막까지 경쟁했던 강력한 후보였습니다.
  • 다만 Uno는 WinUI 3 기반이라, 저희 팀이 WPF에서 자주 사용했던
    Trigger, 특히 DataTrigger를 기본적으로 지원하지 않습니다.
  • VisualStateManager 방식에 익숙하지 않은 팀원들에게는 이 부분이 큰 제약으로 작용했습니다.

결국 MAUI로 결정한 이유

이 모든 후보들을 검토한 끝에, 최종적으로 MAUI를 선택하게 된 이유는 다음과 같습니다:

  • 기존 WPF 경험을 그대로 살릴 수 있는 XAML + MVVM 구조
  • .NET 기반 생태계에서 Android/Windows를 동시에 지원
  • 네이티브 API 접근, 커스터마이징, 배포 등에서 WPF 개발자들에게 진입장벽이 낮은 점
  • 무엇보다도, 현재 팀은 WPF 개발자 4명으로 구성되어 있어
    학습 비용 없이 바로 실전 투입이 가능하다는 점이 결정적이었습니다.

“메인 프로그램은 유지보수를 거치면서 개선하면 되지만,
모니터링 앱은 기업 고객에게 직접적인 만족감을 주는 앱이기 때문에,
디자인과 기술 모두 완성도 있게 구현되어야 한다는 판단을 내렸습니다.”


앞으로는?

모니터링 앱은 이제 막 기본 기능들을 갖춰가고 있는 중입니다.
화면 구성이나 디바이스 연동 같은 핵심 기능부터 하나씩 붙여가고 있고,
진짜 일하면서 마주칠 문제들은 이제부터가 시작일 것 같아요.

MAUI를 쓰면서 생기는 삽질(!)과 깨달음(?)도
계속 이곳 Slog에 남겨보려 합니다.

4개의 좋아요

MAUI에서 전체화면(FullScreen) 설정하기

모니터링 앱처럼 전체화면 모드가 필요한 경우, MAUI에서는 일반적인 WPF, WinUI3, Avalonia, Uno 등과 방식이 다릅니다.

MAUI에서는 직접적으로 Window를 제어할 수 있는 API가 제한적이고, 기본 창도 팝업처럼 동작하기 때문에 Native API를 활용해야 합니다.

아래는 Windows 플랫폼에서 MAUI 창을 전체화면으로 설정하는 예시입니다:

builder.ConfigureLifecycleEvents (events =>
{
#if WINDOWS
    events.AddWindows (windows =>
    {
        windows.OnWindowCreated ((Microsoft.UI.Xaml.Window window) =>
        {
            // 여기서 바로 WinUI Window니까 GetNativeWindow 같은 거 필요 없음
            var hwnd = WindowNative.GetWindowHandle (window);
            var windowId = Win32Interop.GetWindowIdFromWindow (hwnd);
            var appWindow = AppWindow.GetFromWindowId (windowId);

            // 전체화면
            appWindow.SetPresenter (AppWindowPresenterKind.FullScreen);
        });
    });
#endif
});

설명

  • OnWindowCreated는 MAUI 애플리케이션의 윈도우가 생성될 때 호출됩니다.
  • WindowNative.GetWindowHandle을 사용해 Native 핸들을 가져옵니다.
  • 이후 AppWindow 객체를 통해 SetPresenter를 호출하여 전체화면 모드로 전환합니다.

MAUI에서 전체화면 모드 처리: MauiProgram.cs를 깔끔하게 유지하는 방법

MAUI에서 창을 전체화면으로 설정하려면 Native API를 써야 하는데, 이걸 MauiProgram.cs에 직접 넣는다면… 아무리 생각해도 너무 지저분해질 겁니다.

그래서 저는 이런 코드는 별도의 확장 메서드로 분리해서 관리하고 있습니다.

// Extensions/WindowBuildExtensions.cs
public static class WindowBuildExtensions
{
    public static MauiAppBuilder UseFullWindows(this MauiAppBuilder builder)
    {
        return builder.ConfigureLifecycleEvents (events =>
        {
#if WINDOWS
            events.AddWindows (windows =>
            {
                windows.OnWindowCreated ((Microsoft.UI.Xaml.Window window) =>
                {
                    // 여기서 바로 WinUI Window니까 GetNativeWindow 같은 거 필요 없음
                    var hwnd = WindowNative.GetWindowHandle (window);
                    var windowId = Win32Interop.GetWindowIdFromWindow (hwnd);
                    var appWindow = AppWindow.GetFromWindowId (windowId);

                    // 전체화면
                    appWindow.SetPresenter (AppWindowPresenterKind.FullScreen);
                });
            });
#endif
        });
    }
}

그리고 MauiProgram.cs는 이렇게 깔끔하게 유지합니다:

// MauiProgram.cs
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
#if !DEBUG
            .UseFullWindows()
#endif
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });
#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}

:light_bulb: 포인트 정리

  • UseFullWindows() 확장 메서드를 만들어 MauiProgram.cs책임 분리.
  • 전체화면 설정은 Windows 플랫폼에서만 적용.
  • #if !DEBUG 조건을 활용해 디버그 시에는 전체화면 비활성화 가능.

이런 구조로 만들면, 유지보수할 때도 어디서 무슨 일이 일어나는지 한눈에 파악할 수 있어서 정말 편합니다.

2개의 좋아요

MAUI 모니터링 프로젝트에서의 RegionManager 및 모듈 구조 도입기

배경

기존 사내 WPF 기반 모니터링 시스템을 개발했을 당시 다음의 기술 스택으로 운영되고 있었습니다:

  • CommunityToolkit.Mvvm
  • Prism (하지만 Prism은 최근 듀얼 라이선스 정책 이후 더 이상 사용하지 않기로 결정됨)

Prism의 핵심 기능 중 하나인 RegionManager는 사내 팀이 매우 익숙하고 능숙하게 활용하던 기능이었습니다. 때문에 Prism을 완전히 제거하더라도, RegionManager만큼은 꼭 유지하고 싶다는 니즈가 생겨났습니다.

개인 프로젝트 경험이 사내 구조 정비로 이어지다

개인적으로는 Prism 없이도 다양한 XAML 프레임워크(WPF, MAUI, Avalonia, WinUI 3, OpenSilver) 에서 활용할 수 있는 범용 라이브러리를 실험한 적이 있습니다.
그 프로젝트는 단순히 RegionManager, Module에 국한되지 않고, 다양한 UI 패턴과 구조를 다뤄보는 광범위한 실험적인 작업이었습니다.

이번 사내 프로젝트를 준비하면서,
그 경험을 바탕으로 실제로 업무에 꼭 필요한 4가지 핵심 요소만 추려 적용해보기로 했습니다:

  • RegionManager
  • Module
  • IoC, DI

즉, 개인 프로젝트는 실험의 장이었고,
이번에는 그 중 꼭 필요한 기능만을 선별하여, 경량화된 내부용 라이브러리 형태로 도입한 것입니다.


Module이란?

DIIoC는 MAUI에서 이미 기본적으로 제공되기 때문에 별도 설명은 생략하고,
이번 프로젝트에서 특히 강조하고자 했던 개념은 바로 Module입니다.

Module은 서로 다른 프로젝트 간의 화면, 로직, 서비스를 느슨하게 연결해주는 확장 구조입니다.

이 구조를 통해 서로 독립적인 프로젝트(B)하나의 메인 프로젝트(A) 에서 자연스럽게 조립하듯 연결 할 수 있습니다.

어떻게 동작하나요?

IModule이라는 인터페이스만 구현하면,
해당 모듈(B)은 자신만의 View, ViewModel, Service 등을 독립적으로 정의하면서도
프로젝트(A)에서 아무런 이질감 없이 사용할 수 있게 됩니다.

즉, 모듈 단위로 분리된 프로젝트들이 하나의 앱 안에서 유기적으로 동작하는 구조를 만들 수 있습니다.

예를 들어보면

  • A프로젝트: 모니터링 메인 앱
  • B프로젝트: 특정 장비를 제어하는 UI 및 로직

BIModule을 통해 자신이 등록해야 할 View와 서비스만 정의하고,
A에서는 모듈을 자동으로 로드하거나, 명시적으로 불러와서 View를 주입할 수 있습니다.

이 구조는 Prism의 Module 시스템과 매우 유사하지만,
가볍고, MAUI 환경에 맞게 직접 커스터마이징된 형태로 구현되었습니다.


RegionManager + Module의 결합

이처럼 Module을 통해 View가 제공되고,
RegionManager를 통해 이 View가 지정된 UI 영역에 주입됩니다.

즉,

  • Module은 View와 로직을 제공하는 단위,
  • RegionManager는 그것을 UI에 꽂아 넣는 연결자 역할을 하게 됩니다.

덕분에 화면이 많지 않은 프로젝트임에도 불구하고,
각 장비별 화면을 모듈로 분리해 관리하면서
전체 UI 구조는 일관되고 확장 가능한 방식으로 구성할 수 있었습니다.


마무리: 꼭 복잡한 구조여서가 아니라

MAUI는 본래 심플함을 지향하는 플랫폼이지만,
단순한 구조 속에서도 유지보수성과 유연성을 확보하는 방법은 분명히 존재합니다.

  • WPF 기반에서 일하던 팀이 MAUI로 전환하면서 겪는 이질감을 줄이고,
  • 미래에 추가될 장비나 기능에 대비한 구조적 준비를 하며,
  • 직접 만든 RegionManager와 Module 구조를 통해 기술적 일관성과 팀 경험을 이어갈 수 있는 연결고리를 만든 것.

결국 "정말 필요할까?"라는 질문에서 시작됐지만,
돌아보면 "잘 만들어두길 잘했다"는 생각이 드는 구조였습니다.

3개의 좋아요

Skia를 도입기

공장 모니터링 시스템의 특성상 수많은 배관 라인과 제어 요소(컨트롤)들이 화면에 표시되어야 합니다.
이런 요소들을 모두 UI 컨트롤로 구현하게 되면, 화면 로딩이 느려지고 사용자에게 답답한 UI라는 인식을 줄 수 있다는 우려가 생겼습니다.

마침 MAUI로 도면을 구현해보셨던 조장원 MVP님께 조언을 구했는데,
“SkiaSharp을 활용해보는 건 어떻겠냐”는 의견을 듣고, 실제로 SkiaSharp 도입을 결정하게 되었습니다.


IModule 확장 - SkiaSharp 초기 설정

MAUI에서 Skia를 사용하려면 builder.UseSkiaSharp();를 초기 설정에 추가해야 합니다.
하지만 모듈 단위로 쪼개진 현재 프로젝트 구조에서는 해당 설정이 지원되지 않았습니다.

builder.UseSkiaSharp();

그래서 다음과 같이 사내 프로젝트를 업데이트하였습니다

  1. Interface 확장
    image

  2. 적용


XAML에선 쓸 수 없다니…

Skia는 XAML에서 직접 사용할 수 없다는 단점이 있습니다.
결국 뷰 비하인드 코드에서 UI를 구성해야 했고, 핫리로드를 쓸 수 없다는 사실에 처음엔 걱정이 앞섰습니다.

하지만 예상과는 달리, Skia는 도형을 추가해도 일관된 빠른 로딩 속도를 보여줬습니다.
1~2f 정도 수치를 조절하려고 앱을 재시작하는 건 다소 귀찮긴 했지만,
속도와 안정성이라는 이점 앞에서는 충분히 감수할 만했습니다.

그래서인지 회사 야근 없이 이틀 만에 한 페이지를 완성할 수 있었습니다.


비하인드 코드 UI 작성 시 팁

사내 프로젝트든 개인 프로젝트든, 먼저 Figma나 그래픽 툴로 레이아웃을 구상해보는 걸 추천합니다.
XAML로 이관하는데 있어서 헛수고 아니야? 라는 생각이 들어도
Margin, Padding, 해상도 등을 미리 가늠할 수 있어 훨씬 수월합니다.

특히 Path 관련 팁도 있는데, 이건 나중에 발표 때 슬쩍 공유드릴게요 :wink:


사용자 만족도를 높이기 위한 고민

초기 기획이나 요구사항엔 없던 내용이었지만,
“태블릿에도 배포되는 앱인데, 단순한 모니터링보다는 뭔가 있어야 하지 않을까?”라는 생각이 들었습니다.

그래서 두 가지 기능을 추가해보았습니다.

  • 모니터링 화면 줌 인/아웃
  • 줌 상태에서 화면 이동(Pan) 기능

여기에 한 가지 더,

“근데 확대했을 때, 사용자가 지금 어디를 보고 있는지 모르면 불편하지 않을까?”
하는 생각이 들어 미니맵 기능까지 넣어봤습니다.

이 기능은 혹시나 하는 마음에 ChatGPT에 한번 물어봤는데,
그대로 적용했더니 바로 동작하더라고요.
“아, 이런 게 바로 AI를 유연하게 활용하는 거구나” 싶었던 순간이었습니다.


마무리하며

SkiaSharp은 단순히 대체 도구가 아닌, 성능과 확장성 면에서 훌륭한 선택이었습니다.
비록 XAML을 벗어나 비하인드 코드에서 작업해야 했지만,
그에 따른 속도·안정성·유연성은 충분히 감동적이었습니다.

3개의 좋아요

모니터링의 생명은 가시성

급한 불을 끈 후, 일주일 전 사건을 기록으로 남겨봅니다.
(사실 다른 사건도 있었지만, 이번 글에서는 하나의 사례만 정리합니다.)


사건의 배경

이번 사건은 누군가의 실수나 검토 지연 때문이 아니었습니다.
일정이 촉박했고, 상위 단계에서 지연이 누적된 탓에 실제 장비 데이터를 확인할 수 있는 시점이 늦어졌습니다.
또한 PLC 튜닝 작업이 생산 일정과 겹쳐 초기 데이터 확인 자체가 어려웠습니다.

즉, 피할 수 없는 환경적 제약 속에서 시작된 상황이었습니다.


사건의 발생

  • 시점: 중간점검 H-1

  • 상황
    실제 데이터가 올라온 화면을 확인했을 때, 밸브 흐름이 직관적으로 보이지 않는다는 문제가 발견됨
    저는 SkiaSharp으로 큰 배선도를 그렸는데, 기본 구조는 밸브가 열리면 아래쪽 배선으로 흐름이 표시되는 방식이었습니다.

    그런데 실제 데이터를 띄워보니 이렇게만 표현할 경우,
    위쪽 배선의 흐름이 전혀 드러나지 않아 전체 흐름을 한눈에 파악하기 어렵다는 점이 확인되었습니다.

    즉, 물리적으로는 단방향 흐름이 맞지만,
    화면에서는 밸브가 열릴 때 위·아래 배선이 동시에 흐르는 것처럼 보이게 표현해야 가시성이 확보될 수 있었습니다.

    문제는 이걸 단순히 선을 하나 더 긋는 수준으로 해결할 수 없다는 것이었습니다.
    초기에 밸브와 라인을 상하 구조로 하나의 레이어처럼 묶어둔 상태라,
    이를 수정하려면 레이어를 쪼개고, 위/아래 각각 독립적으로 흐름을 표현할 수 있도록 구조를 다시 그려야 했습니다.


대응

막상 마감 직전이었지만, “어차피 뒤집어야 한다면 제대로 다시 만들자”라는 결론을 내렸습니다.
추가로 하루(H-9)의 시간을 확보했고, 예상보다 집중도가 올라가면서 약 4시간 만에 전체 구조를 다시 그릴 수 있었습니다.


사건에서 배운 점

  • 가시성의 중요성: 데이터 자체보다, 그것이 어떻게 보이는지가 모니터링의 핵심임을 다시 확인.
  • 설계 유연성 필요: 단순화를 위해 묶어둔 구조가, 실제 상황에서는 오히려 발목을 잡을 수 있음.
  • 빠른 전환의 가치: 문제를 늦게 발견하더라도, 빠른 의사결정과 집중적인 수정으로 충분히 만회 가능.

정리

이번 사건은 누구의 잘못이 아니라 일정과 구조적 제약이 겹쳐 생긴 불가피한 일이었습니다.
하지만 그 안에서도, 모니터링의 생명은 결국 가시성이라는 점을 다시 한 번 배울 수 있었습니다.

다음 포스트 예고 주제…

어!?..뭐야?

2개의 좋아요

어!?..뭐야?

앞서 예고했던 두 번째 사건을 이어서 작성합니다.


사건의 발생

  • 시점 : 중간점검 H-3 (원래 중간점검하려던 날 발생..)

  • 상황
    Android 배포 파일을 오랜만에 만들다 보니, 기억을 더듬어 겨우 완성했습니다.
    이제 현황판에 배포할 Windows Mini PC의 원격 주소로 배포하려는데, “왜 이렇게 느리지?” 하는 생각과 함께 묘한 불안감이 스쳤습니다.

    첫 로딩 화면은 메뉴 화면이 정상적으로 떴습니다.
    “오, 다행이다”라며 안도했지만… 메인 화면(스키아로 그려진 화면)을 누르는 순간, 프로그램이 아무런 경고도 없이 강제 종료.

    아… 안 돼… 3시간밖에 안 남았는데…

    이벤트 로그를 확인해도 단순히 App Crash 기록만 남아있었습니다.

    혹시나 싶어 미니PC 사양을 확인했습니다:

  • CPU: Intel(R) N100

  • GPU: Intel(R) UHD Graphics

  • RAM: 8GB

    “그래서 좀 느린 건가?” 싶으면서도, “이 정도 사양이면 못 돌릴 정도는 아닐 텐데?”라는 생각이 들었습니다.
    다른 2대의 PC도 확인했더니 CPU는 동일, 한 대만 RAM이 4GB.

    “저 PC에서만 그런걸꺼야…” → 나머지 4GB, 8GB PC 결과는 동일하게 App Crash.


대응

당시 상황에서는 확정적인 원인을 규명할 시간은 없었습니다.
다만 저사양 PC 환경과 Skia 기반 렌더링이 맞물려 문제가 발생했을 가능성 정도만 추측할 수 있었습니다.

문제는 남은 시간이 너무 없었다는 것.

당장 더 높은 사양의 Mini PC로 교체한다고 해도 성공을 보장할 수 없었고, 검증할 시간도 없었습니다.

다행히 중간 점검의 목적은 “개발이 완료되었음을 보여주는 것”이었기 때문에, 반드시 Mini PC에서 실행할 필요는 없었습니다.
서버도 Windows 환경을 요구했기 때문에, 서버에 프로그램으로 실행했고 정상적으로 동작했습니다.

이렇게 무사히(?) 중간 점검은 무사히 넘어갈 수 있었습니다.


사건에서 배운점

  1. 환경 검증의 중요성: 개발 환경에서는 문제없던 코드도, 실제 배포 환경의 사양에서 충분히 재현 테스트가 필요하다.
  2. 사양 변수 가능성: 저사양 PC에서 SkiaSharp 기반 렌더링이 원인일 수도 있다는 점을 의심했지만, 확정할 수는 없었다. 다만 “실행 환경의 제약이 언제든 문제가 될 수 있다 ”는 사실을 체감했다.
  3. 위험 분산: 특정 하드웨어에 종속된 시연은 언제든 리스크가 될 수 있다. 다른 대안 실행 경로를 준비하는 것이 안전하다.

정리

이 사건은 코드 결함이라기보다는 환경 차이에서 발생한 예측 불가 충돌이었습니다.
시간 제약 속에서 근본 원인을 해결하지는 못했지만, 다행히 대체 실행 환경을 활용해 위기를 넘길 수 있었습니다.

그리고 다시 한 번, “개발의 완성은 코드뿐만 아니라 실행 환경까지 고려해야 한다”는 교훈을 남겼습니다.

다음 포스트 예고 주제

어!.. 뭐야!

1개의 좋아요

어!.. 뭐야! (두 번째 사건 후속)

앞서 예고했던 두 번째 사건의 대처 과정을 이어서 작성합니다.


초기 시도

어쩔 수 없이 WPF에서, XAML에 이미 모양이 들어간 경우는 Path Data를 활용하고, 그 외 컨트롤을 섞어 화면을 그리기 시작했습니다.
(사실 Skia 데이터였기 때문에 대부분 Path로 처리했습니다.)

하루를 그렇게 보내고, 다음날 시간이 조금 여유 있다는 이야기를 들으면서,
“어제 다 했던 건데… 밑져야 본전이지” 하는 생각에, WPF에서 Skia를 돌리기 위한 최소 세팅만을 적용하고 한 페이지 정도 Skia 코드를 그대로 넣고 실행해보니, 완벽하게 동일한 화면이 뜨는 것을 확인할 수 있었습니다.


배포 테스트

이제 배포 PC에서 실행해보았습니다.

  • 첫 페이지: 정상 동작 :white_check_mark:

  • 두 번째 페이지: 역시 정상 동작 :white_check_mark:

“MAUI Windows에서는 안 되는데, WPF에서는 된다…”
→ 이유는 나중에 조사하기로 하고, 당장은 두 플랫폼을 병행 운영해야 하는 상황이었습니다.


구조 개선 및 공통 라이브러리화

결국 선택한 방법은 MAUI + WPF 조합 운영.

  • Skia 코드Store를 각각 Core 라이브러리로 분리

  • 페이지별로 필요한 라이브러리를 가져다 쓰도록 구조화

Store = ViewModel입니다. Skia 페이지의 경우 일반적인 MVVM과 달리, 상태 값이 변경되면 화면을 즉시 갱신해야 하기 때문에 React나 Flutter와 유사한 방식이 필요했습니다. 따라서 전통적인 ViewModel이라는 표현보다는 상태 관리 중심의 Store라는 표현을 사용했습니다.

Flutter MobX 사용법 참고
Flutter의 MobX 사용법


결과

첫날 작업 시간을 제외하고, 나머지 2일 만에 모든 작업을 완료했습니다.
이로써, 두 플랫폼에서 공통 코드와 구조를 재사용할 수 있는 기반을 마련할 수 있었습니다.

1개의 좋아요

사건의 해결

이 전 슬로그를 공유를 통해 문제를 해결할 수가 있었습니다.


문제

마우이의 Window는 정말 미니 최저사양의 미니 PC에서 사용할 수 없는것인가?

25년 8월 26일 화요일 까지만해도 사용 할 수 없는 것이라고 생각이 들었습니다. 이 생각은 바로 다음 날인 27일 생각이 바뀌게 되었습니다.

뭐가 문제였을까? SkiaSharp 라이브러리의 문제였습니다.

MAUI의 SkiaSharp과 WPF SkiaSharp의 라이브러리는 같지만 구동방식이 다릅니다. SkiaSharp은 내부적으로 여러 가지 Surface/Backend를 지원합니다.
대표적으로는:
→ GPU 백엔드 (OpenGL, Metal, Vulkan, DirectX 등) → MAUI
→ CPU 백엔드 (Raster, 즉 일반 메모리 기반 그리기) → WPF

WPF의 SkiaSharp
WPF에서 제공되는 SkiaSharp.Views.WPF.SKElement나 SKControl은 GPU 백엔드를 쓰는 게 아니라, CPU 기반 raster backend를 기본으로 사용합니다. 즉, 그리기를 전부 시스템 메모리에서 CPU 연산으로 처리하고, 마지막에 WPF의 DrawingContext나 WriteableBitmap으로 복사해서 화면에 뿌립니다. 이 과정은 GPU와 무관하기 때문에 GPU가 없어도 안전하게 실행됩니다.

MAUI의 SkiaSharp
MAUI의 SkiaSharp.Views.Maui.Controls.SKCanvasView 같은 뷰는 GPU 기반의 surface(GRContext → OpenGL/Metal 등)를 우선적으로 사용합니다.
그래서 GPU가 없거나 드라이버가 이상하면 GPU 초기화 실패 → 메모리 접근 잘못됨 → AccessViolationException

그렇다면 해결책은 어떻게 해야할까?

닷넷코리아와 자마린 방에서 활동해주시는 호원님께서
비슷한 경우의 이슈를 올린 적이 있었고, 최근 들어서야 PR이 되었다는 소식을 전달해주셨습니다.

이슈 정보

머지 정보

결과론적으로 선택은 두가지!
새 버전의 SkiaSharp Library를 기다리느냐, VSC++ 최신 재배포 패키지를 설치하느냐


해결

새 SkiaSharp Library를 기다릴 수 없기에 VSC++ 최신 재배포 패키지 설치하였더니 정상적인 화면이 동작되었습니다.
이로써 저는 이 프로젝트에서 하나의 프레임워크만 관리할 수 있게 되었습니다.

1개의 좋아요