From 3930759007fa00595401717e50cbb8aae6ac70fa Mon Sep 17 00:00:00 2001 From: Carter Li Date: Wed, 23 Sep 2026 13:33:13 +0800 Subject: [PATCH] Common (Windows): adds a WinRT helper --- CMakeLists.txt | 1 + src/common/windows/winrt.cpp | 322 ++++++++++++++++++++++++++ src/common/windows/winrt.hpp | 121 ++++++++++ src/detection/media/media_windows.cpp | 111 +-------- 4 files changed, 455 insertions(+), 100 deletions(-) create mode 100644 src/common/windows/winrt.cpp create mode 100644 src/common/windows/winrt.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 420354b3c..d10f6f397 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1071,6 +1071,7 @@ elseif(WIN32) src/common/windows/registry.c src/common/windows/unicode.c src/common/windows/variant.cpp + src/common/windows/winrt.cpp src/common/windows/version.c src/detection/battery/battery_windows.c src/detection/bios/bios_windows.c diff --git a/src/common/windows/winrt.cpp b/src/common/windows/winrt.cpp new file mode 100644 index 000000000..14ec54f4d --- /dev/null +++ b/src/common/windows/winrt.cpp @@ -0,0 +1,322 @@ +#include "winrt.hpp" + +#if FF_HAVE_WINRT + + #include + +// A COM vtable is a flat array of function pointers: IUnknown's three methods, IInspectable's three, +// then the interface's own methods in declaration order. The pair below writes that array by hand +// instead of deriving from winrt::impl::abi<...>. +// +// Deriving is the idiomatic choice, and it would let the compiler check every slot -- but it cannot +// be used here. A class with virtual functions makes clang emit the vtable of its abstract base, and +// each pure slot of that table refers to libc++abi's __cxa_pure_virtual. fastfetch is linked by the C +// compiler and therefore has neither libc++ nor libc++abi, so that reference would be the only +// unresolved symbol in the program. A hand-written table has no virtual functions at all, which +// keeps this file as free of the C++ runtime as `media_windows.cpp` is. +namespace { + +using WinrtIterableAbi = winrt::impl::abi>::type; + +struct FFWinrtHstringIterable; +struct FFWinrtHstringIterator; + +struct FFWinrtHstringIterableVtbl { + std::int32_t (STDMETHODCALLTYPE* QueryInterface)(FFWinrtHstringIterable* self, GUID const& riid, void** result); + std::uint32_t (STDMETHODCALLTYPE* AddRef)(FFWinrtHstringIterable* self); + std::uint32_t (STDMETHODCALLTYPE* Release)(FFWinrtHstringIterable* self); + std::int32_t (STDMETHODCALLTYPE* GetIids)(FFWinrtHstringIterable* self, std::uint32_t* count, GUID** iids); + std::int32_t (STDMETHODCALLTYPE* GetRuntimeClassName)(FFWinrtHstringIterable* self, void** name); + std::int32_t (STDMETHODCALLTYPE* GetTrustLevel)(FFWinrtHstringIterable* self, std::int32_t* level); + std::int32_t (STDMETHODCALLTYPE* First)(FFWinrtHstringIterable* self, void** result); +}; + +struct FFWinrtHstringIteratorVtbl { + std::int32_t (STDMETHODCALLTYPE* QueryInterface)(FFWinrtHstringIterator* self, GUID const& riid, void** result); + std::uint32_t (STDMETHODCALLTYPE* AddRef)(FFWinrtHstringIterator* self); + std::uint32_t (STDMETHODCALLTYPE* Release)(FFWinrtHstringIterator* self); + std::int32_t (STDMETHODCALLTYPE* GetIids)(FFWinrtHstringIterator* self, std::uint32_t* count, GUID** iids); + std::int32_t (STDMETHODCALLTYPE* GetRuntimeClassName)(FFWinrtHstringIterator* self, void** name); + std::int32_t (STDMETHODCALLTYPE* GetTrustLevel)(FFWinrtHstringIterator* self, std::int32_t* level); + std::int32_t (STDMETHODCALLTYPE* get_Current)(FFWinrtHstringIterator* self, HSTRING* value); + std::int32_t (STDMETHODCALLTYPE* get_HasCurrent)(FFWinrtHstringIterator* self, bool* value); + std::int32_t (STDMETHODCALLTYPE* MoveNext)(FFWinrtHstringIterator* self, bool* value); + std::int32_t (STDMETHODCALLTYPE* GetMany)(FFWinrtHstringIterator* self, std::uint32_t capacity, HSTRING* values, std::uint32_t* actual); +}; + +// Everything the pair allocates comes from malloc(), because the placement is driven by the C +// allocator rather than by `new` (see the note above). +struct FFWinrtHstringIterable { + const FFWinrtHstringIterableVtbl* lpVtbl; + std::uint32_t refCount; + FFlist strings; // HSTRING +}; + +struct FFWinrtHstringIterator { + const FFWinrtHstringIteratorVtbl* lpVtbl; + std::uint32_t refCount; + FFWinrtHstringIterable* owner; + std::uint32_t index; +}; + +// --------------------------------------------------------------------------------------------- +// IIterable +// --------------------------------------------------------------------------------------------- + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterable_QueryInterface(FFWinrtHstringIterable* self, GUID const& riid, void** result) { + if (!result) { + return E_POINTER; + } + *result = nullptr; + + if (IsEqualGUID(riid, winrt::guid_of()) || + IsEqualGUID(riid, winrt::guid_of()) || + IsEqualGUID(riid, winrt::guid_of>())) { + *result = self; + self->lpVtbl->AddRef(self); + return S_OK; + } + + return E_NOINTERFACE; +} + +std::uint32_t STDMETHODCALLTYPE ffWinrtHstringIterable_AddRef(FFWinrtHstringIterable* self) { + return ++self->refCount; +} + +std::uint32_t STDMETHODCALLTYPE ffWinrtHstringIterable_Release(FFWinrtHstringIterable* self) { + std::uint32_t remaining = --self->refCount; + if (remaining == 0) { + FF_LIST_FOR_EACH(HSTRING, string, self->strings) { + WindowsDeleteString(*string); + } + ffListDestroy(&self->strings); + free(self); + } + return remaining; +} + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterable_GetIids(FFWinrtHstringIterable* self, std::uint32_t* count, GUID** iids) { + (void) self; + *count = 0; + *iids = nullptr; + return S_OK; +} + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterable_GetRuntimeClassName(FFWinrtHstringIterable* self, void** name) { + (void) self; + *name = nullptr; + return S_OK; +} + +// `level` is the ABI's 32-bit TrustLevel enum; BaseTrust is 0. +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterable_GetTrustLevel(FFWinrtHstringIterable* self, std::int32_t* level) { + (void) self; + *level = 0; + return S_OK; +} + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterable_First(FFWinrtHstringIterable* self, void** result) { + if (!result) { + return E_POINTER; + } + *result = nullptr; + + auto* iterator = (FFWinrtHstringIterator*) malloc(sizeof(FFWinrtHstringIterator)); + if (!iterator) { + return E_OUTOFMEMORY; + } + + iterator->lpVtbl = nullptr; // assigned below, once the table is declared + iterator->refCount = 1; + iterator->owner = self; + iterator->index = 0; + + // The iterator outlives the call that created it, so it holds a reference of its own. + self->lpVtbl->AddRef(self); + + extern const FFWinrtHstringIteratorVtbl ffWinrtHstringIteratorVtbl; + iterator->lpVtbl = &ffWinrtHstringIteratorVtbl; + + *result = iterator; + return S_OK; +} + +const FFWinrtHstringIterableVtbl ffWinrtHstringIterableVtbl = { + ffWinrtHstringIterable_QueryInterface, + ffWinrtHstringIterable_AddRef, + ffWinrtHstringIterable_Release, + ffWinrtHstringIterable_GetIids, + ffWinrtHstringIterable_GetRuntimeClassName, + ffWinrtHstringIterable_GetTrustLevel, + ffWinrtHstringIterable_First, +}; + +// --------------------------------------------------------------------------------------------- +// IIterator +// --------------------------------------------------------------------------------------------- + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterator_QueryInterface(FFWinrtHstringIterator* self, GUID const& riid, void** result) { + if (!result) { + return E_POINTER; + } + *result = nullptr; + + if (IsEqualGUID(riid, winrt::guid_of()) || + IsEqualGUID(riid, winrt::guid_of()) || + IsEqualGUID(riid, winrt::guid_of>())) { + *result = self; + self->lpVtbl->AddRef(self); + return S_OK; + } + + return E_NOINTERFACE; +} + +std::uint32_t STDMETHODCALLTYPE ffWinrtHstringIterator_AddRef(FFWinrtHstringIterator* self) { + return ++self->refCount; +} + +std::uint32_t STDMETHODCALLTYPE ffWinrtHstringIterator_Release(FFWinrtHstringIterator* self) { + std::uint32_t remaining = --self->refCount; + if (remaining == 0) { + self->owner->lpVtbl->Release(self->owner); + free(self); + } + return remaining; +} + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterator_GetIids(FFWinrtHstringIterator* self, std::uint32_t* count, GUID** iids) { + (void) self; + *count = 0; + *iids = nullptr; + return S_OK; +} + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterator_GetRuntimeClassName(FFWinrtHstringIterator* self, void** name) { + (void) self; + *name = nullptr; + return S_OK; +} + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterator_GetTrustLevel(FFWinrtHstringIterator* self, std::int32_t* level) { + (void) self; + *level = 0; + return S_OK; +} + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterator_get_Current(FFWinrtHstringIterator* self, HSTRING* value) { + if (!value) { + return E_POINTER; + } + *value = nullptr; + + if (self->index >= self->owner->strings.length) { + return E_BOUNDS; + } + + // The caller owns what it gets, so hand out a duplicate rather than the stored string. + return WindowsDuplicateString(*(HSTRING*) ffListGet(&self->owner->strings, sizeof(HSTRING), self->index), value); +} + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterator_get_HasCurrent(FFWinrtHstringIterator* self, bool* value) { + if (!value) { + return E_POINTER; + } + *value = self->index < self->owner->strings.length; + return S_OK; +} + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterator_MoveNext(FFWinrtHstringIterator* self, bool* value) { + if (!value) { + return E_POINTER; + } + + if (self->index < self->owner->strings.length) { + ++self->index; + } + *value = self->index < self->owner->strings.length; + return S_OK; +} + +std::int32_t STDMETHODCALLTYPE ffWinrtHstringIterator_GetMany(FFWinrtHstringIterator* self, std::uint32_t capacity, HSTRING* values, std::uint32_t* actual) { + if (!actual) { + return E_POINTER; + } + *actual = 0; + + if (capacity && !values) { + return E_POINTER; + } + + std::uint32_t available = self->owner->strings.length - self->index; + std::uint32_t taken = capacity < available ? capacity : available; + + for (std::uint32_t i = 0; i < taken; ++i) { + WindowsDuplicateString(*(HSTRING*) ffListGet(&self->owner->strings, sizeof(HSTRING), self->index + i), &values[i]); + } + + self->index += taken; + *actual = taken; + + return taken == capacity ? S_OK : S_FALSE; +} + +const FFWinrtHstringIteratorVtbl ffWinrtHstringIteratorVtbl = { + ffWinrtHstringIterator_QueryInterface, + ffWinrtHstringIterator_AddRef, + ffWinrtHstringIterator_Release, + ffWinrtHstringIterator_GetIids, + ffWinrtHstringIterator_GetRuntimeClassName, + ffWinrtHstringIterator_GetTrustLevel, + ffWinrtHstringIterator_get_Current, + ffWinrtHstringIterator_get_HasCurrent, + ffWinrtHstringIterator_MoveNext, + ffWinrtHstringIterator_GetMany, +}; + +} // namespace + +void ffDeleteHstring(HSTRING* value) { + if (*value) { + WindowsDeleteString(*value); + *value = nullptr; + } +} + +void ffStrbufSetHstring(FFstrbuf* destination, HSTRING value) { + uint32_t length = 0; + const wchar_t* raw = WindowsGetStringRawBuffer(value, &length); + ffStrbufSetNWS(destination, length, raw); +} + +HRESULT ffWinrtCreateHstringIterable(const wchar_t* const* items, uint32_t count, abi_t>** result) { + *result = nullptr; + + if (count > 0 && !items) { + return E_INVALIDARG; + } + + auto* iterable = (FFWinrtHstringIterable*) malloc(sizeof(FFWinrtHstringIterable)); + if (!iterable) { + return E_OUTOFMEMORY; + } + + iterable->lpVtbl = &ffWinrtHstringIterableVtbl; + iterable->refCount = 1; + ffListInitA(&iterable->strings, sizeof(HSTRING), count > 0 ? count : FF_LIST_DEFAULT_ALLOC); + + for (uint32_t i = 0; i < count; ++i) { + HSTRING* slot = (HSTRING*) ffListAdd(&iterable->strings, sizeof(HSTRING)); + HRESULT hr = WindowsCreateString(items[i], (UINT32) ::wcslen(items[i]), slot); + if (FAILED(hr)) { + iterable->lpVtbl->Release(iterable); + return hr; + } + } + + *result = reinterpret_cast(iterable); + return S_OK; +} + +#endif // FF_HAVE_WINRT diff --git a/src/common/windows/winrt.hpp b/src/common/windows/winrt.hpp new file mode 100644 index 000000000..46924f267 --- /dev/null +++ b/src/common/windows/winrt.hpp @@ -0,0 +1,121 @@ +#pragma once + +#ifdef __cplusplus + +extern "C" { + #include "common/FFlist.h" + #include "common/FFstrbuf.h" + #include "common/time.h" + #include "common/windows/com.h" + #include "common/windows/unicode.h" +} + + #if FF_HAVE_WINRT + + #include + #include + #include + + #include + #include + #include + +using winrt::impl::abi_t; + +// Frees an HSTRING and clears the pointer, so it can be used with [[gnu::cleanup]]. +void ffDeleteHstring(HSTRING* value); + +// Copies an HSTRING into an FFstrbuf, transcoding from UTF-16 to UTF-8. +void ffStrbufSetHstring(FFstrbuf* destination, HSTRING value); + +// Wraps `items` in an IVector and hands out its IIterable view, which is the +// parameter shape the FindAllAsync overloads taking additional properties expect. The caller owns +// the result. +HRESULT ffWinrtCreateHstringIterable(const wchar_t* const* items, uint32_t count, abi_t>** result); + +// RoGetActivationFactory() for a WinRT runtime class. +// +// `Projection` must be the *projection* type -- e.g. +// winrt::Windows::Devices.Bluetooth::IBluetoothLEDeviceStatics -- and never abi_t<...>: +// winrt::impl::guid_v is only specialized for projection types, and abi_t expands to +// impl::abi::type, which is a non-deduced context. +template +static inline HRESULT ffGetActivationFactory(const wchar_t* className, abi_t** factory) { + HSTRING_HEADER header; + HSTRING runtimeClass; + HRESULT hr = WindowsCreateStringReference(className, (UINT32) ::wcslen(className), &header, &runtimeClass); + if (FAILED(hr)) { + return hr; + } + + return RoGetActivationFactory(runtimeClass, winrt::guid_of(), reinterpret_cast(factory)); +} + +template +static inline HRESULT ffQueryInterface(SourceAbi* source, abi_t** target) { + return source->QueryInterface(winrt::guid_of(), reinterpret_cast(target)); +} + +// Spins until `operation` leaves AsyncStatus::Started, then hands its results to `result`. +template +static HRESULT ffWaitForAsyncOperation(TOperationAbi* operation, TResultAbi** result) { + FF_AUTO_RELEASE_COM_OBJECT IAsyncInfo* asyncInfo = nullptr; + HRESULT hr = ffQueryInterface(operation, &asyncInfo); + if (FAILED(hr)) { + return hr; + } + + AsyncStatus status = AsyncStatus::Started; + + for (;;) { + hr = asyncInfo->get_Status(&status); + if (FAILED(hr)) { + return hr; + } + if (status == AsyncStatus::Started) { + ffTimeSleep(0); + } else { + break; + } + } + + if (status != AsyncStatus::Completed) { + HRESULT errorCode = E_FAIL; + asyncInfo->get_ErrorCode(&errorCode); + return FAILED(errorCode) ? errorCode : E_FAIL; + } + + return operation->GetResults((void**) result); +} + +template +static HRESULT ffRunAndWait(TOperation&& operation, abi_t** result) { + FF_AUTO_RELEASE_COM_OBJECT abi_t>* opResult = nullptr; + HRESULT hr = operation(reinterpret_cast(&opResult)); + if (FAILED(hr) || !opResult) { + return hr; + } + + return ffWaitForAsyncOperation(opResult, result); +} + +template +static HRESULT ffRunAndWait2(TOperation&& operation, abi_t** result) { + *result = nullptr; + + FF_AUTO_RELEASE_COM_OBJECT abi_t>* opResult = nullptr; + HRESULT hr = operation(reinterpret_cast(&opResult)); + if (FAILED(hr) || !opResult) { + return hr; + } + + return ffWaitForAsyncOperation(opResult, result); +} + + #endif // FF_HAVE_WINRT + +#else + + #error Must be included in C++ source file + +#endif diff --git a/src/detection/media/media_windows.cpp b/src/detection/media/media_windows.cpp index 940fe9bef..3f24f3c8b 100644 --- a/src/detection/media/media_windows.cpp +++ b/src/detection/media/media_windows.cpp @@ -6,10 +6,9 @@ extern "C" { } #if FF_HAVE_WINRT - #include + #include "common/windows/winrt.hpp" + #include - #include - #include #include #include @@ -21,94 +20,6 @@ extern "C" { #include #include -using winrt::impl::abi_t; -using winrt::Windows::Foundation::IAsyncOperation; -using winrt::Windows::Foundation::IAsyncOperationWithProgress; - -static inline void deleteHstring(HSTRING* pstr) { - if (*pstr) { - WindowsDeleteString(*pstr); - } -} - -static inline void ffStrbufSetHstring(FFstrbuf* destination, HSTRING value) { - uint32_t length; - const wchar_t* raw = WindowsGetStringRawBuffer(value, &length); - ffStrbufSetNWS(destination, length, raw); -} - -template -static inline HRESULT ffGetActivationFactory(const wchar_t* className, REFIID iid, Interface** factory) { - HSTRING_HEADER header; - HSTRING runtimeClass; - HRESULT hr = WindowsCreateStringReference(className, (UINT32)::wcslen(className), &header, &runtimeClass); - if (FAILED(hr)) { - return hr; - } - - return RoGetActivationFactory(runtimeClass, iid, reinterpret_cast(factory)); -} - -template -static inline HRESULT ffQueryInterface(SourceAbi* source, abi_t** target) { - return source->QueryInterface(winrt::guid_of(), reinterpret_cast(target)); -} - -template -static HRESULT ffWaitForAsyncOperation(TOperationAbi* operation, TResultAbi** result) { - FF_AUTO_RELEASE_COM_OBJECT IAsyncInfo* asyncInfo = nullptr; - HRESULT hr = ffQueryInterface(operation, &asyncInfo); - if (FAILED(hr)) { - return hr; - } - - AsyncStatus status = AsyncStatus::Started; - - for (;;) { - hr = asyncInfo->get_Status(&status); - if (FAILED(hr)) { - return hr; - } - if (status == AsyncStatus::Started) { - ffTimeSleep(0); - } else { - break; - } - } - - if (status != AsyncStatus::Completed) { - HRESULT errorCode = E_FAIL; - asyncInfo->get_ErrorCode(&errorCode); - return FAILED(errorCode) ? errorCode : E_FAIL; - } - - return operation->GetResults((void**) result); -} - -template -static HRESULT ffRunAndWait(TOperation&& operation, abi_t** result) { - FF_AUTO_RELEASE_COM_OBJECT abi_t>* opResult = nullptr; - HRESULT hr = operation(reinterpret_cast(&opResult)); - if (FAILED(hr) || !opResult) { - return hr; - } - - return ffWaitForAsyncOperation(opResult, result); -} - -template -static HRESULT ffRunAndWait2(TOperation&& operation, abi_t** result) { - *result = nullptr; - - FF_AUTO_RELEASE_COM_OBJECT abi_t>* opResult = nullptr; - HRESULT hr = operation(reinterpret_cast(&opResult)); - if (FAILED(hr) || !opResult) { - return hr; - } - - return ffWaitForAsyncOperation(opResult, result); -} - static HRESULT ffSaveThumbnailToTempPath( abi_t* thumbnail, FFstrbuf* destination) { @@ -138,7 +49,7 @@ static HRESULT ffSaveThumbnailToTempPath( } FF_AUTO_RELEASE_COM_OBJECT abi_t* bufferFactory = nullptr; - hr = ffGetActivationFactory(L"Windows.Storage.Streams.Buffer", winrt::guid_of(), &bufferFactory); + hr = ffGetActivationFactory(L"Windows.Storage.Streams.Buffer", &bufferFactory); if (FAILED(hr)) { return hr; } @@ -223,7 +134,7 @@ static HRESULT ffSaveThumbnailToTempPath( // are actually packaged -- the caller gates it, see `resolveAppUserModelId`. static bool resolvePackagedAppUserModelId(const wchar_t* aumid, FFstrbuf* result) { FF_AUTO_RELEASE_COM_OBJECT abi_t* statics = nullptr; - if (FAILED(ffGetActivationFactory(L"Windows.ApplicationModel.AppInfo", winrt::guid_of(), &statics)) || !statics) { + if (FAILED(ffGetActivationFactory(L"Windows.ApplicationModel.AppInfo", &statics)) || !statics) { return false; } @@ -243,7 +154,7 @@ static bool resolvePackagedAppUserModelId(const wchar_t* aumid, FFstrbuf* result return false; } - [[gnu::cleanup(deleteHstring)]] HSTRING displayName = nullptr; + [[gnu::cleanup(ffDeleteHstring)]] HSTRING displayName = nullptr; if (FAILED(displayInfo->get_DisplayName(reinterpret_cast(&displayName))) || !displayName) { return false; } @@ -316,7 +227,7 @@ static const char* getMedia(FFMediaResult* result, bool saveCover) { do { FF_AUTO_RELEASE_COM_OBJECT abi_t* managerStatics = nullptr; - HRESULT hr = ffGetActivationFactory(L"Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager", winrt::guid_of(), &managerStatics); + HRESULT hr = ffGetActivationFactory(L"Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager", &managerStatics); if (FAILED(hr) || !managerStatics) { error = "winrt: RoGetActivationFactory(GlobalSystemMediaTransportControlsSessionManager) failed"; break; @@ -332,7 +243,7 @@ static const char* getMedia(FFMediaResult* result, bool saveCover) { break; } - [[gnu::cleanup(deleteHstring)]] HSTRING playerId = nullptr; + [[gnu::cleanup(ffDeleteHstring)]] HSTRING playerId = nullptr; FF_AUTO_RELEASE_COM_OBJECT abi_t* session = nullptr; if (instance.config.general.playerName.length) { @@ -367,7 +278,7 @@ static const char* getMedia(FFMediaResult* result, bool saveCover) { currentSession = nullptr; // Don't release the session object break; } - deleteHstring(&playerId); + ffDeleteHstring(&playerId); ffStrbufClear(&result->playerId); } @@ -433,17 +344,17 @@ static const char* getMedia(FFMediaResult* result, bool saveCover) { } } - [[gnu::cleanup(deleteHstring)]] HSTRING title = nullptr; + [[gnu::cleanup(ffDeleteHstring)]] HSTRING title = nullptr; if (SUCCEEDED(mediaProps->get_Title(reinterpret_cast(&title)))) { ffStrbufSetHstring(&result->song, title); } - [[gnu::cleanup(deleteHstring)]] HSTRING artist = nullptr; + [[gnu::cleanup(ffDeleteHstring)]] HSTRING artist = nullptr; if (SUCCEEDED(mediaProps->get_Artist(reinterpret_cast(&artist)))) { ffStrbufSetHstring(&result->artist, artist); } - [[gnu::cleanup(deleteHstring)]] HSTRING album = nullptr; + [[gnu::cleanup(ffDeleteHstring)]] HSTRING album = nullptr; if (SUCCEEDED(mediaProps->get_AlbumTitle(reinterpret_cast(&album)))) { ffStrbufSetHstring(&result->album, album); }