Files
ClassicAssist.Avalonia/.qoder/repowiki/en/content/Technical Architecture/UI Framework/Cross-Platform Abstraction Layer.md
T
Beyhan Oğur d5f52cc3d3 first commit
2026-09-03 04:45:36 +03:00

19 KiB
Raw Blame History

Cross-Platform Abstraction Layer

**Referenced Files in This Document** - [IDispatcher.cs](file://ClassicAssist.Shared/IDispatcher.cs) - [IUIInvoker.cs](file://ClassicAssist.Shared/IUIInvoker.cs) - [WPFDispatcher.cs](file://ClassicAssist/WPFDispatcher.cs) - [WPFUIInvoker.cs](file://ClassicAssist/WPFUIInvoker.cs) - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) - [AvaloniaUIInvoker.cs](file://ClassicAssist.Avalonia/AvaloniaUIInvoker.cs) - [Engine.cs (Shared)](file://ClassicAssist.Shared/Engine.cs) - [Engine.cs (WPF)](file://ClassicAssist/Engine.cs) - [Engine.cs (Avalonia)](file://ClassicAssist.Avalonia/Engine.cs) - [Program.cs (Avalonia)](file://ClassicAssist.Avalonia/Program.cs) - [App.axaml.cs (Avalonia)](file://ClassicAssist.Avalonia/App.axaml.cs) - [MessageBoxProvider.cs](file://ClassicAssist.Shared/Misc/MessageBoxProvider.cs) - [AvaloniaMessageBoxProvider.cs](file://ClassicAssist.Avalonia/Misc/AvaloniaMessageBoxProvider.cs) - [WPFMessageBoxProvider.cs](file://ClassicAssist/UI/Misc/WPFMessageBoxProvider.cs) - [NativeMethods.cs](file://ClassicAssist.Shared/Misc/NativeMethods.cs) - [PlatformDetector.cs](file://ClassicAssist.Shared/Engine/PlatformDetector.cs)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion
  10. Appendices

Introduction

This document explains the cross-platform abstraction layer that enables ClassicAssist to run on Windows, Linux, and macOS. It focuses on:

  • The dispatcher interface for thread marshaling
  • UI invoker patterns for cross-thread UI updates
  • Platform-specific optimizations
  • Handling differences in window management, file system access, and native interop calls across platforms
  • Migration strategies and platform-specific considerations

The goal is to provide a clear understanding of how shared logic remains platform-agnostic while delegating platform-specific behavior through well-defined interfaces.

Project Structure

At a high level, the project separates shared logic from platform-specific implementations:

  • Shared abstractions and core engine live in ClassicAssist.Shared
  • WPF-based implementation lives in ClassicAssist
  • Avalonia-based implementation lives in ClassicAssist.Avalonia
  • Launcher and updater are separate applications with their own UI frameworks
graph TB
subgraph "Shared"
S_ID["IDispatcher.cs"]
S_UI["IUIInvoker.cs"]
S_Eng["Engine.cs (Shared)"]
S_MsgProv["MessageBoxProvider.cs"]
S_Native["NativeMethods.cs"]
S_Platform["PlatformDetector.cs"]
end
subgraph "WPF"
W_Dispatch["WPFDispatcher.cs"]
W_UIInv["WPFUIInvoker.cs"]
W_Eng["Engine.cs (WPF)"]
W_MsgProv["WPFMessageBoxProvider.cs"]
end
subgraph "Avalonia"
A_Dispatch["AvaloniaDispatcher.cs"]
A_UIInv["AvaloniaUIInvoker.cs"]
A_Eng["Engine.cs (Avalonia)"]
A_Program["Program.cs (Avalonia)"]
A_App["App.axaml.cs (Avalonia)"]
A_MsgProv["AvaloniaMessageBoxProvider.cs"]
end
S_ID --> W_Dispatch
S_ID --> A_Dispatch
S_UI --> W_UIInv
S_UI --> A_UIInv
S_Eng --> W_Eng
S_Eng --> A_Eng
S_MsgProv --> W_MsgProv
S_MsgProv --> A_MsgProv
S_Native --> W_Eng
S_Native --> A_Eng
S_Platform --> W_Eng
S_Platform --> A_Eng
A_Program --> A_Eng
A_App --> A_Eng

Diagram sources

Section sources

Core Components

The cross-platform abstraction centers around two primary interfaces:

  • Dispatcher interface for thread marshaling
  • UI Invoker interface for cross-thread UI updates

These interfaces are implemented per platform (WPF and Avalonia), allowing shared code to remain framework-agnostic.

Key responsibilities:

  • Marshal work onto the UI thread safely
  • Provide a consistent API for invoking UI operations from background threads
  • Abstract away platform-specific threading models and message pumps

Section sources

Architecture Overview

The architecture uses dependency injection or static initialization to select the correct platform implementation at runtime. Shared components depend only on interfaces, not concrete classes.

classDiagram
class IDispatcher {
+ "Dispatch(action)"
+ "BeginInvoke(action)"
+ "Invoke(action)"
}
class IUIInvoker {
+ "InvokeOnUiThread(action)"
+ "IsOnUIThread()"
}
class WPFDispatcher
class WPFUIInvoker
class AvaloniaDispatcher
class AvaloniaUIInvoker
IDispatcher <|.. WPFDispatcher : "implements"
IDispatcher <|.. AvaloniaDispatcher : "implements"
IUIInvoker <|.. WPFUIInvoker : "implements"
IUIInvoker <|.. AvaloniaUIInvoker : "implements"

Diagram sources

Detailed Component Analysis

Dispatcher Interface and Implementations

Purpose:

  • Provide a unified way to schedule work on the UI thread regardless of the underlying UI framework
  • Support both synchronous and asynchronous dispatching patterns

Implementation highlights:

  • WPFDispatcher bridges to WPF’s dispatcher model
  • AvaloniaDispatcher bridges to Avalonia’s dispatcher model
  • Both expose methods to enqueue actions and ensure they execute on the UI thread
sequenceDiagram
participant BG as "Background Thread"
participant Shared as "Shared Code"
participant Disp as "IDispatcher"
participant Impl as "WPFDispatcher / AvaloniaDispatcher"
participant UIThread as "UI Thread"
BG->>Shared : "Perform long-running task"
Shared->>Disp : "BeginInvoke(action)"
Disp->>Impl : "Forward call"
Impl->>UIThread : "Marshal action"
UIThread-->>Impl : "Execute action"
Impl-->>Shared : "Return"

Diagram sources

Section sources

UI Invoker Interface and Implementations

Purpose:

  • Encapsulate UI framework-specific invocation semantics
  • Offer IsOnUIThread checks and safe InvokeOnUiThread helpers

Implementation highlights:

  • WPFUIInvoker uses WPF’s thread affinity APIs
  • AvaloniaUIInvoker uses Avalonia’s thread context APIs
  • Shared code calls IUIInvoker without knowing the underlying framework
flowchart TD
Start(["Start"]) --> Check["Check if currently on UI thread"]
Check --> |Yes| ExecuteDirect["Execute UI update directly"]
Check --> |No| Enqueue["Enqueue action via dispatcher"]
Enqueue --> Wait["Wait for completion if needed"]
ExecuteDirect --> End(["End"])
Wait --> End

Diagram sources

Section sources

Engine Initialization and Platform Selection

Purpose:

  • Initialize the correct platform-specific dispatcher and UI invoker
  • Configure application lifecycle and entry points per platform

Implementation highlights:

  • Shared Engine defines common startup logic and configuration
  • WPF Engine initializes WPF-specific resources and sets up WPFDispatcher/WPFUIInvoker
  • Avalonia Engine initializes Avalonia-specific resources and sets up AvaloniaDispatcher/AvaloniaUIInvoker
  • Avalonia Program and App coordinate bootstrapping and DI setup
sequenceDiagram
participant OS as "Operating System"
participant Entry as "Program.cs (Avalonia)"
participant App as "App.axaml.cs (Avalonia)"
participant Eng as "Engine.cs (Avalonia)"
participant Disp as "AvaloniaDispatcher"
participant UIInv as "AvaloniaUIInvoker"
OS->>Entry : "Launch process"
Entry->>App : "Initialize Avalonia"
App->>Eng : "Create and configure Engine"
Eng->>Disp : "Register dispatcher"
Eng->>UIInv : "Register UI invoker"
Eng-->>App : "Ready"

Diagram sources

Section sources

Window Management Abstraction

Purpose:

  • Provide a consistent API for creating, showing, and managing windows across platforms
  • Hide differences between WPF Window and Avalonia Window lifecycles

Approach:

  • Use platform-specific providers registered by each Engine implementation
  • Expose simple Create/Show/Close operations from shared code

Considerations:

  • Ensure UI thread affinity when manipulating window state
  • Handle platform differences in modal dialogs and owner relationships

[No sources needed since this section doesn't analyze specific files]

File System Access Abstraction

Purpose:

  • Normalize path separators, case sensitivity, and special directories across platforms
  • Provide helper utilities for reading/writing configuration and data files

Approach:

  • Use shared utility functions that adapt to platform specifics
  • Centralize path resolution and normalization

Considerations:

  • Respect user profile locations and sandboxing constraints
  • Handle permission differences on Linux/macOS vs Windows

[No sources needed since this section doesn't analyze specific files]

Native Interop Calls

Purpose:

  • Provide a single point for platform-specific native calls
  • Abstract away differences in P/Invoke signatures and availability

Approach:

  • Define conditional compilation or runtime detection for available APIs
  • Wrap native calls behind shared interfaces where possible

Considerations:

  • Guard against missing symbols on non-Windows platforms
  • Prefer managed alternatives when available

Section sources

Message Box Provider Abstraction

Purpose:

  • Standardize user-facing dialog interactions across platforms
  • Allow shared code to show messages without depending on UI framework details

Implementation highlights:

  • MessageBoxProvider defines the shared contract
  • WPFMessageBoxProvider implements it using WPF dialogs
  • AvaloniaMessageBoxProvider implements it using Avalonia dialogs
classDiagram
class MessageBoxProvider {
<<interface>>
+ "Show(message)"
}
class WPFMessageBoxProvider
class AvaloniaMessageBoxProvider
MessageBoxProvider <|.. WPFMessageBoxProvider : "implements"
MessageBoxProvider <|.. AvaloniaMessageBoxProvider : "implements"

Diagram sources

Section sources

Dependency Analysis

The following diagram shows how shared interfaces are implemented by platform-specific classes and how engines wire them together.

graph LR
Shared["Shared Interfaces<br/>IDispatcher, IUIInvoker"] --> WPFImpl["WPF Implementations<br/>WPFDispatcher, WPFUIInvoker"]
Shared --> AvaloniaImpl["Avalonia Implementations<br/>AvaloniaDispatcher, AvaloniaUIInvoker"]
SharedEng["Shared Engine"] --> WPFEng["WPF Engine"]
SharedEng --> AvaloniaEng["Avalonia Engine"]
WPFEng --> WPFImpl
AvaloniaEng --> AvaloniaImpl

Diagram sources

Section sources

Performance Considerations

  • Minimize cross-thread marshals: batch UI updates when possible to reduce dispatcher overhead
  • Avoid blocking the UI thread: use BeginInvoke-style patterns for long-running tasks
  • Prefer lightweight invocations: keep actions small and focused to reduce contention
  • Cache frequently accessed platform info: avoid repeated platform detection calls
  • Be mindful of memory pressure: avoid capturing large objects in dispatched closures

[No sources needed since this section provides general guidance]

Troubleshooting Guide

Common issues and resolutions:

  • Cross-thread operation exceptions: ensure all UI updates go through IUIInvoker.InvokeOnUiThread
  • Deadlocks on UI thread: avoid calling synchronous Invoke from the UI thread; prefer asynchronous patterns
  • Missing platform features: guard native calls with platform detection and fallbacks
  • Dialog visibility problems: verify that dialogs are shown on the correct UI thread and have proper owners

Section sources

Conclusion

The cross-platform abstraction layer cleanly separates shared logic from platform-specific concerns using well-defined interfaces for dispatching and UI invocation. By centralizing platform selection in the Engine implementations and providing consistent APIs for window management, file system access, and native interop, ClassicAssist achieves robust support across Windows, Linux, and macOS. Following the migration strategies and performance guidelines outlined here will help maintain stability and portability as the codebase evolves.

[No sources needed since this section summarizes without analyzing specific files]

Appendices

Migration Strategies

  • Introduce new platform-specific feature:
    • Add an interface method to the relevant shared interface if necessary
    • Implement the method in WPF and Avalonia providers
    • Update Engines to register the new provider
  • Replace direct UI framework calls:
    • Refactor to use IUIInvoker and IDispatcher
    • Remove direct references to WPF/Avalonia types from shared code
  • Handle native interop:
    • Wrap calls in shared utilities with platform guards
    • Provide no-op or alternative implementations for unsupported platforms

[No sources needed since this section doesn't analyze specific files]