← මුල් පිටුව

Native interop

ධාරණය කළ native අන්තර්ගතය, වීඩියෝ, සහ Button එකක් පිටුපස HWND එකක් නොමැත්තේ ඇයි.

මෙම පිටුව පරිවර්තනයකි. ඉංග්‍රීසි අනුවාදය සමඟ වෙනසක් ඇත්නම්, ඉංග්‍රීසි අනුවාදය නිවැරදි ලෙස සලකන්න. English

ප්‍රශ්න දෙකක් එකම ප්‍රශ්නය බව පෙනී යයි:

දෙවැන්නට කෙටි පිළිතුර ඔබට නොහැක, සහ ඔබ එකක් ව්‍යාජව සෑදිය යුතු නැත. ඔබට එය අවශ්‍ය වන්නේ කලාතුරකිනි, මන්ද පළමුවැන්නට සැබෑ පිළිතුරක් ඇත: NativeControlHost.

සැබෑ WinForms යෙදුමක් තුළ Majorsilence.Forms කවුළුවක් ධාරණය කිරීම ප්‍රතිවිරුද්ධ දිශාවයි, එය winforms-interop.md තුළ ආවරණය කෙරේ.

මෙහි handles සැබෑ නොවන්නේ ඇයි

Majorsilence.Forms තමන්ගේම සියලු ඇඳීම් තනි Skia surface එකකට කරයි. සෑම backend එකක්ම එය යටින් ඇති toolkit එකේ ආකෘතියම අනුගමනය කරයි: එක් ඉහළ-මට්ටමේ (top-level) කවුළුවකට එක් ධාරක කවුළුවක් — desktop backend මත සැබෑ OS කවුළුවක් — සහ එය තුළ ඇති සියල්ල ඇඳ ඇත, native child කවුළුවලින් සංයුක්ත කර නැත. Button එකක් යනු canvas එකක් මත ඇඳීමේ මෙහෙයුම් ය. එය පිටුපස HWND එකක් නැත්තේ එය පිටුපස OS වස්තුවක් නැති නිසාය.

සාමාජිකයා අගය හේතුව
Control.Handle IntPtr.Zero වාර්තා කිරීමට එක් එක් පාලකයට වෙනම OS කවුළුවක් නොපවතී. එය කියවීමෙන් තවමත් upstream හි අතුරු ප්‍රතිඵලය ඇති වේ — එය පාලකයේ handle තත්ත්වය සාදයි, එබැවින් IsHandleCreated true වන අතර HandleCreated ක්‍රියාත්මක වේ, _ = control.Handle; ලියන්නේ ඒ සඳහාමය. ImageList.Handle, TreeNode.Handle, Cursor.Handle, TaskDialog.Handle සඳහාද එම අගයමය.
WindowBase.Handle පාරාන්ධ (opaque) ශුන්‍ය නොවන token එකක් HWND එකක් නොවේ. Invoke වලට පෙර handle නිර්මාණය බල කිරීමට WinForms කේතය නිතිපතා .Handle කියවන අතර, ශුන්‍යය ආපසු දීමෙන් එම රටාව බිඳී යයි. අර්ථවත් වන්නේ managed කේතය තුළ පමණි.
WindowBase.PlatformHandle සැබෑ native handle එක, නැතහොත් IntPtr.Zero සැබෑ දෙය, IWindowBackend.TryGetPlatformHandle() හරහා. Avalonia backend මගින් ක්‍රියාත්මක කර ඇත — Windows මත HWND, macOS මත NSWindow, X11 මත XID — සහ Windows-පමණක් WinForms backend මගින්ද, එය තම පෝරමයේ HWND ආපසු දෙයි. දැනට Uno සහ Headless මත ශුන්‍යයි.

ව්‍යාජ handles පිළිබඳ නීතිය

ව්‍යාජව සාදන ලද handle එකක් ආරක්ෂිත වන්නේ එය ඔබ පාලනය කරන managed කේතය හරහා පමණක් ගමන් කරන තාක් කල් පමණි — WindowBase.Handle කරන්නේ හරියටම එයයි, එය ඇත්තේද ඒ සඳහා පමණි. එය native කේතයට ඇතුළු වන මොහොතේම ආරක්ෂිත වීම නතර වේ.

Native libraries ඔබ ලබා දෙන handle එක ගබඩා කර තබා ගැනීම පමණක් නොකරයි. LibVLC හි libvlc_media_player_set_hwnd, mpv හි --wid, සහ GStreamer හි GstVideoOverlay.set_window_handle යන සියල්ල එය OS වෙත යවයි — SetParent, CreateWindowEx, GetClientRect, SetWindowPos, XReparentWindow. ඒවාට සාදාගත් අංකයක් ලබා දීමෙන් ඔබට ප්‍රතිඵල දෙකෙන් එකක් ලැබේ, සහ දෙවැන්න වඩාත් නරකය:

  1. ඇමතුම අසාර්ථක වන අතර, ඔබට කළු සෘජුකෝණාස්‍රයක් හෝ native library එක තුළ crash එකක් ලැබේ.
  2. ඇමතුම වෙනත් දෙයකට අයත් කවුළුවකට එරෙහිව සාර්ථක වේ. HWND යනු pointers නොව handle-table indices වන අතර, කුඩා පූර්ණ සංඛ්‍යා සජීවී අගයන් වේ. hash code එකක් යනු සැබෑ කවුළුවක් සමඟ ගැටෙන හරියටම එවැනි හැඩයේ අංකයකි.

එබැවින්: OS වෙත ළඟා වන කිසිවක් සඳහා කිසි විටෙකත් handle එකක් කෘත්‍රිමව සාදන්න එපා. පහත මාර්ග දෙකෙන් එකක් භාවිත කරන්න.

සන්ධිය (seam): NativeControlHost

Majorsilence.Forms.NativeControlHost යනු යටින් ඇති toolkit එකේ native මූලද්‍රව්‍යයක් සඳහා සෘජුකෝණාස්‍රයක් වෙන් කර තබන Control එකකි. එය තමන්ම කිසිවක් නොඅඳියි; backend එක සැබෑ මූලද්‍රව්‍යය Skia surface එකට ඉහළින් overlay කර එය placeholder එකේ සීමාවලට පෙළගස්වා තබයි. බොහෝ backend මත මෙය “airspace” interop ආකෘතියයි — native මූලද්‍රව්‍ය Skia buffer එකට සංයුක්ත කළ නොහැක, එබැවින් ඒවා එයට ඉහළින් ස්ථානගත කෙරේ. GTK 4 ව්‍යතිරේකයයි, එය පහතින් ආවරණය කෙරේ.

var host = new NativeControlHost { Dock = DockStyle.Fill };
host.NativeControl = someAvaloniaControl;   // හෝ Uno UIElement එකක්, WinForms Control එකක්, Gtk.Widget එකක්…
panel.Controls.Add (host);

ධාරකය සීමා (bounds), සෑම scroll වන මුතුන් මිත්තෙකුගේම (ancestor) ඡේදනය වූ clip කලාපය, සහ සම්පූර්ණ parent දාමය දිගේ ඵලදායී දෘශ්‍යතාව ලුහුබඳින අතර, ඒ තුනම backend වෙත යවයි. paint කිරීමේදී සහ දෘශ්‍යතා වෙනස්වීම්වලදී නැවත සමමුහුර්ත කිරීම ස්වයංක්‍රීයව සිදු වේ; සාමාන්‍ය paint චක්‍රයෙන් පිටත ඔබ ධාරකය ගෙන යන්නේ හෝ ප්‍රමාණය වෙනස් කරන්නේ නම් SyncNativeControl() ඔබම කැඳවන්න.

INativeControlHostBackend යනු විකල්ප backend හැකියාවකි (Backends බලන්න). එය ක්‍රියාත්මක කරන backend සහ ඒවා අපේක්ෂා කරන දේ:

Backend NativeControl හි අපේක්ෂිත type එක හැසිරීම
Avalonia (window host, තනි-දර්ශන host, presenter) Avalonia.Controls.Control Skia surface එකට ඉහළින් ඇති overlay Canvas එකකට එක් කෙරේ.
Uno (window host, presenter) Microsoft.UI.Xaml.UIElement SKXamlCanvas එකට ඉහළින් ඇති root Canvas/panel එකට එක් කෙරේ.
WinForms / WPF (window host, presenter) System.Windows.Forms.Control / System.Windows.FrameworkElement Skia පාලකයට ඉහළින් ඇති overlay ස්තරයකට එක් කෙරේ.
GTK 4 (window host, presenter) Gtk.Widget Gtk.DrawingArea එකට ඉහළින් Gtk.Overlay child එකක් ලෙස එක් කෙරේ. airspace ගැටලුවක් නැත — GTK සෑම widget එකක්ම එක් render tree එකකට සංයුක්ත කරයි, එබැවින් ධාරණය කළ widget එක වෙනත් ඕනෑම එකක් මෙන් clip වී blend වේ.
Headless, Terminal — මෙම හැකියාව ක්‍රියාත්මක නොකරයි; ධාරකය හිස් placeholder එකක් ලෙස විදැහුම් වේ.

වැරදි type එක පැවරීම නිහඬව අසාර්ථක වේ. සෑම backend එකක්ම NativeControl හි type එක පරීක්ෂා කර, එය නොගැළපේ නම් සරලවම ආපසු යයි. කිසිවක් exception විසි නොකරයි, කිසිවක් log නොකරයි, තිරය මත කිසිවක් නොපෙනේ. ඔබේ native අන්තර්ගතය නොපෙනේ නම්, පළමුව type එක පරීක්ෂා කරන්න — Uno backend එකට ලබා දුන් Avalonia Control එකක්, හෝ ප්‍රතිලෝමව, හරියටම මෙය ඇති කරයි.

Airspace සීමාවන්

මේවා Avalonia, Uno, WinForms සහ WPF backend මත ධාරණය කළ ඕනෑම native මූලද්‍රව්‍යයකට අදාළ වන අතර, නිවැරදි කළ යුතු දෝෂ නොව ආකෘතියටම ආවේණික ලක්ෂණ වේ:

GTK 4 ව්‍යතිරේකයයි. Skia surface එක Gtk.Overlay එකක් තුළ පිහිටා ඇති අතර ධාරණය කළ widget එක overlay child එකකි, නමුත් GTK 4 සෑම widget එකක්ම එක් render tree එකකට සංයුක්ත කරයි — එබැවින් ධාරණය කළ widget එක වෙනත් ඕනෑම එකක් මෙන් z-order වී, clip වී, blend වේ, සහ ඉහත අවවාද කිසිවක් අදාළ නොවේ. එකම සම්මුතිය: viewport එකකින් අර්ධ වශයෙන් පිටතට scroll වූ ධාරකයක් එහි native widget එක clip එකක් යටතේ විස්ථාපනය කරනවා වෙනුවට දෘශ්‍යමාන කොටුවට නැවත සකසයි (reflow), එබැවින් අඩක් scroll වූ widget එකක් කපා හැරෙනවා වෙනුවට කුඩා සෘජුකෝණාස්‍රයට නැවත layout වේ.

මාර්ගය A — සැබෑ native අන්තර්ගතය ධාරණය කිරීම

සැබවින්ම OS කවුළුවක් විය යුතු දෙයක් ඔබට අවශ්‍ය වූ විට මෙය භාවිත කරන්න: GPU-accelerated වීඩියෝ surface එකක්, native සිතියම් හෝ CAD දර්ශනයක්, browser engine එකක්.

වැදගත්ම දෙය නම් ඔබ handle එකක් ව්‍යාජව සාදන්නේ නැත — ඔබ සැබෑ එකක් සාදයි සහ එය native library එකට ලබා දෙයි. NativeControl handle එකක් වෙනුවට toolkit වස්තුවක් ගනී, එබැවින් අතරමැද wrapper පියවරක් ඇත. AvaloniaWebViewHandle (Majorsilence.Forms.Avalonia තුළ) සහ Gtk4WebViewHandle (Majorsilence.Forms.Gtk4 තුළ) යනු tree එකේ දැනටමත් ඇති ක්‍රියාත්මක නිදසුන් ය: ඒවා Avalonia.Controls.NativeWebView / WebKit.WebView wrap කර IWebViewHandle.NativeControl ලෙස නිරාවරණය කරයි.

GTK 4 පහසු අවස්ථාවයි. WebKit.WebView යනු හුදෙක් Gtk.Widget එකකි; backend එක එය Skia surface එකට ඉහළින් ඇති Gtk.Overlay එකට එක් කරන අතර GTK එය එම render tree එකටම සංයුක්ත කරයි — සෑදිය යුතු handle එකක් නැත, airspace නැත, වේදිකා ළඟාවීම පිළිබඳ අවවාදයක් නැත. සම්පූර්ණ IWebViewHandle (navigation events, JS eval, script-message bridge) IWebViewFactory හරහා WebKitGTK 6.0 ට එරෙහිව ක්‍රියාත්මක කර ඇත, එම backend මත WebBrowser සහ webview මත පදනම් වූ compat පාලක භාවිත කරන්නේ එයයි.

Avalonia. Avalonia.Controls.NativeControlHost subclass කර CreateNativeControlCore (IPlatformHandle parent) override කරන්න, එය සැබෑ IPlatformHandle එකක් ආපසු දෙයි — Windows මත HWND එකක්, X11 මත XID එකක්, macOS මත NSView එකක්. ඔබේ child කවුළුව එහි සාදන්න, එහි handle එක player එකට ලබා දෙන්න, DestroyNativeControlCore තුළ එය නිදහස් කරන්න, සහ ලැබෙන Avalonia පාලකය NativeControlHost.NativeControl වෙත පවරන්න.

Uno. Uno.UI.NativeElementHosting මගින් Win32NativeWindow(IntPtr Hwnd) සහ X11NativeWindow(IntPtr WindowId) — ඔබ සෑදූ handle එකක් වටා ඇති public wrappers — සහ WASM සඳහා BrowserHtmlElement නිරාවරණය කරයි. ඉන් එකක් ContentPresenter එකක Content ලෙස සකසා එය NativeControl වෙත ලබා දෙන්න.

වේදිකා ළඟාවීම: යථාර්ථවාදීව Windows සහ X11 handle මත පදනම් වූ මාර්ග සඳහා. Wayland සඳහා subsurfaces අවශ්‍ය වේ, macOS ඔබට HWND-හැඩැති කිසිවක් නොව NSView එකක් ලබා දෙයි, සහ WASM/Android/iOS කිසිසේත්ම භාවිත කළ හැකි කවුළු handle එකක් ලබා නොදේ. මේ ආකාරයෙන් ගොඩනගන විශේෂාංගයක් framework එකේ ඉතිරි කොටස ධාවනය වන සෑම තැනකම ධාවනය නොවේ. GTK 4 widget මාර්ගයට එවැනි සීමාවක් නැත — GTK 4 ධාවනය වන ඕනෑම තැනක එය ධාවනය වේ, Wayland ඇතුළුව.

බොහෝ වීඩියෝ libraries කවුළුවක් ගන්නවා වෙනුවට ඔබට decode කළ frames ලබා දිය හැක, එමගින් ගැටලුවෙන් handle එක සම්පූර්ණයෙන්ම ඉවත් වේ: LibVLC හි libvlc_video_set_callbacks (vmem), software mode හි mpv හි render API, GStreamer appsink එකක්, හෝ ඔබ දැනටමත් frames අයිති කර ගන්නා FFmpeg.

ඔබට pixel buffer එකක් ලැබේ, එය SKBitmap එකකට පිටපත් කරන්න, සහ වෙනත් ඕනෑම පාලකයක් මෙන් OnPaint තුළ එය අඳින්න:

public class VideoView : Control
{
    private SKBitmap? frame;

    // decoder එකේම callback thread එකෙන්, අලුතින් decode කළ BGRA frame එකක් සමඟ කැඳවේ.
    // output format එක සකසන විට library එකෙන් width * 4 pitch එකක් ඉල්ලන්න, එවිට
    // ලැබෙන buffer එක තදින් ඇසුරුම් කර ඇති අතර SKBitmap හි row layout එකට ගැළපේ.
    public void PresentFrame (ReadOnlySpan<byte> bgra, int width, int height)
    {
        if (frame is null || frame.Width != width || frame.Height != height) {
            frame?.Dispose ();
            frame = new SKBitmap (width, height, SKColorType.Bgra8888, SKAlphaType.Premul);
        }

        // එක් තොග පිටපතක්, pixel එකින් එක නොවේ: SKBitmap.SetPixel යනු එක් ඇමතුමකට එක් P/Invoke එකකි,
        // එය megapixel frame එකක් සඳහා මිලි තත්පර නොව තත්පර ගණනක් වැය කරයි.
        bgra.CopyTo (frame.GetPixelSpan ());
        frame.NotifyPixelsChanged ();

        // Invalidate() marshal නොකරයි -- එය කවුළුව දක්වා ගොස් ඔබ එය කැඳවන ඕනෑම thread එකක
        // dirty ලෙස සලකුණු කරයි. UI thread එකට පැහැදිලිවම මාරු වන්න.
        BeginInvoke (Invalidate);
    }

    protected override void OnPaint (PaintEventArgs e)
    {
        base.OnPaint (e);
        if (frame is not null)
            e.Canvas.DrawBitmap (frame, new SKRect (0, 0, Width, Height));
    }
}

මෙම සටහන එක් bitmap එකක් භාවිත කරයි, එබැවින් decoder එක ඊළඟ frame එක ලියන අතරතුර paint එකකට එය කියවිය හැක — එය crash එකක් ලෙස නොව, බර යටතේ tearing ලෙස දිස් වේ. bitmaps දෙකක් අතර මාරු වීම (පිටුපස එකට ලියා, තනි reference පැවරුමකින් ප්‍රකාශ කිරීම) සාමාන්‍ය විසඳුම වන අතර, demo එකකට වඩා වැඩි ඕනෑම දෙයක් සඳහා එය කිරීම වටී.

මෙය වඩා හොඳ පෙරනිමිය වන්නේ ඇයි. frame එක Skia දර්ශනයේ කොටසක් බවට පත් වේ, එබැවින් z-order, clipping, scrolling, opacity, සහ transforms සියල්ල වෙනත් සෑම පාලකයකටම මෙන් හැසිරේ — airspace අවවාද කිසිවක් අදාළ නොවේ. එය Headless සහ WASM ඇතුළු සෑම backend එකකම ක්‍රියා කරයි, එය එය පරීක්ෂා කළ හැකි ද කරයි: ඔබට විදැහුම් කළ pixels මත assert කළ හැක. වියදම එක් frame එකකට CPU පිටපතක් සහ software decode වේ.

තෝරා ගැනීම

  මාර්ගය A (native host) මාර්ගය B (frame callbacks)
Majorsilence අන්තර්ගතය සමඟ සංයුක්ත වේ නැත — ඉහළින් අඳියි (GTK 4: ඔව්) ඔව්
Backends Avalonia, Uno, WinForms, WPF, GTK 4 සියල්ල, Headless සහ Terminal ඇතුළුව
වේදිකා යථාර්ථවාදීව Windows, X11 (GTK 4: Wayland ද) සෑම තැනකම
GPU decode මාර්ගය ඔව් නැත (software decode + පිටපත)
CI තුළ පරීක්ෂා කළ හැක නැත ඔව්
සැබෑ OS handle එකක් අවශ්‍යයි ඔව් (එකක් සාදන්න — කිසි විටෙකත් ව්‍යාජ නොකරන්න); GTK 4: නැත, Gtk.Widget එකක් නැත

වීඩියෝ සඳහා පෙරනිමියෙන් B භාවිත කරන්න. ඔබට browser engine එකක්, hardware decode, හෝ කවුළුවකට ඇඳීමට පමණක් දන්නා තෙවන පාර්ශ්ව native දර්ශනයක් අවශ්‍ය වූ විට A වෙත යොමු වන්න.

දන්නා හිඩැස්

සම්පූර්ණ විස්තර repository එකේ docs/native-interop.md තුළ ඇත.