diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..43de3ddefd --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,3 @@ + +When running code review, please refer to the following documents if they are relevant to the changed parts of the codebase: +- `native/Avalonia.Native/**` -> `native/Avalonia.Native/README.md` diff --git a/Avalonia.sln b/Avalonia.sln index 207f673c26..3b86c4df47 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -99,6 +99,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\TrimmingEnable.props = build\TrimmingEnable.props build\UnitTests.NetFX.props = build\UnitTests.NetFX.props build\XUnit.props = build\XUnit.props + build\MicroCOM.props = build\MicroCOM.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}" diff --git a/Directory.Packages.props b/Directory.Packages.props index d77a38da3b..0b02597482 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,9 +15,9 @@ - - - + + + diff --git a/build/MicroCOM.props b/build/MicroCOM.props new file mode 100644 index 0000000000..1efa4b2ef3 --- /dev/null +++ b/build/MicroCOM.props @@ -0,0 +1,26 @@ + + + + + + $(MSBuildThisFileDirectory)/../../MicroCom + + + + + + + + + + + + + diff --git a/native/Avalonia.Native/README.md b/native/Avalonia.Native/README.md new file mode 100644 index 0000000000..47e06b996e --- /dev/null +++ b/native/Avalonia.Native/README.md @@ -0,0 +1,87 @@ +# Avalonia.Native + +This project implements the macOS native platform backend for Avalonia using Objective-C++ and COM (Component Object Model) interfaces. + + +## COM Reference Management + +This codebase uses COM for cross-boundary object lifetime management. COM types are typically prefixed with `IAvn` (interfaces) or `Avn` (implementations). All COM objects ultimately derive from `IUnknown` and use reference counting (`AddRef`/`Release`). + +**No raw COM pointer should ever be stored in class fields, instance variables, closures, or containers.** Raw COM pointers are only acceptable as function parameters and local variables with the lifetime of the current function call. + +### COM Return Value Convention + +By convention, any COM interface reference returned from a COM call has its reference counter incremented — it is the caller's responsibility to release it. With the `IFoo* GetFoo()` pattern it is hard to track this correctly, so all methods in `avn.idl` should use the out-parameter pattern instead: + +```cpp +// WRONG — easy to leak or forget Release: +IFoo* GetFoo(); + +// CORRECT — use HRESULT + out-parameter: +HRESULT GetFoo(IFoo** ppv); +``` +# Smart pointer types + +### Objective-C Objects + +For Objective-C objects, reference counting is provided by the compiler. Objective-C pointers _look_ like raw pointers, however every `NSObject*` reference is properly ref-counted by ARC (Automatic Reference Counting). No manual wrapping is needed for `NSObject*` fields. + +### COM Smart Pointer Types + +All retained COM references must use one of the following wrappers defined in `inc/comimpl.h`: + +#### `ComPtr` — Owning Reference + +Use for any COM pointer that the holder needs to keep alive. + +```cpp +ComPtr _window; // correct +IAvnWindow* _window; // WRONG — raw COM pointer in a field +``` + +#### `ComObjectWeakPtr` — Non-Owning Weak Reference + +Use for intentional non-owning references to `ComObject`-derived objects (internal implementations). Allows safely referencing COM objects without extending their lifetime. + +```cpp +ComObjectWeakPtr _parent; // correct +WindowBaseImpl* _parent; // WRONG +``` + +Access weak references with `tryGet()`, which returns a `ComPtr` (null if the object was destroyed): + +```cpp +auto parent = _parent.tryGet(); +if (parent) { + parent->DoSomething(); +} +``` + +#### `ComStaticPtr` — Process-Lifetime Static Reference + +Use for static/global COM singletons that must live for the entire process lifetime. Intentionally does **not** Release in its destructor to avoid crashes during app teardown. + +```cpp +static ComStaticPtr GlDisplay; // correct +static IAvnGlDisplay* GlDisplay; // WRONG +``` + +Assign via `set()`: + +```cpp +GlDisplay.set(comnew()); +``` + +### `comnew(args...)` — COM Object Factory + +A convenience template similar to `std::make_shared`. Creates a new COM object and returns it wrapped in a `ComPtr` with correct ownership (no double-AddRef): + +```cpp +// Instead of: +ComPtr cursor(new Cursor(nsCursor), true); + +// Write: +ComPtr cursor = comnew(nsCursor); +``` + +This works because `new T()` on a `ComObject`-derived type starts with refcount=1, and `comnew` wraps it in a `ComPtr` that takes ownership without an additional `AddRef`. diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h index e16adb1ec2..0b09ee66fb 100644 --- a/native/Avalonia.Native/inc/comimpl.h +++ b/native/Avalonia.Native/inc/comimpl.h @@ -8,6 +8,7 @@ #include #include +#include /** START_COM_CALL causes AddRef to be called at the beginning of a function. @@ -110,6 +111,11 @@ public: TInterface** getPPV() { + if(_obj) + { + _obj->Release(); + _obj = nullptr; + } return &_obj; } @@ -138,6 +144,78 @@ public: } }; +template +ComPtr comnew(Args&&... args) +{ + return ComPtr(new T(std::forward(args)...), true); +} + +/** + * ComStaticPtr is a COM smart pointer for process-lifetime static singletons. + * It AddRef/Release on set/reset, but intentionally does NOT Release in its destructor + * to avoid crashes during app teardown when destruction order is undefined. + */ +template +class ComStaticPtr +{ +private: + TInterface* _obj = nullptr; +public: + ComStaticPtr() = default; + ~ComStaticPtr() = default; + + ComStaticPtr(const ComPtr& ptr) + { + _obj = ptr; + if (_obj) + _obj->AddRef(); + } + + ComStaticPtr(const ComStaticPtr&) = delete; + ComStaticPtr(ComStaticPtr&&) = delete; + ComStaticPtr& operator=(const ComStaticPtr&) = delete; + ComStaticPtr& operator=(ComStaticPtr&&) = delete; + + void set(TInterface* obj) + { + if (_obj == obj) + return; + if (_obj) + _obj->Release(); + _obj = obj; + if (_obj) + _obj->AddRef(); + } + + void setNoAddRef(TInterface* obj) + { + if (_obj) + _obj->Release(); + _obj = obj; + } + + void reset() + { + if (_obj) + { + _obj->Release(); + _obj = nullptr; + } + } + + TInterface* getRaw() const { return _obj; } + + template ComPtr getComPtr() + { + if (_obj == nullptr) + return nullptr; + return dynamic_cast(_obj); + } + + operator TInterface*() const { return _obj; } + TInterface* operator->() const { return _obj; } +}; + class ComObjectWeakRefToken { public: @@ -249,6 +327,9 @@ public: _rawPtr = nullptr; _token = nullptr; } + + bool operator==(const ComObjectWeakPtr& other) const { return _rawPtr == other._rawPtr; } + bool operator!=(const ComObjectWeakPtr& other) const { return _rawPtr != other._rawPtr; } }; #define FORWARD_IUNKNOWN() \ diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index 78a40b215e..b6964195e1 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -79,16 +79,14 @@ public: class AvnStringArrayImpl : public virtual ComSingleObject { private: - std::vector> _list; + std::vector> _list; public: FORWARD_IUNKNOWN() AvnStringArrayImpl(NSArray* array) { for(int c = 0; c < [array count]; c++) { - ComPtr s; - *s.getPPV() = new AvnStringImpl([array objectAtIndex:c]); - _list.push_back(s); + _list.push_back(comnew([array objectAtIndex:c])); } } @@ -96,17 +94,14 @@ public: { for(int c = 0; c < [array count]; c++) { - ComPtr s; - *s.getPPV() = new AvnStringImpl([array objectAtIndex:c].absoluteString); + auto s = comnew([array objectAtIndex:c].absoluteString); _list.push_back(s); } } AvnStringArrayImpl(NSString* string) { - ComPtr s; - *s.getPPV() = new AvnStringImpl(string); - _list.push_back(s); + _list.push_back(comnew(string)); } virtual unsigned int GetCount() override diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 1e7c98cb3a..c4e715229b 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -37,7 +37,7 @@ bool _isTransitioningToFullScreen; bool _isTitlebarSession; AvnMenu* _menu; - IAvnAutomationPeer* _automationPeer; + ComPtr _automationPeer; AvnAutomationNode* _automationNode; } @@ -144,8 +144,6 @@ { auto appMenu = ::GetAppMenu(); - auto nativeAppMenu = dynamic_cast(appMenu); - [[appMenuItem menu] removeItem:appMenuItem]; if(_menu != nullptr) @@ -153,9 +151,9 @@ [_menu setHasGlobalMenuItem:false]; } - [nativeAppMenu->GetNative() addItem:appMenuItem]; + [appMenu->GetNative() addItem:appMenuItem]; - [NSApp setMenu:nativeAppMenu->GetNative()]; + [NSApp setMenu:appMenu->GetNative()]; } } diff --git a/native/Avalonia.Native/src/OSX/TopLevelImpl.mm b/native/Avalonia.Native/src/OSX/TopLevelImpl.mm index bdfc1be62f..d0b62d9d55 100644 --- a/native/Avalonia.Native/src/OSX/TopLevelImpl.mm +++ b/native/Avalonia.Native/src/OSX/TopLevelImpl.mm @@ -311,7 +311,9 @@ HRESULT TopLevelImpl::BeginDragAndDropOperation( for (auto i = 0; i < itemCount; ++i) { - auto item = source->GetItem(i); + ComPtr item; + if(S_OK != source->GetItem(i, item.getPPV())) + continue; auto writeableItem = [[WriteableClipboardItem alloc] initWithItem:item source:source]; auto draggingItem = [[NSDraggingItem alloc] initWithPasteboardWriter:writeableItem]; [draggingItem setDraggingFrame:dragItemRect contents:dragItemImage]; diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 873a520d6d..2fbbf60bba 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -101,7 +101,7 @@ protected: AutoFitContentView *StandardContainer; AvnPoint lastPositionSet; bool _shown; - std::list _children; + std::list> _children; public: ComObjectWeakPtr Parent = nullptr; diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 551ea4c2e0..2a1de62b05 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -27,7 +27,7 @@ WindowBaseImpl::~WindowBaseImpl() { } WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, bool usePanel) : TopLevelImpl(events) { - _children = std::list(); + _children = std::list>(); _shown = false; _inResize = false; BaseEvents = events; diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index f42b7c27cb..548d7aaf09 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -100,11 +100,14 @@ void WindowImpl::ZOrderChildWindows() { for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) { - auto window = (*iterator)->Window; + auto child = (*iterator).tryGet(); + if (!child) continue; + + auto window = child->Window; // #9565: Only bring window to front if it's on the currently active space if ([window isOnActiveSpace]) { - (*iterator)->BringToFront(); + child->BringToFront(); } } } @@ -113,7 +116,8 @@ bool WindowImpl::CanBecomeKeyWindow() { for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) { - if((*iterator)->IsModal()) + auto child = (*iterator).tryGet(); + if(child && child->IsModal()) { return false; } diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index b42dc22f7a..005eaf0b50 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -8,7 +8,7 @@ @implementation AvnAccessibilityElement { - IAvnAutomationPeer* _peer; + ComPtr _peer; AvnAutomationNode* _node; NSMutableArray* _children; NSArray* _attributeNames; @@ -70,7 +70,7 @@ return [NSString stringWithFormat:@"%@ '%@' (%p)", GetNSStringAndRelease(_peer->GetClassName()), GetNSStringAndRelease(_peer->GetName()), - _peer]; + _peer.getRaw()]; } - (IAvnAutomationPeer *)peer diff --git a/native/Avalonia.Native/src/OSX/cgl.mm b/native/Avalonia.Native/src/OSX/cgl.mm index 4fc4064df1..e963322033 100644 --- a/native/Avalonia.Native/src/OSX/cgl.mm +++ b/native/Avalonia.Native/src/OSX/cgl.mm @@ -219,7 +219,7 @@ public: } }; -static IAvnGlDisplay* GlDisplay = new AvnGlDisplay(); +static ComStaticPtr GlDisplay(comnew()); extern IAvnGlDisplay* GetGlDisplay() diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm index 9786a64b27..f050a894c8 100644 --- a/native/Avalonia.Native/src/OSX/clipboard.mm +++ b/native/Avalonia.Native/src/OSX/clipboard.mm @@ -183,7 +183,9 @@ public: for (auto i = 0; i < count; ++i) { - auto item = source->GetItem(i); + ComPtr item; + if(S_OK != source->GetItem(i, item.getPPV())) + continue; auto writeableItem = [[WriteableClipboardItem alloc] initWithItem:item source:source]; [writeableItems addObject:writeableItem]; } @@ -219,8 +221,8 @@ extern IAvnClipboard* CreateClipboard(NSPasteboard* pb) @implementation WriteableClipboardItem { - IAvnClipboardDataItem* _item; - IAvnClipboardDataSource* _source; + ComPtr _item; + ComPtr _source; } - (nonnull WriteableClipboardItem*) initWithItem:(nonnull IAvnClipboardDataItem*)item source:(nonnull IAvnClipboardDataSource*)source @@ -229,9 +231,6 @@ extern IAvnClipboard* CreateClipboard(NSPasteboard* pb) _item = item; _source = source; - // Each item references its source so it doesn't get disposed too early. - source->AddRef(); - return self; } @@ -283,8 +282,9 @@ NSString* TryConvertFormatToUti(NSString* format) - (nonnull NSArray*) writableTypesForPasteboard:(nonnull NSPasteboard*)pasteboard { - auto formats = _item->ProvideFormats(); - if (formats == nullptr) + ComPtr formats; + auto hr = _item->ProvideFormats(formats.getPPV()); + if (hr != S_OK || formats == nullptr) return [NSArray array]; auto count = formats->GetCount(); @@ -304,7 +304,6 @@ NSString* TryConvertFormatToUti(NSString* format) if (uti != nil) [utis addObject:uti]; } - formats->Release(); [utis addObject:GetAvnCustomDataType()]; @@ -323,8 +322,9 @@ NSString* TryConvertFormatToUti(NSString* format) if ([type isEqualToString:GetAvnCustomDataType()]) return @""; - ComPtr value(_item->GetValue([type UTF8String]), true); - if (value.getRaw() == nullptr) + ComPtr value; + HRESULT hr = _item->GetValue([type UTF8String], value.getPPV()); + if (hr != S_OK || value.getRaw() == nullptr) return nil; if (value->IsString()) @@ -336,19 +336,4 @@ NSString* TryConvertFormatToUti(NSString* format) return [NSData dataWithBytesNoCopy:buffer length:length]; } -- (void) dealloc -{ - if (_item != nullptr) - { - _item->Release(); - _item = nullptr; - } - - if (_source != nullptr) - { - _source->Release(); - _source = nullptr; - } -} - @end diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index a993784fc4..6564a457af 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -36,7 +36,8 @@ extern IAvnPlatformRenderTimer* CreatePlatformRenderTimer(); extern IAvnNativeObjectsMemoryManagement* CreateMemoryManagementHelper(); extern void SetAppMenu(IAvnMenu *menu); extern void SetServicesMenu (IAvnMenu* menu); -extern IAvnMenu* GetAppMenu (); +class AvnAppMenu; +extern AvnAppMenu* GetAppMenu (); extern NSMenuItem* GetAppMenuItem (); extern void SetDockMenu(NSMenu* menu); diff --git a/native/Avalonia.Native/src/OSX/cursor.h b/native/Avalonia.Native/src/OSX/cursor.h index 75a9c3d2ad..cc7f765785 100644 --- a/native/Avalonia.Native/src/OSX/cursor.h +++ b/native/Avalonia.Native/src/OSX/cursor.h @@ -28,5 +28,4 @@ public: } }; -extern std::map s_cursorMap; #endif /* cursor_h */ diff --git a/native/Avalonia.Native/src/OSX/cursor.mm b/native/Avalonia.Native/src/OSX/cursor.mm index 855a9a9abc..9947aa7c53 100644 --- a/native/Avalonia.Native/src/OSX/cursor.mm +++ b/native/Avalonia.Native/src/OSX/cursor.mm @@ -3,24 +3,24 @@ class CursorFactory : public ComSingleObject { - Cursor* arrowCursor = new Cursor([NSCursor arrowCursor]); - Cursor* crossCursor = new Cursor([NSCursor crosshairCursor]); - Cursor* resizeUpCursor = new Cursor([NSCursor resizeUpCursor]); - Cursor* resizeDownCursor = new Cursor([NSCursor resizeDownCursor]); - Cursor* resizeUpDownCursor = new Cursor([NSCursor resizeUpDownCursor]); - Cursor* dragCopyCursor = new Cursor([NSCursor dragCopyCursor]); - Cursor* openHandCursor = new Cursor([NSCursor openHandCursor]); - Cursor* dragLinkCursor = new Cursor([NSCursor dragLinkCursor]); - Cursor* pointingHandCursor = new Cursor([NSCursor pointingHandCursor]); - Cursor* contextualMenuCursor = new Cursor([NSCursor contextualMenuCursor]); - Cursor* IBeamCursor = new Cursor([NSCursor IBeamCursor]); - Cursor* resizeLeftCursor = new Cursor([NSCursor resizeLeftCursor]); - Cursor* resizeRightCursor = new Cursor([NSCursor resizeRightCursor]); - Cursor* resizeWestEastCursor = new Cursor([NSCursor resizeLeftRightCursor]); - Cursor* operationNotAllowedCursor = new Cursor([NSCursor operationNotAllowedCursor]); - Cursor* noCursor = new Cursor([NSCursor arrowCursor], true); + ComPtr arrowCursor = comnew([NSCursor arrowCursor]); + ComPtr crossCursor = comnew([NSCursor crosshairCursor]); + ComPtr resizeUpCursor = comnew([NSCursor resizeUpCursor]); + ComPtr resizeDownCursor = comnew([NSCursor resizeDownCursor]); + ComPtr resizeUpDownCursor = comnew([NSCursor resizeUpDownCursor]); + ComPtr dragCopyCursor = comnew([NSCursor dragCopyCursor]); + ComPtr openHandCursor = comnew([NSCursor openHandCursor]); + ComPtr dragLinkCursor = comnew([NSCursor dragLinkCursor]); + ComPtr pointingHandCursor = comnew([NSCursor pointingHandCursor]); + ComPtr contextualMenuCursor = comnew([NSCursor contextualMenuCursor]); + ComPtr IBeamCursor = comnew([NSCursor IBeamCursor]); + ComPtr resizeLeftCursor = comnew([NSCursor resizeLeftCursor]); + ComPtr resizeRightCursor = comnew([NSCursor resizeRightCursor]); + ComPtr resizeWestEastCursor = comnew([NSCursor resizeLeftRightCursor]); + ComPtr operationNotAllowedCursor = comnew([NSCursor operationNotAllowedCursor]); + ComPtr noCursor = comnew([NSCursor arrowCursor], true); - std::map s_cursorMap = + std::map> s_cursorMap = { { CursorArrow, arrowCursor }, { CursorAppStarting, arrowCursor }, diff --git a/native/Avalonia.Native/src/OSX/menu.h b/native/Avalonia.Native/src/OSX/menu.h index eb706d3e07..72c19eb120 100644 --- a/native/Avalonia.Native/src/OSX/menu.h +++ b/native/Avalonia.Native/src/OSX/menu.h @@ -29,8 +29,8 @@ class AvnAppMenuItem : public ComSingleObject { private: NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem - IAvnActionCallback* _callback; - IAvnPredicateCallback* _predicate; + ComPtr _callback; + ComPtr _predicate; bool _isCheckable; public: diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index 1adbe093bb..a67c96a796 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -35,7 +35,7 @@ @implementation AvnMenuItem { - AvnAppMenuItem* _item; + ComObjectWeakPtr _item; } - (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem @@ -61,13 +61,19 @@ { return YES; } + auto item = _item.tryGet(); + if(item == nullptr) + return NO; - return _item->EvaluateItemEnabled(); + return item->EvaluateItemEnabled(); } - (void)didSelectItem:(nullable id)sender { - _item->RaiseOnClicked(); + auto item = _item.tryGet(); + if(item == nullptr) + return; + item->RaiseOnClicked(); } @end @@ -478,17 +484,15 @@ extern IAvnMenuItem* CreateAppMenuItemSeparator() } } -static IAvnMenu* s_appMenu = nullptr; +static ComStaticPtr s_appMenu; static NSMenuItem* s_appMenuItem = nullptr; extern void SetAppMenu(IAvnMenu *menu) { - s_appMenu = menu; + s_appMenu.set(dynamic_cast(menu)); if(s_appMenu != nullptr) { - auto nativeMenu = dynamic_cast(s_appMenu); - auto currentMenu = [s_appMenuItem menu]; if (currentMenu != nullptr) @@ -496,7 +500,7 @@ extern void SetAppMenu(IAvnMenu *menu) [currentMenu removeItem:s_appMenuItem]; } - s_appMenuItem = [nativeMenu->GetNative() itemAtIndex:0]; + s_appMenuItem = [s_appMenu->GetNative() itemAtIndex:0]; if (currentMenu == nullptr) { @@ -524,9 +528,9 @@ extern void SetServicesMenu (IAvnMenu* menu) [NSApplication sharedApplication].servicesMenu = nativeMenu->GetNative(); } -extern IAvnMenu* GetAppMenu () +extern AvnAppMenu* GetAppMenu () { - return s_appMenu; + return s_appMenu.getRaw(); } extern NSMenuItem* GetAppMenuItem () diff --git a/native/Avalonia.Native/src/OSX/metal.mm b/native/Avalonia.Native/src/OSX/metal.mm index 517872b147..3da1282306 100644 --- a/native/Avalonia.Native/src/OSX/metal.mm +++ b/native/Avalonia.Native/src/OSX/metal.mm @@ -304,7 +304,7 @@ public: } }; -static AvnMetalDisplay* _display = new AvnMetalDisplay(); +static ComStaticPtr _display(comnew()); extern IAvnMetalDisplay* GetMetalDisplay() { diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index c4d20ad4b7..c58a2b894b 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -7,11 +7,11 @@ $(NoWarn);CS0649;CA1847 1 $(AvsCurrentTargetFramework) + true - @@ -22,6 +22,8 @@ + + diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj index 73b983f788..22a091449f 100644 --- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj +++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj @@ -5,6 +5,7 @@ $(AvsCurrentTargetFramework) true true + true @@ -37,4 +38,5 @@ + diff --git a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj index 17fa827202..4f3d98c88e 100644 --- a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj +++ b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj @@ -2,13 +2,14 @@ $(AvsCurrentTargetFramework);$(AvsLegacyTargetFrameworks) true + true - + diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 9f79fad8ff..fb38606254 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -7,6 +7,7 @@ True $(AvsCurrentTargetFramework);$(AvsLegacyTargetFrameworks) true + true @@ -21,10 +22,10 @@ - + diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 0ab5189ffd..ce9ce0d5b0 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -1004,14 +1004,14 @@ interface IAvnClipboard : IUnknown interface IAvnClipboardDataSource : IUnknown { int GetItemCount(); - IAvnClipboardDataItem* GetItem(int index); + HRESULT GetItem(int index, IAvnClipboardDataItem**ppv); } [uuid(e40f36d9-69f4-45fd-9ca2-6e64e80feb6d)] interface IAvnClipboardDataItem : IUnknown { - IAvnStringArray* ProvideFormats(); - IAvnClipboardDataValue* GetValue([const] char* format); + HRESULT ProvideFormats(IAvnStringArray** ppv); + HRESULT GetValue([const] char* format, IAvnClipboardDataValue** ppv); } [uuid(e97f24f6-1c84-4d95-8ffe-5b2c72e016ed)] diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index f2e75c8f6b..29ccafd02e 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -3,6 +3,7 @@ $(AvsCurrentTargetFramework);$(AvsLegacyTargetFrameworks) true true + true @@ -17,13 +18,13 @@ - + diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs index 10dbcfcc6b..f896448d68 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs @@ -41,15 +41,16 @@ namespace Avalonia.Win32.WinRT.Composition } public uint SourceCount => (uint)(_sources?.Length ?? 0); - - public override void OnUnreferencedFromNative() + + protected override void Destroyed() { - if (_sources == null) - return; - - /*foreach(var s in _sources) - s.Dispose();*/ - _sources = null; + if (_sources != null) + { + foreach (var source in _sources) + source.Dispose(); + } + + base.Destroyed(); } } diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs index 29c1e1fe27..2eea4d6820 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs @@ -83,12 +83,21 @@ internal static class WinUiCompositionUtils using var backDropParameter = backDropParameterFactory.Create(backdropString.Handle); using var backDropParameterAsSource = backDropParameter.QueryInterface(); - var blurEffect = new WinUIGaussianBlurEffect(backDropParameterAsSource); + using var blurEffect = new WinUIGaussianBlurEffect(backDropParameterAsSource); + + // WinRT has broken reference counting for effects returned from GetSource. It will Release returned ref + // and attempt to use it afterward. This usually works because the wrapper effect DOES hold a reference + // to the COM object, so invalid memory access succeeds. + // In our case MicroCOM will rightfully release native memory once the latest native reference is gone. + // Another problem is WinUIEffectBase destroying its sources when it's no longer referenced by anything. + // So we need to force-keep a native pointer until we are done. + using var blurLease = MicroComRuntime.LeaseNativePointerForCall(blurEffect); + using var blurEffectFactory = compositor.CreateEffectFactory(blurEffect); using var compositionEffectBrush = blurEffectFactory.CreateBrush(); using var backdropBrush = CreateBackdropBrush(compositor); - var saturateEffect = new SaturationEffect(blurEffect); + using var saturateEffect = new SaturationEffect(blurEffect); using var satEffectFactory = compositor.CreateEffectFactory(saturateEffect); using var sat = satEffectFactory.CreateBrush(); compositionEffectBrush.SetSourceParameter(backdropString.Handle, backdropBrush); diff --git a/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs b/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs index 602375758e..e79f445a8a 100644 --- a/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs +++ b/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs @@ -8,13 +8,8 @@ using MicroCom.Runtime; namespace Avalonia.Win32.WinRT { [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] - internal class WinRTInspectable : IInspectable, IMicroComShadowContainer + internal class WinRTInspectable : Avalonia.MicroCom.CallbackBase, IInspectable { - public virtual void Dispose() - { - - } - public unsafe void GetIids(ulong* iidCount, Guid** iids) { var interfaces = GetType().GetInterfaces().Where(typeof(IUnknown).IsAssignableFrom) @@ -28,13 +23,5 @@ namespace Avalonia.Win32.WinRT public IntPtr RuntimeClassName => NativeWinRTMethods.WindowsCreateString(GetType().FullName!); public TrustLevel TrustLevel => TrustLevel.BaseTrust; - public MicroComShadow? Shadow { get; set; } - public virtual void OnReferencedFromNative() - { - } - - public virtual void OnUnreferencedFromNative() - { - } } }