Skip to content

Commit fa69ba2

Browse files
authored
MRTCore will also search for [modulename].pri for unpackaged app (#814)
* name * exclude test * cr * comment
1 parent 47b45d9 commit fa69ba2

13 files changed

Lines changed: 436 additions & 28 deletions

File tree

build/build-mrt.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ steps:
145145
!**\*TestAdapter.dll
146146
!**\TE.*.dll
147147
!**\obj\**
148+
!**\MrtCoreUnpackagedTests.dll
148149
searchFolder: '${{ parameters.MRTBinariesDirectory }}\Release\$(buildPlatform)'
149150
testRunTitle: 'test MRT $(buildPlatform)'
150151
platform: '$(buildPlatform)'

dev/MRTCore/mrt/Core/src/MRM.cpp

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33

44
#include <Windows.h>
5-
5+
#include <Pathcch.h>
66
#include "mrm/BaseInternal.h"
77
#include "mrm/common/platform.h"
88
#include "mrm/readers/MrmReaders.h"
@@ -25,6 +25,7 @@ typedef struct
2525

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

2930
#define INDEX_RESOURCE_ID -1
3031
#define INDEX_RESOURCE_URI -2
@@ -853,18 +854,26 @@ STDAPI_(void) MrmFreeResource(_In_opt_ void* resource)
853854
return;
854855
}
855856

856-
// Append filename to current module path. If the file doesn't exist, it will
857+
// When filename is provided, append filename to current module path. If the file doesn't exist, it will
857858
// append the filename to parent path. If none exists, file in current module
858859
// path will be returned.
859-
// Visual Studio usually builds the executable to a subfolder with project name
860+
// Visual Studio usually builds the executable to a subfolder with project name for packaged project
860861
// unless TargetPlatformIdentifier is UAP. To accommodate this behavior, we will
861862
// try to search resources.pri from both module path and parent path.
862-
STDAPI MrmGetFilePathFromName(_In_ PCWSTR filename, _Outptr_ PWSTR* filePath)
863+
//
864+
// When filename is not provided or empty, we will search resources.pri under current module
865+
// path. If the file doesn't exist, search [modulename].pri instead.
866+
// For unpackaged app, the built resource name is [modulename].pri. We still search resources.pri
867+
// because that is a supported scenario for inbox MRT (Xaml islands).
868+
//
869+
// A file path is always returned even if none of the files under the above mentioned searching
870+
// criteria exists. In that case, we will return filename (if provided) or resources.pri (if name not
871+
// provided) under module file path. The reason is that we don't want to fail the creation of
872+
// ResourceManager even if an app doesn't have PRI file.
873+
STDAPI MrmGetFilePathFromName(_In_opt_ PCWSTR filename, _Outptr_ PWSTR* filePath)
863874
{
864875
*filePath = nullptr;
865876

866-
RETURN_HR_IF(E_INVALIDARG, filename == nullptr || *filename == L'\0');
867-
868877
wchar_t path[MAX_PATH];
869878
DWORD size = ARRAYSIZE(path);
870879
DWORD length = GetModuleFileName(nullptr, path, size);
@@ -899,24 +908,38 @@ STDAPI MrmGetFilePathFromName(_In_ PCWSTR filename, _Outptr_ PWSTR* filePath)
899908
pointerToPath++;
900909
}
901910

911+
pointerToPath = pathAllocated ? pathAllocated.get() : path;
912+
913+
wchar_t moduleFilename[MAX_PATH];
914+
RETURN_IF_FAILED(StringCchCopyW(moduleFilename, ARRAYSIZE(moduleFilename), lastSlash != nullptr ? lastSlash + 1 : pointerToPath));
915+
902916
if (lastSlash != nullptr)
903917
{
904918
*(lastSlash + 1) = 0;
905919
}
906920

907-
pointerToPath = pathAllocated ? pathAllocated.get() : path;
908-
909921
std::unique_ptr<wchar_t, decltype(&MrmFreeResource)> finalPath(nullptr, MrmFreeResource);
910922

923+
PCWSTR filenameToUse = filename;
924+
if (filename == nullptr || *filename == L'\0')
925+
{
926+
filenameToUse = c_defaultPriFilename;
927+
}
928+
911929
// Will build the path at most twice.
912-
// First time using current path. If not exist, do another time with parent path.
930+
// If filename is provided:
931+
// - search under exe path
932+
// - if not exist, search parent path
933+
// If filename is not provided:
934+
// - search under exe path with default name (resources.pri)
935+
// - if not exist, search under same path with [modulename].pri
913936
for (int i = 0; i < 2; i++)
914937
{
915938
size_t lengthInSizeT;
916939
RETURN_IF_FAILED(StringCchLengthW(pointerToPath, STRSAFE_MAX_CCH, &lengthInSizeT));
917940
length = static_cast<DWORD>(lengthInSizeT);
918941

919-
RETURN_IF_FAILED(StringCchLengthW(filename, STRSAFE_MAX_CCH, &lengthInSizeT));
942+
RETURN_IF_FAILED(StringCchLengthW(filenameToUse, STRSAFE_MAX_CCH, &lengthInSizeT));
920943
DWORD lengthOfName = static_cast<DWORD>(lengthInSizeT);
921944

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

931954
RETURN_IF_FAILED(StringCchCopyW(outputPath.get(), length, pointerToPath));
932-
RETURN_IF_FAILED(StringCchCatW(outputPath.get(), length, filename));
955+
RETURN_IF_FAILED(StringCchCatW(outputPath.get(), length, filenameToUse));
933956

934-
if (GetFileAttributes(outputPath.get()) != INVALID_FILE_ATTRIBUTES)
957+
DWORD attributes = GetFileAttributes(outputPath.get());
958+
959+
if ((attributes != INVALID_FILE_ATTRIBUTES) && !(attributes & FILE_ATTRIBUTE_DIRECTORY))
935960
{
936961
// The file exists. Done.
937962
finalPath.swap(outputPath);
@@ -940,16 +965,28 @@ STDAPI MrmGetFilePathFromName(_In_ PCWSTR filename, _Outptr_ PWSTR* filePath)
940965

941966
if (i == 0)
942967
{
943-
// If none of the file exists, will return the file in current path
968+
// The filename in the first iteration (the file under module path) will be returned if no file is found
969+
// in all iterations.
944970
finalPath.swap(outputPath);
945-
}
946971

947-
if (secondToLastSlash == nullptr)
948-
{
949-
break;
950-
}
972+
// Prep for second iteration
973+
if (filename == nullptr || *filename == L'\0')
974+
{
975+
// Change to [modulename].pri
976+
RETURN_IF_FAILED(PathCchRenameExtension(moduleFilename, ARRAYSIZE(moduleFilename), L"pri"));
977+
filenameToUse = moduleFilename;
978+
}
979+
else
980+
{
981+
// move to parent folder
982+
if (secondToLastSlash == nullptr)
983+
{
984+
break;
985+
}
951986

952-
*(secondToLastSlash + 1) = 0;
987+
*(secondToLastSlash + 1) = 0;
988+
}
989+
}
953990
}
954991

955992
*filePath = finalPath.release();

dev/MRTCore/mrt/Core/src/MRM.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33

44
#pragma once
@@ -126,7 +126,7 @@ extern "C"
126126
STDAPI_(void*) MrmAllocateBuffer(size_t size);
127127
STDAPI_(void) MrmFreeResource(_In_opt_ void* resource);
128128

129-
STDAPI MrmGetFilePathFromName(_In_ PCWSTR filename, _Outptr_ PWSTR* filePath);
129+
STDAPI MrmGetFilePathFromName(_In_opt_ PCWSTR filename, _Outptr_ PWSTR* filePath);
130130

131131
#ifdef __cplusplus
132132
}

dev/MRTCore/mrt/Core/src/MRM.vcxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
<AdditionalIncludeDirectories>..\..\mrm\include</AdditionalIncludeDirectories>
8686
</ClCompile>
8787
<Link>
88-
<AdditionalDependencies>$(OutDir)..\mrmmin\mrmmin.lib;rpcrt4.lib;onecore_downlevel.lib;%(AdditionalDependencies)</AdditionalDependencies>
88+
<AdditionalDependencies>$(OutDir)..\mrmmin\mrmmin.lib;rpcrt4.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
8989
<ModuleDefinitionFile>MRM.def</ModuleDefinitionFile>
9090
<SubSystem>Windows</SubSystem>
9191
<GenerateDebugInformation>true</GenerateDebugInformation>

dev/MRTCore/mrt/Core/unittests/MrmTests.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33

44
#include <Windows.h>
@@ -560,6 +560,23 @@ TEST_CLASS(BasicTest)
560560
}
561561
}
562562

563+
TEST_METHOD(GetFilePath)
564+
{
565+
wchar_t* path;
566+
Assert::AreEqual(MrmGetFilePathFromName(L"resources.pri", &path), S_OK);
567+
Assert::IsNotNull(wcsstr(path, L"resources.pri"));
568+
MrmFreeResource(path);
569+
570+
Assert::AreEqual(MrmGetFilePathFromName(L"something.pri", &path), S_OK);
571+
// Even if the file doesn't exist, we will still return a path. This is for those don't use PRI for resource.
572+
Assert::IsNotNull(wcsstr(path, L"something.pri"));
573+
MrmFreeResource(path);
574+
575+
Assert::AreEqual(MrmGetFilePathFromName(nullptr, &path), S_OK);
576+
Assert::IsNotNull(wcsstr(path, L"resources.pri"));
577+
MrmFreeResource(path);
578+
}
579+
563580
private:
564581
void VerifyQualifierValue(UINT32 qualifierCount, PWSTR* qualifierNames, PWSTR* qualifierValues, PCWSTR name, PCWSTR expectedValue)
565582
{
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System.Reflection;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
7+
8+
// General Information about an assembly is controlled through the following
9+
// set of attributes. Change these attribute values to modify the information
10+
// associated with an assembly.
11+
[assembly: AssemblyTitle("UnpackagedTests")]
12+
[assembly: AssemblyDescription("")]
13+
[assembly: AssemblyConfiguration("")]
14+
[assembly: AssemblyTrademark("")]
15+
[assembly: AssemblyCulture("")]
16+
17+
18+
// The following GUID is for the ID of the typelib if this project is exposed to COM
19+
[assembly: Guid("b706ab66-8dd1-48eb-a81d-4ee55bc3d6ea")]
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
namespace MrtCoreUnpackagedTests
5+
{
6+
using System;
7+
using System.ComponentModel;
8+
using System.Data;
9+
using System.IO;
10+
using System.Reflection;
11+
using System.Runtime.InteropServices;
12+
using System.Security;
13+
using WEX.Common.Managed;
14+
using WEX.Logging.Interop;
15+
using WEX.TestExecution;
16+
using WEX.TestExecution.Markup;
17+
using Microsoft.ApplicationModel.Resources;
18+
19+
#region Helper class for WinRT activation
20+
internal class ActivationContext : IDisposable
21+
{
22+
private IntPtr m_cookie = (IntPtr)0;
23+
private IntPtr m_ctx = (IntPtr)0;
24+
private bool m_activated = false;
25+
26+
public ActivationContext()
27+
{
28+
Activate();
29+
}
30+
31+
public void Activate()
32+
{
33+
if (!m_activated)
34+
{
35+
string assemblyPath = Assembly.GetExecutingAssembly().Location;
36+
ActivateContext(Path.Combine(Path.GetDirectoryName(assemblyPath), "app.manifest"));
37+
m_activated = true;
38+
}
39+
}
40+
41+
public void Dispose()
42+
{
43+
if (m_activated)
44+
{
45+
NativeMethods.DeactivateActCtx(0, m_cookie);
46+
NativeMethods.ReleaseActCtx(m_ctx);
47+
m_cookie = (IntPtr)0;
48+
m_ctx = (IntPtr)0;
49+
m_activated = false;
50+
}
51+
}
52+
53+
private void ActivateContext(string manifestPath)
54+
{
55+
var context = new NativeMethods.ACTCTX();
56+
context.cbSize = (uint)Marshal.SizeOf(typeof(NativeMethods.ACTCTX));
57+
context.lpSource = manifestPath;
58+
59+
m_ctx = NativeMethods.CreateActCtx(ref context);
60+
if (m_ctx == (IntPtr)(-1))
61+
{
62+
throw new Win32Exception(Marshal.GetLastWin32Error());
63+
}
64+
65+
if (!NativeMethods.ActivateActCtx(m_ctx, out m_cookie))
66+
{
67+
throw new Win32Exception(Marshal.GetLastWin32Error());
68+
}
69+
}
70+
}
71+
72+
[SuppressUnmanagedCodeSecurity]
73+
internal static class NativeMethods
74+
{
75+
[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "CreateActCtxW")]
76+
internal static extern IntPtr CreateActCtx(ref ACTCTX pActctx);
77+
78+
[DllImport("kernel32.dll", SetLastError = true)]
79+
[return: MarshalAs(UnmanagedType.Bool)]
80+
internal static extern bool ActivateActCtx(IntPtr hActCtx, out IntPtr lpCookie);
81+
82+
[DllImport("kernel32.dll", SetLastError = true)]
83+
[return: MarshalAs(UnmanagedType.Bool)]
84+
internal static extern bool DeactivateActCtx(int dwFlags, IntPtr ulCookie);
85+
86+
[DllImport("kernel32.dll")]
87+
internal static extern void ReleaseActCtx(IntPtr hActCtx);
88+
89+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
90+
internal struct ACTCTX
91+
{
92+
public UInt32 cbSize;
93+
public UInt32 dwFlags;
94+
public string lpSource;
95+
public UInt16 wProcessorArchitecture;
96+
public UInt16 wLangId;
97+
public string lpAssemblyDirectory;
98+
public string lpResourceName;
99+
public string lpApplicationName;
100+
public IntPtr hModule;
101+
}
102+
}
103+
#endregion
104+
105+
[TestClass]
106+
public class TestClass
107+
{
108+
private ActivationContext m_context = new ActivationContext();
109+
private static string m_assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
110+
111+
[AssemblyInitialize]
112+
public static void ModuleSetup(TestContext testContext)
113+
{
114+
// Cleanup just in case
115+
File.Delete(Path.Combine(m_assemblyFolder, "resources.pri"));
116+
File.Delete(Path.Combine(m_assemblyFolder, "te.processhost.pri"));
117+
}
118+
119+
[TestMethod]
120+
public void DefaultResourceManager()
121+
{
122+
var resourceManager = new ResourceManager();
123+
var resourceMap = resourceManager.MainResourceMap;
124+
var map = resourceMap.GetSubtree("resources");
125+
126+
// No resource file is loaded
127+
Verify.AreEqual(map.ResourceCount, 0u);
128+
var ex = Verify.Throws<Exception>(() => map.GetValue("IDS_MANIFEST_MUSIC_APP_NAME"));
129+
Verify.AreEqual((uint)ex.HResult, 0x80070490); // HRESULT_FROM_WIN32(ERROR_NOT_FOUND)
130+
}
131+
132+
[TestMethod]
133+
public void DefaultResourceManagerWithResourcePri()
134+
{
135+
File.Copy(Path.Combine(m_assemblyFolder, "resources.pri.standalone"), Path.Combine(m_assemblyFolder, "resources.pri"));
136+
137+
var resourceManager = new ResourceManager();
138+
var resourceMap = resourceManager.MainResourceMap;
139+
var map = resourceMap.GetSubtree("resources");
140+
Verify.AreNotEqual(map.ResourceCount, 0u);
141+
var resource = map.GetValue("IDS_MANIFEST_MUSIC_APP_NAME").ValueAsString;
142+
Verify.AreEqual(resource, "Groove Music");
143+
144+
File.Delete(Path.Combine(m_assemblyFolder, "resources.pri"));
145+
}
146+
147+
[TestMethod]
148+
public void DefaultResourceManagerWithExePri()
149+
{
150+
File.Copy(Path.Combine(m_assemblyFolder, "resources.pri.standalone"), Path.Combine(m_assemblyFolder, "te.processhost.pri"));
151+
152+
var resourceManager = new ResourceManager();
153+
var resourceMap = resourceManager.MainResourceMap;
154+
var map = resourceMap.GetSubtree("resources");
155+
Verify.AreNotEqual(map.ResourceCount, 0u);
156+
var resource = map.GetValue("IDS_MANIFEST_MUSIC_APP_NAME").ValueAsString;
157+
Verify.AreEqual(resource, "Groove Music");
158+
159+
File.Delete(Path.Combine(m_assemblyFolder, "te.processhost.pri"));
160+
}
161+
162+
[TestMethod]
163+
public void ResourceManagerWithFile()
164+
{
165+
var resourceManager = new ResourceManager("resources.pri.standalone");
166+
var resourceMap = resourceManager.MainResourceMap;
167+
var map = resourceMap.GetSubtree("resources");
168+
Verify.AreNotEqual(map.ResourceCount, 0u);
169+
var resource = map.GetValue("IDS_MANIFEST_MUSIC_APP_NAME").ValueAsString;
170+
Verify.AreEqual(resource, "Groove Music");
171+
}
172+
}
173+
}

0 commit comments

Comments
 (0)