{"id":109322,"date":"2024-01-26T07:00:00","date_gmt":"2024-01-26T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109322"},"modified":"2024-01-26T08:06:05","modified_gmt":"2024-01-26T16:06:05","slug":"20240126-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240126-00\/?p=109322","title":{"rendered":"How can I expose a pre-existing block of memory as a Windows Runtime object without copying the data?"},"content":{"rendered":"<p>Let&#8217;s implement an <code>IMemoryBuffer<\/code>. The basic idea is that the <code>IMemoryBuffer<\/code> object controls the memory, and it grants access to the memory by handing out objects which implement <code>IMemoryBufferReference<\/code>.<\/p>\n<p>Here&#8217;s an implementation using C++\/WinRT. We start with the <code>Memory\u00adLifetime<\/code>.<\/p>\n<pre>namespace winrt\r\n{\r\n    using namespace winrt::Windows::Foundation;\r\n}\r\n\r\nstruct MemoryLifetime\r\n{\r\n    using MemoryCleanupHandler = winrt::DeferralCompletedHandler;\r\n\r\n    MemoryLifetime(\r\n        winrt::array_view&lt;uint8_t&gt; view,\r\n        MemoryCleanupHandler const&amp; cleanup)\r\n    : m_view(view)\r\n    {\r\n        m_cleanup.add(cleanup);\r\n    }\r\n\r\n    ~MemoryLifetime()\r\n    {\r\n        m_cleanup();\r\n    }\r\n\r\n    \/\/ Not copyable, not assignable.\r\n    MemoryLifetime&amp; operator=(MemoryLifetime const&amp;) = delete;\r\n    MemoryLifetime(MemoryLifetime const&amp;) = delete;\r\n\r\n    winrt::array_view&lt;uint8_t&gt; m_view;\r\n    winrt::event&lt;MemoryCleanupHandler&gt; m_cleanup;\r\n};\r\n<\/pre>\n<p>The <code>Memory\u00adLifetime<\/code> object represents a block of memory that is cleaned up at destruction by the provided <code>Memory\u00adCleanup\u00adHandler<\/code> delegate. All not-yet-closed <code>IMemoryBuffer<\/code> and <code>IMemory\u00adBuffer\u00adReference<\/code> objects retain a strong reference to the <code>Memory\u00adLifetime<\/code>.<\/p>\n<p>I pull a couple of sneaky tricks in dealing with the delegate. First, I reuse the <code>Deferral\u00adCompleted\u00adHandler<\/code>, since it is a delegate for <code>void()<\/code>, which is what we want too.<\/p>\n<p>Second, I store the delegate in a <code>winrt::event<\/code> rather than as a delegate directly. I&#8217;m taking advantage of a few features of <code>winrt::event<\/code>:<\/p>\n<ul>\n<li>It detects delegates which are not agile and puts them in an agile wrapper so that we can raise the event from any thread.<\/li>\n<li>It catches exceptions that are thrown from the delegate, which is good because any uncaught exception in a destructor terminates the process because destructors default to <code>noexcept<\/code>.<\/li>\n<\/ul>\n<p>The <code>Memory\u00adLifetime<\/code> is kept in a <code>shared_ptr<\/code>. This next class helps us manage that pointer.<\/p>\n<pre>struct MemoryLifetimeTracker\r\n{\r\n    MemoryLifetimeTracker(std::shared_ptr&lt;MemoryLifetime&gt; lifetime)\r\n        : m_lifetime(std::move(lifetime)) {}\r\n\r\n    std::shared_ptr&lt;MemoryLifetime&gt; Lifetime()\r\n    {\r\n        auto lock = winrt::slim_shared_lock_guard(m_srwlock);\r\n        return m_lifetime;\r\n    }\r\n\r\n    winrt::array_view&lt;uint8_t&gt; GetView()\r\n    {\r\n        auto lock = winrt::slim_shared_lock_guard(m_srwlock);\r\n        return m_lifetime ? m_lifetime-&gt;m_view\r\n                          : winrt::array_view&lt;uint8_t&gt;{};\r\n    }\r\n\r\n    \/\/ For IMemoryBufferByteAccess\r\n    HRESULT GetBuffer(uint8_t** buffer, uint32_t* size) noexcept\r\n    {\r\n        auto view = GetView();\r\n        *buffer = view.data();\r\n        *size = view.size();\r\n        return S_OK;\r\n    }\r\n\r\n    std::shared_ptr&lt;MemoryLifetime&gt; Reset()\r\n    {\r\n        auto lock = winrt::slim_lock_guard(m_srwlock);\r\n        return std::exchange(m_lifetime, {});\r\n    }\r\n\r\nprivate:\r\n    winrt::slim_mutex m_srwlock;\r\n    std::shared_ptr&lt;MemoryLifetime&gt; m_lifetime;\r\n};\r\n<\/pre>\n<p>The code in <code>Reset()<\/code> to clean up the <code>m_lifetime<\/code> is tricky because we must hold the lock in order to access <code>m_lifetime<\/code>, but we don&#8217;t want <code>Memory\u00adLifetime<\/code>&#8216;s destructor to run from inside the lock, because we don&#8217;t know what sorts of shenanigans the cleanup delegate will get up to, and we don&#8217;t want to hold the lock across what could be a very long and dangerous function. So we exchange the shared pointer while under the lock, and then return it. The caller will then allow the shared pointer to destruct, outside the lock. (It&#8217;s okay for the <code>Memory\u00adLifetime\u00adTracker<\/code> to destroy the shared pointer without a lock. There are no conflicting threads at that point.)<\/p>\n<p>The next piece is the <code>IMemory\u00adBuffer\u00adReference<\/code>. This is the most complicated part.<\/p>\n<pre>struct CustomMemoryBufferReference :\r\n    winrt::implements&lt;\r\n        CustomMemoryBufferReference,\r\n        winrt::IMemoryBufferReference,\r\n        winrt::IClosable,\r\n        ::Windows::Foundation::IMemoryBufferByteAccess&gt;\r\n{\r\n    using ClosedEventHandler = winrt::TypedEventHandler&lt;\r\n        winrt::IMemoryBufferReference, winrt::IInspectable&gt;;\r\n\r\n    static_assert(<!-- backref: How do I prevent my C++\/WinRT implementation class from participating in COM aggregation? -->!outer(), \"Must not be composable.\");\r\n\r\n    CustomMemoryBufferReference(\r\n        std::shared_ptr&lt;MemoryLifetime&gt; const&amp; lifetime)\r\n    : m_tracker(lifetime)\r\n    {\r\n        NonDelegatingAddRef();\r\n    }\r\n\r\n    uint32_t Capacity()\r\n    {\r\n        return m_tracker.GetView().size();\r\n    }\r\n\r\n    STDMETHOD(GetBuffer)(uint8_t** buffer, uint32_t* size)\r\n        noexcept override\r\n    {\r\n        return m_tracker.GetBuffer(buffer, size);\r\n    }\r\n\r\n    decltype(std::declval&lt;implements&gt;().Release())\r\n        __stdcall Release() noexcept override\r\n    {\r\n        auto count = NonDelegatingRelease();\r\n        if (count == 1)\r\n        {\r\n            count = Close(count);\r\n        }\r\n        return count;\r\n    }\r\n\r\n    winrt::event_token Closed(ClosedEventHandler const&amp; handler)\r\n    {\r\n        return m_closed.add(handler);\r\n    }\r\n\r\n    void Closed(winrt::event_token token)\r\n    {\r\n        m_closed.remove(token);\r\n    }\r\n\r\n    uint32_t Close(uint32_t count = 0)\r\n    {\r\n        if (!m_notified.exchange(true, std::memory_order_relaxed))\r\n        {\r\n            m_closed(*this, nullptr);\r\n            m_tracker.Reset();\r\n            count = NonDelegatingRelease();\r\n        }\r\n        return count;\r\n    }\r\n\r\n    MemoryLifetimeTracker m_tracker;\r\n    std::atomic&lt;bool&gt; m_notified;\r\n    winrt::event&lt;ClosedEventHandler&gt; m_closed;\r\n};\r\n<\/pre>\n<p>The <code>Custom\u00adMemory\u00adBuffer\u00adReference<\/code> is constructed with a shared pointer to a <code>Memory\u00adLifetime<\/code> that gives us access to the underlying memory.<\/p>\n<p>We follow the general pattern of <!-- backref: How can I give away a COM reference just before my object destructs? --> giving away a COM reference just before the object destructs, but since the cleanup can also be explicitly triggered via <code>Close()<\/code>, we put the &#8220;notified&#8221; flag in the <code>Close()<\/code> method.<\/p>\n<p>If we call <code>Close()<\/code> as part of the final application-visible <code>Release()<\/code>, we want to return the revised reference count so that it&#8217;s easier to debug the application by observing the return value of <code>Release()<\/code> to figure out whether that was the final <code>Release()<\/code>. We pass the original reference count as a parameter, and if the <code>Close()<\/code> method raises the <code>Closed<\/code> event, then it returns the revised reference count.<\/p>\n<p>if the <code>Close()<\/code> method is called via the projection, it is done with no parameters, so the count parameter defaults to zero. Furthermore, the projected <code>Close()<\/code> is void, so our <code>uint32_t<\/code> return value is ignored. (We are <a title=\"Using CRTP to your advantage: Simplifying overloaded Windows Runtime method projections in C++\/WinRT\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20211208-00\/?p=106012\"> taking advantage of C++\/WinRT&#8217;s use of CRTP<\/a>.)<\/p>\n<p>The last piece is the <code>Custom\u00adMemory\u00adBuffer<\/code>.<\/p>\n<pre>struct CustomMemoryBuffer :\r\n    winrt::implements&lt;\r\n        CustomMemoryBuffer,\r\n        winrt::IMemoryBuffer,\r\n        winrt::cloaked&lt;winrt::IMemoryBufferByteAccess&gt;,\r\n        winrt::IClosable&gt;\r\n{\r\n    using MemoryCleanupHandler = winrt::DeferralCompletedHandler;\r\n\r\n    CustomMemoryBuffer(\r\n        winrt::array_view&lt;uint8_t&gt; view,\r\n        MemoryCleanupHandler const&amp; cleanup)\r\n    : m_lifetime(std::make_shared&lt;MemoryLifetime&gt;(view, cleanup))\r\n    {\r\n    }\r\n\r\n    \/\/ IMemoryBuffer\r\n    winrt::IMemoryBufferReference CreateReference()\r\n    {\r\n        return winrt::make&lt;CustomMemoryBufferReference&gt;(\r\n            m_tracker.Lifetime());\r\n    }\r\n\r\n    \/\/ IMemoryBufferByteAccess\r\n    STDMETHOD(GetBuffer)(uint8_t** buffer, uint32_t* size)\r\n        noexcept override\r\n    {\r\n        return m_tracker.GetBuffer(buffer, size);\r\n    }\r\n\r\n    \/\/ IClosable\r\n    void Close() { m_tracker.Reset(); }\r\n\r\n    MemoryLifetimeTracker m_tracker;\r\n};\r\n\r\ntemplate&lt;typename T&gt;\r\nwinrt::IMemoryBuffer CreateCustomMemoryBuffer(\r\n    winrt::array_view&lt;T&gt; view,\r\n    winrt::DeferralCompletedHandler const&amp; cleanup)\r\n{\r\n    auto byte_view = winrt::array_view(\r\n        reinterpret_cast&lt;uint8_t*&gt;(view.data()),\r\n        view.size() \/ sizeof(T));\r\n    return winrt::make&lt;CustomMemoryBuffer&gt;(byte_view, cleanup);\r\n}\r\n\r\ninline winrt::IMemoryBuffer CreateCustomMemoryBuffer(\r\n    void* buffer, uint32_t size,\r\n    winrt::DeferralCompletedHandler const&amp; cleanup)\r\n{\r\n    return CreateCustomMemoryBuffer(\r\n        { reinterpret_cast&lt;uint8_t*&gt;(buffer), size },\r\n        cleanup);\r\n}\r\n<\/pre>\n<p>The <code>Custom\u00adMemory\u00adBuffer<\/code> is our implementation of <code>IMemoryBuffer<\/code>. You create it from an <code>array_view<\/code> and a handler that is called when all outstanding references have been released or closed. We also provide a convenience overload for <code>void*<\/code> buffers.<\/p>\n<p>Here&#8217;s an example usage of our implementation:<\/p>\n<pre>winrt::IMemoryBuffer\r\n    CreateSharedMemoryBuffer(uint32_t size)\r\n{\r\n    winrt::handle mapping =\r\n        winrt::check_pointer(\r\n            CreateFileMappingW(INVALID_HANDLE_VALUE,\r\n                nullptr, PAGE_READWRITE, 0, size, nullptr)) };\r\n    auto view = winrt::check_pointer(\r\n        MapViewOfFile(mapping.get(), FILE_MAP_WRITE, 0, 0, size));\r\n    return CreateCustomMemoryBuffer(view, size, [view]\r\n        {\r\n            winrt::check_bool(UnmapViewOfFile(view));\r\n        });\r\n}\r\n<\/pre>\n<p>We create and map an unnamed file mapping and create a <code>Custom\u00adMemory\u00adBuffer<\/code> around that block of memory, with a cleanup delegate that unmaps the view.<\/p>\n<p>We&#8217;ll come back to this helper class later after we look at some other implementations.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Assembling all the pieces.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-109322","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Assembling all the pieces.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109322","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=109322"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109322\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=109322"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109322"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109322"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}