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()
- {
- }
}
}