C++/WinRT의 문자열 처리

C++/WinRT에서는 std::wstring 같은 C++ 표준 라이브러리 전각 문자열 형식을 사용해 Windows 런타임 API를 호출할 수 있습니다(참고: std::string 같은 반각 문자열 형식은 사용할 수 없음). C++/WinRT에는 winrt::hstring이라고 불리는 사용자 지정 문자열 형식이 없습니다(C++/WinRT 기본 라이브러리인 %WindowsSdkDir%Include\<WindowsTargetPlatformVersion>\cppwinrt\winrt\base.h에 정의됨). 또한 Windows 런타임 생성자, 함수 및 속성이 실제로 가져와 반환하는 문자열 형식이기도 합니다. 하지만 대부분 경우 hstring의 변환 생성자와 변환 연산자 덕분에 클라이언트 코드의 hstring 인식 여부를 선택할 수 있습니다. API를 직접 ‘작성’하는 경우에는 hstring에 대해 알아둘 필요가 더욱 많습니다.

C++에는 문자열 형식이 많습니다. 또한 C++ 표준 라이브러리의 std::basic_string 외에도 변형된 형식이 여러 라이브러리에 존재합니다. C++17에는 다양한 문자열 변환 유틸리티를 비롯해 std::basic_string_view도 있기 때문에 모든 문자열 형식의 차이를 좁힐 수 있습니다. winrt::hstringstd::basic_string_view가 디자인된 목적인 상호 운용성을 제공하기 위해 std::wstring_view와의 상호 운용성을 제공합니다.

std::wstring(선택적으로 winrt::hstring)에 Uri 사용

Windows::Foundation::Uriwinrt::hstring에서 생성됩니다.

public:
    Uri(winrt::hstring uri) const;

하지만 hstring에는 변환 생성자가 있기 때문에 잘 모르더라도 작업하는 데 문제가 없습니다. 다음은 와이드 문자열 리터럴에서, 와이드 문자열 보기에서, 그리고 std::wstring에서 Uri를 생성하는 방법을 나타낸 코드 예제입니다.

#include <winrt/Windows.Foundation.h>
#include <string_view>

using namespace winrt;
using namespace Windows::Foundation;

int main()
{
    using namespace std::literals;

    winrt::init_apartment();

    // You can make a Uri from a wide string literal.
    Uri contosoUri{ L"http://www.contoso.com" };

    // Or from a wide string view.
    Uri contosoSVUri{ L"http://www.contoso.com"sv };

    // Or from a std::wstring.
    std::wstring wideString{ L"http://www.adventure-works.com" };
    Uri awUri{ wideString };
}

속성 접근자인 Uri::Domainhstring 형식입니다.

public:
    winrt::hstring Domain();

다시 얘기하지만 hstring에는 std::wstring_view로 변환할 수 있는 연산자가 있기 때문에 세부 정보를 반드시 알아야 하는 것은 아닙니다.

// Access a property of type hstring, via a conversion operator to a standard type.
std::wstring domainWstring{ contosoUri.Domain() }; // L"contoso.com"
domainWstring = awUri.Domain(); // L"adventure-works.com"

// Or, you can choose to keep the hstring unconverted.
hstring domainHstring{ contosoUri.Domain() }; // L"contoso.com"
domainHstring = awUri.Domain(); // L"adventure-works.com"

마찬가지로 IStringable::ToString 역시 hstring을 반환합니다.

public:
    hstring ToString() const;

UriIStringable 인터페이스를 구현합니다.

// Access hstring's IStringable::ToString, via a conversion operator to a standard type.
std::wstring tostringWstring{ contosoUri.ToString() }; // L"http://www.contoso.com/"
tostringWstring = awUri.ToString(); // L"http://www.adventure-works.com/"

// Or you can choose to keep the hstring unconverted.
hstring tostringHstring{ contosoUri.ToString() }; // L"http://www.contoso.com/"
tostringHstring = awUri.ToString(); // L"http://www.adventure-works.com/"

hstring::c_str 함수를 사용하여 표준 전각 함수를 hstring에서 가져올 수 있습니다(std::wstring에서 가져오는 방식과 동일).

#include <iostream>
std::wcout << tostringHstring.c_str() << std::endl;

hstring이 있는 경우에는 여기에서 Uri를 생성할 수 있습니다.

Uri awUriFromHstring{ tostringHstring };

hstring을 가져오는 메서드를 고려하세요.

public:
    Uri CombineUri(winrt::hstring relativeUri) const;

앞에서 방금 보았던 옵션들 역시 모두 이러한 경우에 적용됩니다.

std::wstring contact{ L"contact" };
contosoUri = contosoUri.CombineUri(contact);
    
std::wcout << contosoUri.ToString().c_str() << std::endl;

hstring은 멤버로서 std::wstring_view 변환 연산자가 있기 때문에 아무런 대가 없이 변환이 이루어집니다.

void legacy_print(std::wstring_view view);

void Print(winrt::hstring const& hstring)
{
    legacy_print(hstring);
}

winrt::hstring 함수와 연산자

winrt::hstring일 때는 다양한 생성자, 연산자, 함수 및 반복기가 구현됩니다.

hstring은 범위이기 때문에 범위 기반 for와 함께, 혹은 std::for_each와 함께 사용할 수 있습니다. 또한 C++ 표준 라이브러리에서 상응하는 항목을 자연스럽게, 그리고 효율적으로 비교할 수 있는 비교 연산자도 제공합니다. 여기에는 hstring을 연결 컨테이너의 키로 사용하는 데 필요한 모든 것이 포함됩니다.

다수의 C++ 라이브러리가 std::string을 사용하며, UTF-8 텍스트에서만 유효하다는 사실은 이미 잘 알고 있습니다. 편의상 앞뒤로 변환을 위해 winrt::to_stringwinrt::to_hstring과 같은 도우미를 제공합니다.

WINRT_ASSERT는 매크로 정의이며 _ASSERTE로 확장됩니다.

winrt::hstring w{ L"Hello, World!" };

std::string c = winrt::to_string(w);
WINRT_ASSERT(c == "Hello, World!");

w = winrt::to_hstring(c);
WINRT_ASSERT(w == L"Hello, World!");

hstring 함수 및 연산자에 대한 자세한 예제와 내용은 winrt::hstring API 참조 항목을 참조하세요.

winrt::hstringwinrt::param::hstring의 이론적 근거

Windows 런타임은 wchar_t 문자와 관련하여 구현되지만 Windows 런타임의 ABI(Application Binary Interface)는 std::wstring 또는 std::wstring_view가 제공하는 것의 하위 세트가 아닙니다. 따라서 이 둘을 사용하면 상당한 비효율성으로 이어질 수 있습니다. 대신 C++/WinRT는 winrt::hstring을 제공합니다. 이는 기본 HSTRING을 따를 뿐만 아니라 std::wstring 인터페이스와 비슷한 인터페이스 뒤에서 구현되기 때문에 문자열을 변경할 수 없다는 것을 의미합니다.

winrt::hstring을 논리적으로 허용해야 하는 C++/WinRT 입력 매개 변수에 실제로 winrt::param::hstring이 필요하다는 사실을 확인할 수 있습니다. param 네임스페이스에는 C++ 표준 라이브러리 형식에 자연스럽게 바인딩하여 복사본과 기타 비효율성을 회피할 수 있도록 입력 매개 변수를 최적화하는 데만 사용되는 형식 세트만 포함됩니다. 이 형식을 직접 사용해서는 안 됩니다. 사용자 고유의 함수에 최적화를 사용하려면 std::wstring_view를 사용하세요. 매개 변수를 ABI 경계로 전달도 참조하세요.

결론적으로 Windows 런타임 문자열 관리를 위한 고유 정보는 대부분 무시하고 알고 있는 정보만으로도 효율적으로 작업할 수 있습니다. 문자열이 Windows 런타임에서 얼마나 많이 사용되는지 생각해보면 이는 매우 중요합니다.

문자열 형식 지정

문자열 형식을 지정하는 한 가지 옵션은 std::wostringstream입니다. 다음은 간단한 디버그 추적 메시지의 형식을 지정하여 표시하는 예제입니다.

#include <sstream>
#include <winrt/Windows.UI.Input.h>
#include <winrt/Windows.UI.Xaml.Input.h>
...
void MainPage::OnPointerPressed(winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e)
{
    winrt::Windows::Foundation::Point const point{ e.GetCurrentPoint(nullptr).Position() };
    std::wostringstream wostringstream;
    wostringstream << L"Pointer pressed at (" << point.X << L"," << point.Y << L")" << std::endl;
    ::OutputDebugString(wostringstream.str().c_str());
}

속성을 설정하는 올바른 방법

setter 함수로 값을 전달하여 속성을 설정합니다. 예제는 다음과 같습니다.

// The right way to set the Text property.
myTextBlock.Text(L"Hello!");

아래는 잘못된 코드입니다. 컴파일할 수는 있지만 Text() 접근자 함수에서 반환된 임시 winrt::hstring을 수정한 다음, 결과를 버리는 것이 전부입니다.

// *Not* the right way to set the Text property.
myTextBlock.Text() = L"Hello!";

중요 API