Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MRTCore will also search for [modulename].pri for unpackaged app #814

Merged
merged 5 commits into from
May 13, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions build/build-mrt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ steps:
!**\*TestAdapter.dll
!**\TE.*.dll
!**\obj\**
!**\MrtCoreUnpackagedTests.dll
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!

searchFolder: '${{ parameters.MRTBinariesDirectory }}\Release\$(buildPlatform)'
testRunTitle: 'test MRT $(buildPlatform)'
platform: '$(buildPlatform)'
Expand Down
72 changes: 52 additions & 20 deletions dev/MRTCore/mrt/Core/src/MRM.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

#include <Windows.h>

#include <Pathcch.h>
#include "mrm/BaseInternal.h"
#include "mrm/common/platform.h"
#include "mrm/readers/MrmReaders.h"
Expand All @@ -25,6 +25,7 @@ typedef struct

constexpr wchar_t ResourceUriPrefix[] = L"ms-resource://";
constexpr int ResourceUriPrefixLength = ARRAYSIZE(ResourceUriPrefix) - 1;
constexpr wchar_t c_defaultPriFilename[] = L"resources.pri";

#define INDEX_RESOURCE_ID -1
#define INDEX_RESOURCE_URI -2
Expand Down Expand Up @@ -853,18 +854,21 @@ STDAPI_(void) MrmFreeResource(_In_opt_ void* resource)
return;
}

// Append filename to current module path. If the file doesn't exist, it will
// When filename is provided, append filename to current module path. If the file doesn't exist, it will
// append the filename to parent path. If none exists, file in current module
// path will be returned.
// Visual Studio usually builds the executable to a subfolder with project name
// Visual Studio usually builds the executable to a subfolder with project name for packaged project
// unless TargetPlatformIdentifier is UAP. To accommodate this behavior, we will
// try to search resources.pri from both module path and parent path.
STDAPI MrmGetFilePathFromName(_In_ PCWSTR filename, _Outptr_ PWSTR* filePath)
//
// When filename is not provided or empty, we will search resources.pri under current module
// path. If the file doesn't exist, search [modulename].pri instead.
// For unpackaged app, the built resource name is [modulename].pri. We still search resources.pri
// because that is a supported scenario for inbox MRT (Xaml islands).
STDAPI MrmGetFilePathFromName(_In_opt_ PCWSTR filename, _Outptr_ PWSTR* filePath)
{
*filePath = nullptr;

RETURN_HR_IF(E_INVALIDARG, filename == nullptr || *filename == L'\0');

wchar_t path[MAX_PATH];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not as part of this PR, but we should remove usages of MAX_PATH.

DWORD size = ARRAYSIZE(path);
DWORD length = GetModuleFileName(nullptr, path, size);
Expand Down Expand Up @@ -899,24 +903,38 @@ STDAPI MrmGetFilePathFromName(_In_ PCWSTR filename, _Outptr_ PWSTR* filePath)
pointerToPath++;
}

pointerToPath = pathAllocated ? pathAllocated.get() : path;
rohanp-msft marked this conversation as resolved.
Show resolved Hide resolved

wchar_t moduleFilename[MAX_PATH];
RETURN_IF_FAILED(StringCchCopyW(moduleFilename, ARRAYSIZE(moduleFilename), lastSlash != nullptr ? lastSlash + 1 : pointerToPath));

if (lastSlash != nullptr)
{
*(lastSlash + 1) = 0;
}

pointerToPath = pathAllocated ? pathAllocated.get() : path;

std::unique_ptr<wchar_t, decltype(&MrmFreeResource)> finalPath(nullptr, MrmFreeResource);

PCWSTR filenameToUse = filename;
if (filename == nullptr || *filename == L'\0')
{
filenameToUse = c_defaultPriFilename;
}

// Will build the path at most twice.
// First time using current path. If not exist, do another time with parent path.
// If filename is provided:
// - search under exe path
// - if not exist, search parent path
// If filename is not provided:
// - search under exe path with default name (resources.pri)
// - if not exist, search under same path with [modulename].pri
evelynwu-msft marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < 2; i++)
{
size_t lengthInSizeT;
RETURN_IF_FAILED(StringCchLengthW(pointerToPath, STRSAFE_MAX_CCH, &lengthInSizeT));
length = static_cast<DWORD>(lengthInSizeT);

RETURN_IF_FAILED(StringCchLengthW(filename, STRSAFE_MAX_CCH, &lengthInSizeT));
RETURN_IF_FAILED(StringCchLengthW(filenameToUse, STRSAFE_MAX_CCH, &lengthInSizeT));
DWORD lengthOfName = static_cast<DWORD>(lengthInSizeT);

RETURN_IF_FAILED(DWordAdd(length, lengthOfName, &length));
Expand All @@ -929,9 +947,11 @@ STDAPI MrmGetFilePathFromName(_In_ PCWSTR filename, _Outptr_ PWSTR* filePath)
std::unique_ptr<wchar_t, decltype(&MrmFreeResource)> outputPath(rawOutputPath, MrmFreeResource);

RETURN_IF_FAILED(StringCchCopyW(outputPath.get(), length, pointerToPath));
RETURN_IF_FAILED(StringCchCatW(outputPath.get(), length, filename));
RETURN_IF_FAILED(StringCchCatW(outputPath.get(), length, filenameToUse));

if (GetFileAttributes(outputPath.get()) != INVALID_FILE_ATTRIBUTES)
DWORD attributes = GetFileAttributes(outputPath.get());

if ((attributes != INVALID_FILE_ATTRIBUTES) && !(attributes & FILE_ATTRIBUTE_DIRECTORY))
{
// The file exists. Done.
finalPath.swap(outputPath);
Expand All @@ -940,16 +960,28 @@ STDAPI MrmGetFilePathFromName(_In_ PCWSTR filename, _Outptr_ PWSTR* filePath)

if (i == 0)
{
// If none of the file exists, will return the file in current path
// The filename in the first iteration (the file under module path) will be returned if no file is found
// in all iterations.
finalPath.swap(outputPath);
}

if (secondToLastSlash == nullptr)
{
break;
}
// Prep for second iteration
if (filename == nullptr || *filename == L'\0')
{
// Change to [modulename].pri
RETURN_IF_FAILED(PathCchRenameExtension(moduleFilename, ARRAYSIZE(moduleFilename), L"pri"));
filenameToUse = moduleFilename;
}
else
{
// move to parent folder
if (secondToLastSlash == nullptr)
{
break;
}

*(secondToLastSlash + 1) = 0;
*(secondToLastSlash + 1) = 0;
}
}
}

*filePath = finalPath.release();
Expand Down
4 changes: 2 additions & 2 deletions dev/MRTCore/mrt/Core/src/MRM.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

#pragma once
Expand Down Expand Up @@ -126,7 +126,7 @@ extern "C"
STDAPI_(void*) MrmAllocateBuffer(size_t size);
STDAPI_(void) MrmFreeResource(_In_opt_ void* resource);

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

#ifdef __cplusplus
}
Expand Down
2 changes: 1 addition & 1 deletion dev/MRTCore/mrt/Core/src/MRM.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
<AdditionalIncludeDirectories>..\..\mrm\include</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>$(OutDir)..\mrmmin\mrmmin.lib;rpcrt4.lib;onecore_downlevel.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>$(OutDir)..\mrmmin\mrmmin.lib;rpcrt4.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
<ModuleDefinitionFile>MRM.def</ModuleDefinitionFile>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
Expand Down
19 changes: 18 additions & 1 deletion dev/MRTCore/mrt/Core/unittests/MrmTests.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

#include <Windows.h>
Expand Down Expand Up @@ -560,6 +560,23 @@ TEST_CLASS(BasicTest)
}
}

TEST_METHOD(GetFilePath)
{
wchar_t* path;
Assert::AreEqual(MrmGetFilePathFromName(L"resources.pri", &path), S_OK);
Assert::IsNotNull(wcsstr(path, L"resources.pri"));
MrmFreeResource(path);

Assert::AreEqual(MrmGetFilePathFromName(L"something.pri", &path), S_OK);
// Even if the file doesn't exist, we will still return a path. This is for those don't use PRI for resource.
Copy link
Contributor

@rohanp-msft rohanp-msft May 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for those don't use PRI for resource.

Can you expand this comment, please? It's not clear to me. If someone's not using a PRI file, why is this PRI-specific API being invoked?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an existing app may want to migrate to switch to WinUI3, but not ready to switch the resource management yet. They can use ResourceNotFound event to hook up their existing resource management.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding that or part of that as a comment would be good, IMO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. The comment on 963-964 of MRM.cpp was not enlightening because it didn't explain why that was the desired behavior.

Assert::IsNotNull(wcsstr(path, L"something.pri"));
MrmFreeResource(path);

Assert::AreEqual(MrmGetFilePathFromName(nullptr, &path), S_OK);
Assert::IsNotNull(wcsstr(path, L"resources.pri"));
MrmFreeResource(path);
}

private:
void VerifyQualifierValue(UINT32 qualifierCount, PWSTR* qualifierNames, PWSTR* qualifierValues, PCWSTR name, PCWSTR expectedValue)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("UnpackagedTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]


// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("b706ab66-8dd1-48eb-a81d-4ee55bc3d6ea")]
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace MrtCoreUnpackagedTests
{
using System;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using WEX.Common.Managed;
using WEX.Logging.Interop;
using WEX.TestExecution;
using WEX.TestExecution.Markup;
using Microsoft.ApplicationModel.Resources;

#region Helper class for WinRT activation
internal class ActivationContext : IDisposable
{
private IntPtr m_cookie = (IntPtr)0;
private IntPtr m_ctx = (IntPtr)0;
private bool m_activated = false;

public ActivationContext()
{
Activate();
}

public void Activate()
{
if (!m_activated)
{
string assemblyPath = Assembly.GetExecutingAssembly().Location;
ActivateContext(Path.Combine(Path.GetDirectoryName(assemblyPath), "app.manifest"));
m_activated = true;
}
}

public void Dispose()
{
if (m_activated)
{
NativeMethods.DeactivateActCtx(0, m_cookie);
NativeMethods.ReleaseActCtx(m_ctx);
m_cookie = (IntPtr)0;
m_ctx = (IntPtr)0;
m_activated = false;
}
}

private void ActivateContext(string manifestPath)
{
var context = new NativeMethods.ACTCTX();
context.cbSize = (uint)Marshal.SizeOf(typeof(NativeMethods.ACTCTX));
context.lpSource = manifestPath;

m_ctx = NativeMethods.CreateActCtx(ref context);
if (m_ctx == (IntPtr)(-1))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}

if (!NativeMethods.ActivateActCtx(m_ctx, out m_cookie))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}

[SuppressUnmanagedCodeSecurity]
internal static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "CreateActCtxW")]
internal static extern IntPtr CreateActCtx(ref ACTCTX pActctx);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool ActivateActCtx(IntPtr hActCtx, out IntPtr lpCookie);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeactivateActCtx(int dwFlags, IntPtr ulCookie);

[DllImport("kernel32.dll")]
internal static extern void ReleaseActCtx(IntPtr hActCtx);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct ACTCTX
{
public UInt32 cbSize;
public UInt32 dwFlags;
public string lpSource;
public UInt16 wProcessorArchitecture;
public UInt16 wLangId;
public string lpAssemblyDirectory;
public string lpResourceName;
public string lpApplicationName;
public IntPtr hModule;
}
}
#endregion

[TestClass]
public class TestClass
{
private ActivationContext m_context = new ActivationContext();
private static string m_assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

[AssemblyInitialize]
public static void ModuleSetup(TestContext testContext)
{
// Cleanup just in case
File.Delete(Path.Combine(m_assemblyFolder, "resources.pri"));
File.Delete(Path.Combine(m_assemblyFolder, "te.processhost.pri"));
}

[TestMethod]
public void DefaultResourceManager()
{
var resourceManager = new ResourceManager();
var resourceMap = resourceManager.MainResourceMap;
var map = resourceMap.GetSubtree("resources");

// No resource file is loaded
Verify.AreEqual(map.ResourceCount, 0u);
var ex = Verify.Throws<Exception>(() => map.GetValue("IDS_MANIFEST_MUSIC_APP_NAME"));
Verify.AreEqual((uint)ex.HResult, 0x80070490); // HRESULT_FROM_WIN32(ERROR_NOT_FOUND)
}

[TestMethod]
public void DefaultResourceManagerWithResourcePri()
{
File.Copy(Path.Combine(m_assemblyFolder, "resources.pri.standalone"), Path.Combine(m_assemblyFolder, "resources.pri"));

var resourceManager = new ResourceManager();
var resourceMap = resourceManager.MainResourceMap;
var map = resourceMap.GetSubtree("resources");
Verify.AreNotEqual(map.ResourceCount, 0u);
var resource = map.GetValue("IDS_MANIFEST_MUSIC_APP_NAME").ValueAsString;
Verify.AreEqual(resource, "Groove Music");

File.Delete(Path.Combine(m_assemblyFolder, "resources.pri"));
}

[TestMethod]
public void DefaultResourceManagerWithExePri()
{
File.Copy(Path.Combine(m_assemblyFolder, "resources.pri.standalone"), Path.Combine(m_assemblyFolder, "te.processhost.pri"));

var resourceManager = new ResourceManager();
var resourceMap = resourceManager.MainResourceMap;
var map = resourceMap.GetSubtree("resources");
Verify.AreNotEqual(map.ResourceCount, 0u);
var resource = map.GetValue("IDS_MANIFEST_MUSIC_APP_NAME").ValueAsString;
Verify.AreEqual(resource, "Groove Music");
rohanp-msft marked this conversation as resolved.
Show resolved Hide resolved

File.Delete(Path.Combine(m_assemblyFolder, "te.processhost.pri"));
}

[TestMethod]
public void ResourceManagerWithFile()
{
var resourceManager = new ResourceManager("resources.pri.standalone");
var resourceMap = resourceManager.MainResourceMap;
var map = resourceMap.GetSubtree("resources");
Verify.AreNotEqual(map.ResourceCount, 0u);
var resource = map.GetValue("IDS_MANIFEST_MUSIC_APP_NAME").ValueAsString;
Verify.AreEqual(resource, "Groove Music");
}
}
}
Loading