Skip to content

Commit

Permalink
Introduce a WinRT utils library and checked resources
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
DHowett committed Oct 28, 2019
1 parent 634687b commit dca08d8
Show file tree
Hide file tree
Showing 16 changed files with 354 additions and 67 deletions.
21 changes: 21 additions & 0 deletions OpenConsole.sln
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LocalTests_TerminalApp", "s
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winconpty", "src\winconpty\winconpty.vcxproj", "{58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}"
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
Expand Down Expand Up @@ -1257,6 +1259,24 @@ Global
{58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|x64.Build.0 = Release|x64
{58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|x86.ActiveCfg = Release|Win32
{58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.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
Expand Down Expand Up @@ -1321,6 +1341,7 @@ Global
{CA5CAD1A-9A12-429C-B551-8562EC954746} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {59840756-302F-44DF-AA47-441A9D673202}
{58A03BB2-DF5A-4B66-91A0-7EF3BA01269A} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE} = {59840756-302F-44DF-AA47-441A9D673202}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
Expand Down
55 changes: 26 additions & 29 deletions src/cascadia/TerminalApp/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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

Expand All @@ -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 {};
}
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -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
Expand All @@ -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()));
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 (...)
{
Expand Down Expand Up @@ -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);
});

Expand Down
2 changes: 0 additions & 2 deletions src/cascadia/TerminalApp/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -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{};

Expand Down
46 changes: 22 additions & 24 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "TerminalPage.h"
#include "Utils.h"

#include <LibraryResources.h>

#include "TerminalPage.g.cpp"
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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));
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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));
Expand Down Expand Up @@ -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);
Expand All @@ -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");
Expand All @@ -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);
Expand Down Expand Up @@ -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 });
}
Expand Down
5 changes: 0 additions & 5 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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();
Expand Down Expand Up @@ -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();

Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalApp/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -29,3 +30,5 @@ BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/)

return TRUE;
}

UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"TerminalApp/Resources")
Loading

0 comments on commit dca08d8

Please sign in to comment.