Introduce a WinRT utils library and "checked resources" (#3350)

This commit introduces a C++/WinRT utility library and moves
ScopedResourceLoader into it. I decided to get uppity and introduce
something I like to call "checked resources." The idea is that every
resource reference from a library is knowable at compile time, and we
should be able to statically ensure that all resources exist.

This is a system that lets us immediately failfast (on launch) when a
library makes a static reference to a resource that doesn't exist at
runtime.

It exposes two new (preprocessor) APIs:
* `RS_(wchar_t)`: loads a localizable string resource by name.
* `USES_RESOURCE(wchar_t)`: marks a resource key as used, but is intended
  for loading images or passing static resource keys as parameters to
  functions that will look them up later.

Resource checking relies on diligent use of `USES_RESOURCE()` and `RS_()`
(which uses `USES_RESOURCE`), but can make sure we don't ship something
that'll blow up at runtime.

It works like this:

**IN DEBUG MODE**
- All resource names referenced through `USES_RESOURCE()` are emitted
  alongside their referencing filenames and line numbers into a static
  section of the binary.
  That section is named `.util$res$m`.

- We emit two sentinel values into two different sections, `.util$res$a`
  and `.util$res$z`.

- The linker sorts all sections alphabetically before crushing them
  together into the final binary.

- When we first construct a library's scoped resource loader, we
  iterate over every resource reference between `$a` and `$z` and check
  residency.

**IN RELEASE MODE**
- All checked resource code is compiled out.

Fixes #2146.

Macros are the only way to do something this cool, incidentally.

## Validation Steps Performed
Made references to a bunch of bad resources, tried to break it a lot.

It looks like this when it fails:

### App.cpp
```
36  static const std::array<std::wstring_view, 2> settingsLoadErrorsLabels {
37      USES_RESOURCE(L"NoProfilesText"),
38      USES_RESOURCE(L"AllProfilesHiddenText_HA_JUST_KIDDING")
39  };
```

```
WinRTUtils\LibraryResources.cpp(68)\TerminalApp.dll:
    FailFast(1) tid(1034) 8000FFFF Catastrophic failure
    Msg:[Resource AllProfilesHiddenText_HA_JUST_KIDDING not found in
      scope TerminalApp/Resources (App.cpp:38)] [EnsureAllResourcesArePresent]
```
This commit is contained in:
Dustin L. Howett (MSFT) 2019-11-01 15:47:05 -07:00 committed by GitHub
parent a34c47a493
commit 15505337d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 362 additions and 67 deletions

View File

@ -256,6 +256,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winconpty", "src\winconpty\
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererUia", "src\renderer\uia\lib\uia.vcxproj", "{48D21369-3D7B-4431-9967-24E81292CF63}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinRTUtils", "src\cascadia\WinRTUtils\WinRTUtils.vcxproj", "{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
AuditMode|Any CPU = AuditMode|Any CPU
@ -1270,6 +1272,24 @@ Global
{48D21369-3D7B-4431-9967-24E81292CF63}.Release|x64.Build.0 = Release|x64
{48D21369-3D7B-4431-9967-24E81292CF63}.Release|x86.ActiveCfg = Release|Win32
{48D21369-3D7B-4431-9967-24E81292CF63}.Release|x86.Build.0 = Release|Win32
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.AuditMode|Any CPU.ActiveCfg = Release|x64
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.AuditMode|ARM64.ActiveCfg = Release|ARM64
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.AuditMode|x64.ActiveCfg = Release|x64
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.AuditMode|x86.ActiveCfg = Release|Win32
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|Any CPU.ActiveCfg = Debug|Win32
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|ARM64.ActiveCfg = Debug|ARM64
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|ARM64.Build.0 = Debug|ARM64
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|x64.ActiveCfg = Debug|x64
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|x64.Build.0 = Debug|x64
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|x86.ActiveCfg = Debug|Win32
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|x86.Build.0 = Debug|Win32
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|Any CPU.ActiveCfg = Release|Win32
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|ARM64.ActiveCfg = Release|ARM64
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|ARM64.Build.0 = Release|ARM64
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|x64.ActiveCfg = Release|x64
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|x64.Build.0 = Release|x64
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|x86.ActiveCfg = Release|Win32
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1335,6 +1355,7 @@ Global
{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {59840756-302F-44DF-AA47-441A9D673202}
{58A03BB2-DF5A-4B66-91A0-7EF3BA01269A} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
{48D21369-3D7B-4431-9967-24E81292CF63} = {05500DEF-2294-41E3-AF9A-24E580B82836}
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE} = {59840756-302F-44DF-AA47-441A9D673202}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}

View File

@ -5,6 +5,8 @@
#include "App.h"
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
#include <LibraryResources.h>
#include "App.g.cpp"
using namespace winrt::Windows::ApplicationModel::DataTransfer;
@ -27,13 +29,13 @@ namespace winrt
// Make sure that these keys are in the same order as the
// SettingsLoadWarnings/Errors enum is!
static const std::array<std::wstring_view, 3> settingsLoadWarningsLabels {
L"MissingDefaultProfileText",
L"DuplicateProfileText",
L"UnknownColorSchemeText"
USES_RESOURCE(L"MissingDefaultProfileText"),
USES_RESOURCE(L"DuplicateProfileText"),
USES_RESOURCE(L"UnknownColorSchemeText")
};
static const std::array<std::wstring_view, 2> settingsLoadErrorsLabels {
L"NoProfilesText",
L"AllProfilesHiddenText"
USES_RESOURCE(L"NoProfilesText"),
USES_RESOURCE(L"AllProfilesHiddenText")
};
// clang-format on
@ -46,15 +48,14 @@ static const std::array<std::wstring_view, 2> settingsLoadErrorsLabels {
// Arguments:
// - key: the value to use to look for a resource key in the given map
// - map: A map of keys->Resource keys.
// - loader: the ScopedResourceLoader to use to look up the localized string.
// Return Value:
// - the localized string for the given type, if it exists.
template<std::size_t N>
static winrt::hstring _GetMessageText(uint32_t index, std::array<std::wstring_view, N> keys, ScopedResourceLoader& loader)
static winrt::hstring _GetMessageText(uint32_t index, std::array<std::wstring_view, N> keys)
{
if (index < keys.size())
{
return loader.GetLocalizedString(keys.at(index));
return GetLibraryResourceString(keys.at(index));
}
return {};
}
@ -65,12 +66,11 @@ static winrt::hstring _GetMessageText(uint32_t index, std::array<std::wstring_vi
// - The warning should have an entry in settingsLoadWarningsLabels.
// Arguments:
// - warning: the SettingsLoadWarnings value to get the localized text for.
// - loader: the ScopedResourceLoader to use to look up the localized string.
// Return Value:
// - localized text for the given warning
static winrt::hstring _GetWarningText(::TerminalApp::SettingsLoadWarnings warning, ScopedResourceLoader& loader)
static winrt::hstring _GetWarningText(::TerminalApp::SettingsLoadWarnings warning)
{
return _GetMessageText(static_cast<uint32_t>(warning), settingsLoadWarningsLabels, loader);
return _GetMessageText(static_cast<uint32_t>(warning), settingsLoadWarningsLabels);
}
// Function Description:
@ -79,12 +79,11 @@ static winrt::hstring _GetWarningText(::TerminalApp::SettingsLoadWarnings warnin
// - The warning should have an entry in settingsLoadErrorsLabels.
// Arguments:
// - error: the SettingsLoadErrors value to get the localized text for.
// - loader: the ScopedResourceLoader to use to look up the localized string.
// Return Value:
// - localized text for the given error
static winrt::hstring _GetErrorText(::TerminalApp::SettingsLoadErrors error, ScopedResourceLoader& loader)
static winrt::hstring _GetErrorText(::TerminalApp::SettingsLoadErrors error)
{
return _GetMessageText(static_cast<uint32_t>(error), settingsLoadErrorsLabels, loader);
return _GetMessageText(static_cast<uint32_t>(error), settingsLoadErrorsLabels);
}
// Function Description:
@ -128,12 +127,10 @@ namespace winrt::TerminalApp::implementation
// Initialize will become protected or be deleted when GH#1339 (workaround for MSFT:22116519) are fixed.
Initialize();
_resourceLoader = std::make_shared<ScopedResourceLoader>(L"TerminalApp/Resources");
// The TerminalPage has to be constructed during our construction, to
// make sure that there's a terminal page for callers of
// SetTitleBarContent
_root = winrt::make_self<TerminalPage>(_resourceLoader);
_root = winrt::make_self<TerminalPage>();
}
// Method Description:
@ -222,8 +219,8 @@ namespace winrt::TerminalApp::implementation
const winrt::hstring& contentKey,
HRESULT settingsLoadedResult)
{
auto title = _resourceLoader->GetLocalizedString(titleKey);
auto buttonText = _resourceLoader->GetLocalizedString(L"Ok");
auto title = GetLibraryResourceString(titleKey);
auto buttonText = RS_(L"Ok");
Controls::TextBlock warningsTextBlock;
// Make sure you can copy-paste
@ -232,7 +229,7 @@ namespace winrt::TerminalApp::implementation
warningsTextBlock.TextWrapping(TextWrapping::Wrap);
winrt::Windows::UI::Xaml::Documents::Run errorRun;
const auto errorLabel = _resourceLoader->GetLocalizedString(contentKey);
const auto errorLabel = GetLibraryResourceString(contentKey);
errorRun.Text(errorLabel);
warningsTextBlock.Inlines().Append(errorRun);
@ -246,7 +243,7 @@ namespace winrt::TerminalApp::implementation
// Add a note that we're using the default settings in this case.
winrt::Windows::UI::Xaml::Documents::Run usingDefaultsRun;
const auto usingDefaultsText = _resourceLoader->GetLocalizedString(L"UsingDefaultSettingsText");
const auto usingDefaultsText = RS_(L"UsingDefaultSettingsText");
usingDefaultsRun.Text(usingDefaultsText);
warningsTextBlock.Inlines().Append(usingDefaultsRun);
@ -266,8 +263,8 @@ namespace winrt::TerminalApp::implementation
// when this is called, nothing happens. See _ShowDialog for details
void App::_ShowLoadWarningsDialog()
{
auto title = _resourceLoader->GetLocalizedString(L"SettingsValidateErrorTitle");
auto buttonText = _resourceLoader->GetLocalizedString(L"Ok");
auto title = RS_(L"SettingsValidateErrorTitle");
auto buttonText = RS_(L"Ok");
Controls::TextBlock warningsTextBlock;
// Make sure you can copy-paste
@ -279,7 +276,7 @@ namespace winrt::TerminalApp::implementation
for (const auto& warning : warnings)
{
// Try looking up the warning message key for each warning.
const auto warningText = _GetWarningText(warning, *_resourceLoader);
const auto warningText = _GetWarningText(warning);
if (!warningText.empty())
{
warningsTextBlock.Inlines().Append(_BuildErrorRun(warningText, Resources()));
@ -307,8 +304,8 @@ namespace winrt::TerminalApp::implementation
{
if (FAILED(_settingsLoadedResult))
{
const winrt::hstring titleKey = L"InitialJsonParseErrorTitle";
const winrt::hstring textKey = L"InitialJsonParseErrorText";
const winrt::hstring titleKey = USES_RESOURCE(L"InitialJsonParseErrorTitle");
const winrt::hstring textKey = USES_RESOURCE(L"InitialJsonParseErrorText");
_ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult);
}
else if (_settingsLoadedResult == S_FALSE)
@ -432,7 +429,7 @@ namespace winrt::TerminalApp::implementation
catch (const ::TerminalApp::SettingsException& ex)
{
hr = E_INVALIDARG;
_settingsLoadExceptionText = _GetErrorText(ex.Error(), *_resourceLoader);
_settingsLoadExceptionText = _GetErrorText(ex.Error());
}
catch (...)
{
@ -561,8 +558,8 @@ namespace winrt::TerminalApp::implementation
if (FAILED(_settingsLoadedResult))
{
_root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
const winrt::hstring titleKey = L"ReloadJsonParseErrorTitle";
const winrt::hstring textKey = L"ReloadJsonParseErrorText";
const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
_ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult);
});

View File

@ -58,8 +58,6 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr<::TerminalApp::CascadiaSettings> _settings{ nullptr };
std::shared_ptr<ScopedResourceLoader> _resourceLoader{ nullptr };
HRESULT _settingsLoadedResult;
winrt::hstring _settingsLoadExceptionText{};

View File

@ -5,6 +5,8 @@
#include "TerminalPage.h"
#include "Utils.h"
#include <LibraryResources.h>
#include "TerminalPage.g.cpp"
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
@ -32,14 +34,10 @@ namespace winrt
namespace winrt::TerminalApp::implementation
{
TerminalPage::TerminalPage() {}
TerminalPage::TerminalPage(std::shared_ptr<ScopedResourceLoader> resourceLoader) :
TerminalPage::TerminalPage() :
_tabs{}
{
InitializeComponent();
_resourceLoader = resourceLoader;
}
void TerminalPage::SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI)
@ -102,9 +100,9 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::ShowOkDialog(const winrt::hstring& titleKey,
const winrt::hstring& contentKey)
{
auto title = _resourceLoader->GetLocalizedString(titleKey);
auto message = _resourceLoader->GetLocalizedString(contentKey);
auto buttonText = _resourceLoader->GetLocalizedString(L"Ok");
auto title = GetLibraryResourceString(titleKey);
auto message = GetLibraryResourceString(contentKey);
auto buttonText = RS_(L"Ok");
WUX::Controls::ContentDialog dialog;
dialog.Title(winrt::box_value(title));
@ -120,14 +118,14 @@ namespace winrt::TerminalApp::implementation
// Notes link.
void TerminalPage::_ShowAboutDialog()
{
const auto title = _resourceLoader->GetLocalizedString(L"AboutTitleText");
const auto versionLabel = _resourceLoader->GetLocalizedString(L"VersionLabelText");
const auto gettingStartedLabel = _resourceLoader->GetLocalizedString(L"GettingStartedLabelText");
const auto documentationLabel = _resourceLoader->GetLocalizedString(L"DocumentationLabelText");
const auto releaseNotesLabel = _resourceLoader->GetLocalizedString(L"ReleaseNotesLabelText");
const auto gettingStartedUriValue = _resourceLoader->GetLocalizedString(L"GettingStartedUriValue");
const auto documentationUriValue = _resourceLoader->GetLocalizedString(L"DocumentationUriValue");
const auto releaseNotesUriValue = _resourceLoader->GetLocalizedString(L"ReleaseNotesUriValue");
const auto title = RS_(L"AboutTitleText");
const auto versionLabel = RS_(L"VersionLabelText");
const auto gettingStartedLabel = RS_(L"GettingStartedLabelText");
const auto documentationLabel = RS_(L"DocumentationLabelText");
const auto releaseNotesLabel = RS_(L"ReleaseNotesLabelText");
const auto gettingStartedUriValue = RS_(L"GettingStartedUriValue");
const auto documentationUriValue = RS_(L"DocumentationUriValue");
const auto releaseNotesUriValue = RS_(L"ReleaseNotesUriValue");
const auto package = winrt::Windows::ApplicationModel::Package::Current();
const auto packageName = package.DisplayName();
const auto version = package.Id().Version();
@ -171,7 +169,7 @@ namespace winrt::TerminalApp::implementation
winrt::hstring aboutText{ aboutTextStream.str() };
about.Text(aboutText);
const auto buttonText = _resourceLoader->GetLocalizedString(L"Ok");
const auto buttonText = RS_(L"Ok");
WUX::Controls::TextBlock aboutTextBlock;
aboutTextBlock.Inlines().Append(about);
@ -197,9 +195,9 @@ namespace winrt::TerminalApp::implementation
// when this is called, nothing happens. See _ShowDialog for details
void TerminalPage::_ShowCloseWarningDialog()
{
auto title = _resourceLoader->GetLocalizedString(L"CloseWindowWarningTitle");
auto primaryButtonText = _resourceLoader->GetLocalizedString(L"CloseAll");
auto secondaryButtonText = _resourceLoader->GetLocalizedString(L"Cancel");
auto title = RS_(L"CloseWindowWarningTitle");
auto primaryButtonText = RS_(L"CloseAll");
auto secondaryButtonText = RS_(L"Cancel");
WUX::Controls::ContentDialog dialog;
dialog.Title(winrt::box_value(title));
@ -279,7 +277,7 @@ namespace winrt::TerminalApp::implementation
{
// Create the settings button.
auto settingsItem = WUX::Controls::MenuFlyoutItem{};
settingsItem.Text(_resourceLoader->GetLocalizedString(L"SettingsMenuItem"));
settingsItem.Text(RS_(L"SettingsMenuItem"));
WUX::Controls::SymbolIcon ico{};
ico.Symbol(WUX::Controls::Symbol::Setting);
@ -296,7 +294,7 @@ namespace winrt::TerminalApp::implementation
// Create the feedback button.
auto feedbackFlyout = WUX::Controls::MenuFlyoutItem{};
feedbackFlyout.Text(_resourceLoader->GetLocalizedString(L"FeedbackMenuItem"));
feedbackFlyout.Text(RS_(L"FeedbackMenuItem"));
WUX::Controls::FontIcon feedbackIcon{};
feedbackIcon.Glyph(L"\xE939");
@ -308,7 +306,7 @@ namespace winrt::TerminalApp::implementation
// Create the about button.
auto aboutFlyout = WUX::Controls::MenuFlyoutItem{};
aboutFlyout.Text(_resourceLoader->GetLocalizedString(L"AboutMenuItem"));
aboutFlyout.Text(RS_(L"AboutMenuItem"));
WUX::Controls::SymbolIcon aboutIcon{};
aboutIcon.Symbol(WUX::Controls::Symbol::Help);
@ -509,7 +507,7 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_FeedbackButtonOnClick(const IInspectable&,
const RoutedEventArgs&)
{
const auto feedbackUriValue = _resourceLoader->GetLocalizedString(L"FeedbackUriValue");
const auto feedbackUriValue = RS_(L"FeedbackUriValue");
winrt::Windows::System::Launcher::LaunchUriAsync({ feedbackUriValue });
}

View File

@ -9,7 +9,6 @@
#include "Tab.h"
#include "CascadiaSettings.h"
#include "Profile.h"
#include "ScopedResourceLoader.h"
#include <winrt/Microsoft.Terminal.TerminalControl.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
@ -24,8 +23,6 @@ namespace winrt::TerminalApp::implementation
public:
TerminalPage();
TerminalPage(std::shared_ptr<ScopedResourceLoader> resourceLoader);
void SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI);
void Create();
@ -60,8 +57,6 @@ namespace winrt::TerminalApp::implementation
std::vector<std::shared_ptr<Tab>> _tabs;
std::shared_ptr<ScopedResourceLoader> _resourceLoader{ nullptr };
void _ShowAboutDialog();
void _ShowCloseWarningDialog();

View File

@ -2,6 +2,7 @@
// Licensed under the MIT license.
#include "pch.h"
#include <LibraryResources.h>
// Note: Generate GUID using TlgGuid.exe tool
TRACELOGGING_DEFINE_PROVIDER(
@ -29,3 +30,5 @@ BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/)
return TRUE;
}
UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"TerminalApp/Resources")

View File

@ -114,7 +114,6 @@
<ClCompile Include="../JsonUtils.cpp" />
<ClCompile Include="../Utils.cpp" />
<ClCompile Include="../DefaultProfileUtils.cpp" />
<ClCompile Include="../ScopedResourceLoader.cpp" />
<ClCompile Include="../PowershellCoreProfileGenerator.cpp" />
<ClCompile Include="../WslDistroGenerator.cpp" />
<ClCompile Include="../AzureCloudShellGenerator.cpp" />
@ -184,6 +183,11 @@
-->
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\WinRTUtils\WinRTUtils.vcxproj">
<Project>{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<!-- For whatever reason, we can't include the TerminalControl and
TerminalSettings projects' winmds via project references. So we'll have to
manually include the winmds as References below -->

View File

@ -24,7 +24,6 @@
#include <hstring.h>
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/windows.ui.core.h>

View File

@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ScopedResourceLoader.h"
#include "LibraryResources.h"
/*
CHECKED RESOURCES
This is the support infrastructure for "checked resources", a system that lets
us immediately failfast (on launch) when a library makes a static reference to
a resource that doesn't exist at runtime.
Resource checking relies on diligent use of USES_RESOURCE() and RS_() (which
uses USES_RESOURCE), but can make sure we don't ship something that'll blow up
at runtime.
It works like this:
** IN DEBUG MODE **
- All resource names referenced through USES_RESOURCE() are emitted alongside
their referencing filenames and line numbers into a static section of the
binary.
That section is named .util$res$m.
- We emit two sentinel values into two different sections, .util$res$a and
.util$res$z.
- The linker sorts all sections alphabetically before crushing them together
into the final binary.
- When we first construct our library's scoped resource loader, we iterate over
every resource reference between $a and $z and check residency.
** IN RELEASE MODE **
- All checked resource code is compiled out.
*/
extern const wchar_t* g_WinRTUtilsLibraryResourceScope;
#ifdef _DEBUG
#pragma detect_mismatch("winrt_utils_debug", "1")
#pragma section(".util$res$a", read)
#pragma section(".util$res$z", read)
__declspec(allocate(".util$res$a")) static const ::Microsoft::Console::Utils::StaticResource* debugResFirst{ nullptr };
__declspec(allocate(".util$res$z")) static const ::Microsoft::Console::Utils::StaticResource* debugResLast{ nullptr };
static void EnsureAllResourcesArePresent(const ScopedResourceLoader& loader)
{
for (auto resp = &debugResFirst; resp != &debugResLast; ++resp)
{
if (*resp)
{
const auto& res = **resp;
if (!loader.HasResourceWithName(res.resourceKey))
{
auto filename = wcsrchr(res.filename, L'\\');
if (!filename)
{
filename = res.filename;
}
else
{
filename++; // skip the '\'
}
FAIL_FAST_MSG("Resource %ls not found in scope %ls (%ls:%u)", res.resourceKey, g_WinRTUtilsLibraryResourceScope, filename, res.line);
}
}
}
}
#else // _DEBUG
#pragma detect_mismatch("winrt_utils_debug", "0")
#endif
static ScopedResourceLoader GetLibraryResourceLoader() UTILS_NONDEBUG_NOEXCEPT
{
ScopedResourceLoader loader{ g_WinRTUtilsLibraryResourceScope };
#ifdef _DEBUG
EnsureAllResourcesArePresent(loader);
#endif
return loader;
}
winrt::hstring GetLibraryResourceString(const std::wstring_view key) UTILS_NONDEBUG_NOEXCEPT
{
static auto loader{ GetLibraryResourceLoader() };
return loader.GetLocalizedString(key);
}

View File

@ -17,7 +17,7 @@ ScopedResourceLoader::ScopedResourceLoader(const std::wstring_view resourceLocat
// - Gets the resource map associated with the scoped resource subcompartment.
// Return Value:
// - the resource map associated with the scoped resource subcompartment.
ResourceMap ScopedResourceLoader::GetResourceMap()
ResourceMap ScopedResourceLoader::GetResourceMap() const
{
return _resourceMap;
}
@ -31,7 +31,18 @@ ResourceMap ScopedResourceLoader::GetResourceMap()
// - resourceName: the key up by which to look the resource
// Return Value:
// - The final localized string for the given key.
winrt::hstring ScopedResourceLoader::GetLocalizedString(const std::wstring_view resourceName)
winrt::hstring ScopedResourceLoader::GetLocalizedString(const std::wstring_view resourceName) const
{
return _resourceMap.GetValue(resourceName, _resourceContext).ValueAsString();
}
// Method Description:
// - Returns whether this resource loader can find a resource with the given key.
// Arguments:
// - resourceName: the key up by which to look the resource
// Return Value:
// - A boolean indicating whether the resource was found
bool ScopedResourceLoader::HasResourceWithName(const std::wstring_view resourceName) const
{
return _resourceMap.HasKey(resourceName);
}

View File

@ -7,8 +7,9 @@ class ScopedResourceLoader
{
public:
ScopedResourceLoader(const std::wstring_view resourceLocatorBase);
winrt::Windows::ApplicationModel::Resources::Core::ResourceMap GetResourceMap();
winrt::hstring GetLocalizedString(const std::wstring_view resourceName);
winrt::Windows::ApplicationModel::Resources::Core::ResourceMap GetResourceMap() const;
winrt::hstring GetLocalizedString(const std::wstring_view resourceName) const;
bool HasResourceWithName(const std::wstring_view resourceName) const;
private:
winrt::Windows::ApplicationModel::Resources::Core::ResourceMap _resourceMap;

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<!-- ========================= CppWinRT Metadata ======================== -->
<PropertyGroup>
<ConfigurationType>StaticLibrary</ConfigurationType>
<SubSystem>Console</SubSystem>
<OpenConsoleUniversalApp>true</OpenConsoleUniversalApp>
</PropertyGroup>
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="inc\ScopedResourceLoader.h" />
<ClInclude Include="inc\LibraryResources.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ScopedResourceLoader.cpp" />
<ClCompile Include="LibraryResources.cpp" />
</ItemGroup>
<!-- ========================= idl Files ======================== -->
<ItemGroup>
</ItemGroup>
<!-- ========================= Misc Files ======================== -->
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<!-- ========================= Project References ======================== -->
<ItemGroup>
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
</ItemGroup>
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<!-- ========================= Globals ======================== -->
<PropertyGroup Label="Globals">
<ProjectGuid>{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>WinRTUtils</RootNamespace>
<ProjectName>WinRTUtils</ProjectName>
<TargetName>WinRTUtils</TargetName>
</PropertyGroup>
<PropertyGroup>
<!--
DON'T REDIRECT OUR OUTPUT.
Setting this will tell cppwinrt.build.post.props to copy our output from
the default OutDir up one level, so the wapproj will be able to find it.
-->
<NoOutputRedirection>true</NoOutputRedirection>
</PropertyGroup>
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
</Project>

View File

@ -0,0 +1,67 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
/*
USING RESOURCES
To use PRI resources that are included alongside your library:
- In one file, use
UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"ResourceScope"). The
provided scope will be used as the basename for all library
resource lookups with RS_() or GetLibraryLocalizedString().
- Use RS_(L"ResourceName") for all statically-known resource
names.
- Use GetLibraryResourceString(string) for all resource lookups
for keys known only at runtime.
- For any static resource lookups that are deferred through
another function call, use USES_RESOURCE(L"Key") to ensure the
key is tracked.
*/
#ifdef _DEBUG
/*
The definitions in this section exist to support checked resources.
Check out the comment in LibraryResources.cpp to learn more.
*/
// Don't let non-debug and debug builds live together.
#pragma detect_mismatch("winrt_utils_debug", "1")
#pragma section(".util$res$m", read)
namespace Microsoft::Console::Utils
{
struct StaticResource
{
const wchar_t* resourceKey;
const wchar_t* filename;
unsigned int line;
};
}
#define USES_RESOURCE(x) ([]() { \
static const ::Microsoft::Console::Utils::StaticResource res{ \
(x), __FILEW__, __LINE__ \
}; \
__declspec(allocate(".util$res$m")) static auto pRes{ &res }; \
return pRes->resourceKey; \
}())
#define RS_(x) GetLibraryResourceString(USES_RESOURCE(x))
#define UTILS_NONDEBUG_NOEXCEPT
#else // _DEBUG
#pragma detect_mismatch("winrt_utils_debug", "0")
#define USES_RESOURCE(x) (x)
#define RS_(x) GetLibraryResourceString((x))
#define UTILS_NONDEBUG_NOEXCEPT noexcept
#endif
#define UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(x) \
__declspec(selectany) extern const wchar_t* g_WinRTUtilsLibraryResourceScope{ (x) };
winrt::hstring GetLibraryResourceString(const std::wstring_view key) UTILS_NONDEBUG_NOEXCEPT;

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
</packages>

View File

@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"

View File

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// pch.h
// Header for platform projection include files
//
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <LibraryIncludes.h>
// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
// SDK definition of this function, so the only fix is to undef it.
// from WinBase.h
// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif
#include <wil/cppwinrt.h>
#include <unknwn.h>
#include <hstring.h>
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.System.h>

View File

@ -126,7 +126,7 @@
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\gsl\include;$(OpenConsoleDir)\dep\wil\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\src\cascadia\WinRTUtils\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\gsl\include;$(OpenConsoleDir)\dep\wil\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>