Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 104 additions & 3 deletions dev/MRTCore/mrt/Core/src/MRM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,10 +409,15 @@ static HRESULT LoadStringOrEmbeddedResource(
_Outptr_opt_result_maybenull_ PWSTR* resourceName,
_Out_opt_ UINT32* qualifierCount,
_Outptr_opt_result_buffer_(*qualifierCount) PWSTR** qualifierNames,
_Outptr_opt_result_buffer_(*qualifierCount) PWSTR** qualifierValues)
_Outptr_opt_result_buffer_(*qualifierCount) PWSTR** qualifierValues,
_Out_opt_ bool* isView = nullptr)
{
data->data = nullptr;
data->size = 0;
if (isView != nullptr)
{
*isView = false;
}

ResourceCandidateResult candidate;
PWSTR localName = nullptr;
Expand Down Expand Up @@ -441,8 +446,26 @@ static HRESULT LoadStringOrEmbeddedResource(
return E_UNEXPECTED;
}

// This ensures the blob result holds a copy of the data we can return to the caller, not a pointer to the PRI file.
RETURN_IF_FAILED(BlobResultReleaseOwnershipBuffer(blobResult, &data->data, &data->size));
// When the caller opted into a no-copy load AND the blob is a reference directly into the
// memory-mapped PRI (rather than an owned buffer produced by e.g. a decompressing reader),
// publish that pointer as a non-owning view. The pages are read-only and remain valid for
// the lifetime of the resource manager, which the caller is required to keep alive. If the
// blob is instead owned by the BlobResult, we must fall back to the copy path or the pointer
// would dangle once the local BlobResult is destroyed.
if ((isView != nullptr) && (blobResult.GetType() == DefResultType_Reference))
{
size_t referenceSizeInBytes = 0;
const void* reference = blobResult.GetRef(&referenceSizeInBytes);
RETURN_HR_IF_NULL(E_UNEXPECTED, reference);
data->data = const_cast<void*>(reference);
data->size = static_cast<UINT32>(referenceSizeInBytes);
*isView = true;
}
else
{
// This ensures the blob result holds a copy of the data we can return to the caller, not a pointer to the PRI file.
RETURN_IF_FAILED(BlobResultReleaseOwnershipBuffer(blobResult, &data->data, &data->size));
}

*resourceString = nullptr;
*resourceType = MrmType_Embedded;
Expand Down Expand Up @@ -941,6 +964,63 @@ STDAPI MrmLoadStringOrEmbeddedResourceByIndexWithQualifierValues(

STDAPI_(void*) MrmAllocateBuffer(size_t size) { return Def_Alloc(size); }

STDAPI MrmLoadStringOrEmbeddedResourceNoCopy(
_In_ MrmManagerHandle resourceManager,
_In_opt_ MrmContextHandle resourceContext,
_In_opt_ MrmMapHandle resourceMap,
_In_ PCWSTR resourceId,
_Out_ MrmType* resourceType,
_Outptr_result_maybenull_ PWSTR* resourceString,
_Out_ MrmResourceData2* data)
{
data->data = nullptr;
data->size = 0;
data->isView = FALSE;

MrmResourceData base{};
bool isView = false;
if (IsResourceUri(resourceId))
{
RETURN_IF_FAILED_WITH_EXPECTED(LoadStringOrEmbeddedResource(
resourceManager, resourceContext, nullptr, INDEX_RESOURCE_URI, resourceId, resourceType, resourceString, &base, nullptr, nullptr, nullptr, nullptr, &isView),
HRESULT_FROM_WIN32(ERROR_MRM_NAMED_RESOURCE_NOT_FOUND));
}
else
{
RETURN_IF_FAILED_WITH_EXPECTED(LoadStringOrEmbeddedResource(
resourceManager, resourceContext, resourceMap, INDEX_RESOURCE_ID, resourceId, resourceType, resourceString, &base, nullptr, nullptr, nullptr, nullptr, &isView),
HRESULT_FROM_WIN32(ERROR_MRM_NAMED_RESOURCE_NOT_FOUND));
}
data->size = base.size;
data->data = base.data;
data->isView = isView ? TRUE : FALSE;
return S_OK;
}

STDAPI MrmLoadStringOrEmbeddedResourceByIndexNoCopy(
_In_ MrmManagerHandle resourceManager,
_In_opt_ MrmContextHandle resourceContext,
_In_opt_ MrmMapHandle resourceMap,
UINT32 index,
_Out_ MrmType* resourceType,
_Outptr_ PWSTR* resourceName,
_Outptr_result_maybenull_ PWSTR* resourceString,
_Out_ MrmResourceData2* data)
{
data->data = nullptr;
data->size = 0;
data->isView = FALSE;

MrmResourceData base{};
bool isView = false;
RETURN_IF_FAILED(LoadStringOrEmbeddedResource(
resourceManager, resourceContext, resourceMap, index, nullptr, resourceType, resourceString, &base, resourceName, nullptr, nullptr, nullptr, &isView));
data->size = base.size;
data->data = base.data;
data->isView = isView ? TRUE : FALSE;
return S_OK;
}

STDAPI_(void) MrmFreeResource(_In_opt_ void* resource)
{
if (resource != nullptr)
Expand All @@ -951,6 +1031,27 @@ STDAPI_(void) MrmFreeResource(_In_opt_ void* resource)
return;
}

STDAPI_(void) MrmFreeResourceData(_Inout_opt_ MrmResourceData2* data)
{
if (data == nullptr)
{
return;
}

// Views borrow memory owned by the resource manager's PRI mapping; nothing to free. Only owned
// buffers were allocated by the loader and must be released.
if (!data->isView && (data->data != nullptr))
{
Def_Free(data->data);
}

data->data = nullptr;
data->size = 0;
data->isView = FALSE;

return;
}

// The base directory is communicated via the MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY process
// environment variable, which is inherited by child processes spawned via CreateProcess. Its owner
// also stamps its process id in MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY_PID. Honor the base
Expand Down
3 changes: 3 additions & 0 deletions dev/MRTCore/mrt/Core/src/MRM.def
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ EXPORTS
MrmLoadStringOrEmbeddedFromResourceUri
MrmLoadStringOrEmbeddedResourceByIndex
MrmLoadStringOrEmbeddedResourceByIndexWithQualifierValues
MrmLoadStringOrEmbeddedResourceNoCopy
MrmLoadStringOrEmbeddedResourceByIndexNoCopy
MrmAllocateBuffer
MrmFreeResource
MrmFreeResourceData
MrmGetFilePathFromName
46 changes: 46 additions & 0 deletions dev/MRTCore/mrt/Core/src/MRM.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ extern "C"
void* data;
};

// Versioned extension of MrmResourceData used by the *NoCopy loader variants. MrmResourceData
// itself is a frozen public ABI and must never grow, so the additional 'isView' state lives on
// this separate struct. The leading 'size'/'data' members intentionally mirror MrmResourceData
// in the same order so the two remain layout-compatible. Only the *NoCopy exports (and
// MrmFreeResourceData) read or write this type, so no legacy caller ever sees the larger size.
struct MrmResourceData2
{
UINT32 size;
void* data;

// When TRUE, 'data' is a non-owning view directly into the memory-mapped, read-only PRI
// owned by the resource manager. In that case the caller must keep the resource manager
// alive for as long as 'data' is used, and must NOT free 'data' (MrmFreeResourceData is a
// no-op for views). When FALSE (the default for zero-initialized instances) 'data' is a heap
// allocation owned by the caller, freed via MrmFreeResourceData.
BOOL isView;
};

STDAPI MrmCreateResourceManager(_In_ PCWSTR priFileName, _Out_ MrmManagerHandle* resourceManager);
STDAPI_(void) MrmDestroyResourceManager(_In_opt_ MrmManagerHandle resourceManager);

Expand Down Expand Up @@ -123,9 +141,37 @@ extern "C"
_Outptr_result_buffer_(*qualifierCount) PWSTR** qualifierNames,
_Outptr_result_buffer_(*qualifierCount) PWSTR** qualifierValues);

// No-copy loader variants. For embedded/binary resources these publish a non-owning view
// (MrmResourceData2::isView == TRUE) directly into the memory-mapped PRI instead of allocating
// and copying a private heap buffer. The caller must keep 'resourceManager' alive while the
// returned view is in use and must free the result with MrmFreeResourceData (which is a no-op
// for views). String/path resources are unaffected and behave exactly as the copying variants.
STDAPI MrmLoadStringOrEmbeddedResourceNoCopy(
_In_ MrmManagerHandle resourceManager,
_In_opt_ MrmContextHandle resourceContext,
_In_opt_ MrmMapHandle resourceMap,
_In_ PCWSTR resourceId,
_Out_ MrmType* resourceType,
_Outptr_result_maybenull_ PWSTR* resourceString,
_Out_ MrmResourceData2* data);

STDAPI MrmLoadStringOrEmbeddedResourceByIndexNoCopy(
_In_ MrmManagerHandle resourceManager,
_In_opt_ MrmContextHandle resourceContext,
_In_opt_ MrmMapHandle resourceMap,
UINT32 index,
_Out_ MrmType* resourceType,
_Outptr_ PWSTR* resourceName,
_Outptr_result_maybenull_ PWSTR* resourceString,
_Out_ MrmResourceData2* data);

STDAPI_(void*) MrmAllocateBuffer(size_t size);
STDAPI_(void) MrmFreeResource(_In_opt_ void* resource);

// Frees an MrmResourceData2 produced by a *NoCopy loader. Safe for both owned buffers (frees
// 'data') and views (no-op). Must be used to free results of the *NoCopy variants.
STDAPI_(void) MrmFreeResourceData(_Inout_opt_ MrmResourceData2* data);

STDAPI MrmGetFilePathFromName(_In_opt_ PCWSTR filename, _Outptr_ PWSTR* filePath);

#ifdef __cplusplus
Expand Down
147 changes: 147 additions & 0 deletions dev/MRTCore/mrt/Core/unittests/MrmTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,153 @@ class BasicTest
MrmDestroyResourceManager(resourceManager);
}

TEST_METHOD(ReadStringOrEmbeddedResourceNoCopyReturnsView)
{
MrmManagerHandle resourceManager;
VERIFY_ARE_EQUAL(MrmCreateResourceManager(L".\\resources.pri", &resourceManager), S_OK);

// The no-copy variant should return a non-owning view directly into the memory-mapped PRI,
// whose bytes are identical to those returned by the copying variant.
MrmType copyType;
wchar_t* copyString;
MrmResourceData copyData {};
VERIFY_ARE_EQUAL(MrmLoadStringOrEmbeddedResource(resourceManager, nullptr, nullptr, L"Files/Controls/AlbumBasicInfoControl.xbf", &copyType, &copyString, &copyData), S_OK);
VERIFY_IS_TRUE(copyType == MrmType_Embedded);
VERIFY_IS_NOT_NULL(copyData.data);
VERIFY_ARE_EQUAL(copyData.size, 15002u);

MrmType viewType;
wchar_t* viewString;
MrmResourceData2 viewData {};
VERIFY_ARE_EQUAL(MrmLoadStringOrEmbeddedResourceNoCopy(resourceManager, nullptr, nullptr, L"Files/Controls/AlbumBasicInfoControl.xbf", &viewType, &viewString, &viewData), S_OK);
VERIFY_IS_NULL(viewString);
VERIFY_IS_TRUE(viewType == MrmType_Embedded);
VERIFY_IS_TRUE(viewData.isView);
VERIFY_IS_NOT_NULL(viewData.data);
VERIFY_ARE_EQUAL(viewData.size, copyData.size);

// A view must not point at the freshly allocated copy - it references the mapped file.
VERIFY_ARE_NOT_EQUAL(viewData.data, copyData.data);
VERIFY_ARE_EQUAL(0, memcmp(viewData.data, copyData.data, viewData.size));

// Freeing a view is a no-op that clears the descriptor; freeing the copy releases the buffer.
MrmFreeResourceData(&viewData);
VERIFY_IS_NULL(viewData.data);
VERIFY_ARE_EQUAL(viewData.size, 0u);
VERIFY_IS_FALSE(viewData.isView);

MrmFreeResource(copyData.data);

MrmDestroyResourceManager(resourceManager);
}

TEST_METHOD(ReadStringOrEmbeddedResourceNoCopyStringIsNotAView)
{
MrmManagerHandle resourceManager;
VERIFY_ARE_EQUAL(MrmCreateResourceManager(L".\\resources.pri", &resourceManager), S_OK);

// String resources are unaffected by the no-copy path: no blob, not a view.
MrmType resourceType;
wchar_t* resourceString;
MrmResourceData2 resourceData {};
VERIFY_ARE_EQUAL(MrmLoadStringOrEmbeddedResourceNoCopy(resourceManager, nullptr, nullptr, L"resources/IDS_MANIFEST_MUSIC_APP_NAME", &resourceType, &resourceString, &resourceData), S_OK);

VERIFY_IS_NOT_NULL(resourceString);
VERIFY_IS_TRUE(resourceType == MrmType_String);
VERIFY_IS_FALSE(resourceData.isView);
VERIFY_IS_NULL(resourceData.data);
VERIFY_ARE_EQUAL(resourceData.size, 0u);
VerifyStringEqual(resourceString, L"Groove Music");

MrmFreeResource(resourceString);
MrmDestroyResourceManager(resourceManager);
}

TEST_METHOD(ReadStringOrEmbeddedResourceByIndexNoCopyReturnsView)
{
MrmManagerHandle resourceManager;
VERIFY_ARE_EQUAL(MrmCreateResourceManager(L".\\resources.pri", &resourceManager), S_OK);

MrmMapHandle resourceMap;
VERIFY_ARE_EQUAL(MrmGetChildResourceMap(resourceManager, nullptr, L"Files", &resourceMap), S_OK);

UINT32 resourceCount = 0;
VERIFY_ARE_EQUAL(MrmGetResourceCount(resourceManager, resourceMap, &resourceCount), S_OK);
VERIFY_IS_TRUE(resourceCount > 0);

// Walk the map by index and, for the first embedded resource, verify the no-copy variant
// yields a view whose bytes match the copying variant.
bool verifiedEmbedded = false;
for (UINT32 index = 0; index < resourceCount && !verifiedEmbedded; index++)
{
MrmType viewType;
wchar_t* viewName = nullptr;
wchar_t* viewString = nullptr;
MrmResourceData2 viewData {};
VERIFY_ARE_EQUAL(MrmLoadStringOrEmbeddedResourceByIndexNoCopy(resourceManager, nullptr, resourceMap, index, &viewType, &viewName, &viewString, &viewData), S_OK);

if (viewType == MrmType_Embedded)
{
VERIFY_IS_TRUE(viewData.isView);
VERIFY_IS_NOT_NULL(viewData.data);
VERIFY_IS_TRUE(viewData.size > 0);

MrmType copyType;
wchar_t* copyName = nullptr;
wchar_t* copyString = nullptr;
MrmResourceData copyData {};
VERIFY_ARE_EQUAL(MrmLoadStringOrEmbeddedResourceByIndex(resourceManager, nullptr, resourceMap, index, &copyType, &copyName, &copyString, &copyData), S_OK);
VERIFY_IS_TRUE(copyType == MrmType_Embedded);
VERIFY_ARE_EQUAL(viewData.size, copyData.size);
VERIFY_ARE_EQUAL(0, memcmp(viewData.data, copyData.data, viewData.size));

MrmFreeResource(copyData.data);
MrmFreeResource(copyName);
verifiedEmbedded = true;
}

MrmFreeResourceData(&viewData);
MrmFreeResource(viewName);
}

VERIFY_IS_TRUE(verifiedEmbedded);

MrmDestroyResourceManager(resourceManager);
}

TEST_METHOD(MrmFreeResourceDataFreesOwnedBuffer)
{
// MrmFreeResourceData must release an owned (non-view) buffer and reset the descriptor
// without crashing. Build an owned descriptor from the matched allocator so the test is
// deterministic regardless of which resources happen to be stored as references.
MrmResourceData2 resourceData {};
resourceData.size = 64;
resourceData.data = MrmAllocateBuffer(resourceData.size);
resourceData.isView = FALSE;
VERIFY_IS_NOT_NULL(resourceData.data);

MrmFreeResourceData(&resourceData);
VERIFY_IS_NULL(resourceData.data);
VERIFY_ARE_EQUAL(resourceData.size, 0u);
VERIFY_IS_FALSE(resourceData.isView);

// Freeing a view descriptor must NOT free the borrowed memory, only clear the descriptor.
BYTE borrowed[8] = {};
MrmResourceData2 viewData {};
viewData.size = sizeof(borrowed);
viewData.data = borrowed;
viewData.isView = TRUE;

MrmFreeResourceData(&viewData);
VERIFY_IS_NULL(viewData.data);
VERIFY_ARE_EQUAL(viewData.size, 0u);
VERIFY_IS_FALSE(viewData.isView);

// Calling again on a cleared descriptor, and on nullptr, is safe.
MrmFreeResourceData(&resourceData);
MrmFreeResourceData(nullptr);
}

TEST_METHOD(ReadStringOrEmbeddedResourceWithQualifierOverride)
{
MrmManagerHandle resourceManager;
Expand Down
Loading