Skip to content

Implement WinRT / C# projection for the WSLC SDK API surface#40360

Open
florelis wants to merge 11 commits into
microsoft:masterfrom
florelis:csFullApi
Open

Implement WinRT / C# projection for the WSLC SDK API surface#40360
florelis wants to merge 11 commits into
microsoft:masterfrom
florelis:csFullApi

Conversation

@florelis
Copy link
Copy Markdown
Member

@florelis florelis commented Apr 29, 2026

Summary of the Pull Request

This PR the C++/WinRT wrapper for the WSLC SDK, which is then projected to C#.

PR Checklist

  • Closes: Link to issue #xxx
  • Communication: I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected
  • Tests: Added/updated if needed and all pass
  • Localization: All end user facing strings can be localized
  • Dev docs: Added/updated if needed
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

This builds on top of #40121 and #40212. The relevant changes for this are only the .h and .cpp files.

The implementation for all types follows a similar pattern, but there are some differences between types that are meant to be constructed by the consumer (e.g., SessionSettings) and types that can only be obtained by a function call (e.g., Session).

For types constructed by the consumer:

  • Added constructors to the implementation types that don't have one in the public API
  • Declared private fields to back all the properties
  • All getters and setters use the backing fields directly
  • Setters perform minimal validation (e.g., non-empty strings)
  • String values are not stored as a WinRT hstring but as a native std::(w)string to directly back any string pointers passed to the C API.
  • Properties that in the C API use 0 to represent the default value, here use instead an IReference<> (projected to C# as a nullable type) and nullptr to represent the default.
  • Added a ToStruct() or ToStructPointer() (depending on usage) to get and retrieve the equivalent struct for the C API. This native struct is cached so that we only create it once. To avoid dealing with updating the native object if a property is changed, I blocked all setters if the native struct was already created.

For the types that are only constructed by the API:

  • Added a backing field for pointer to the handle returned by the API
  • Most functions are just forwarding parameters to the handle
  • The destructor takes care of releasing them

For the few properties that are lists (e.g., ContainerPortMapping), I created two backing vectors. One is for the WinRT objects that are used when interacting with the consumer, and the other is for backing the pointers passed to the C API.

For callbacks, we use events that are configured before calling Start() on the object, and we pass the C API a callback that invokes the event handler.

The tests are more or less a direct translation of the tests for the C API. The tests show some issues that I have not resolved yet.

Validation Steps Performed

Copilot AI review requested due to automatic review settings April 29, 2026 23:11

This comment was marked as outdated.

@benhillis
Copy link
Copy Markdown
Member

The feature/wsl-for-apps branch is now defunct. If your PR is still valid, please retarget the master branch. Reach out to @benhillis for help if needed.

@florelis florelis changed the base branch from feature/wsl-for-apps to master April 30, 2026 21:41
Copilot AI review requested due to automatic review settings April 30, 2026 21:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI review requested due to automatic review settings May 11, 2026 00:20

This comment was marked as outdated.

@florelis florelis changed the title Implement WinRT / C# projection for the simple parts of the WSLC SDK API surface Implement WinRT / C# projection for the WSLC SDK API surface May 11, 2026
Copilot AI review requested due to automatic review settings May 11, 2026 15:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated 12 comments.

Comment thread src/windows/WslcSDK/winrt/Session.cpp
Comment thread src/windows/WslcSDK/winrt/Process.cpp
// This takes ownership without increasing the ref count to account for the AddRef in ApplyCallbacksToSettings().
process.attach(static_cast<Process*>(context));

process->m_exitedEvent(*process, exitCode);
Comment thread src/windows/WslcSDK/winrt/Streams.cpp
Comment on lines +99 to +103
if (m_cmdLine.Size() > 0)
{
auto argc = m_cmdLine.Size();
m_cmdLineStrings = StringArray{argc};
for (auto const& arg : m_cmdLine)
Comment thread test/windows/WslcSdkWinRTTests.cpp
Comment thread test/windows/WslcSdkWinRTTests.cpp
Comment thread test/windows/WslcSdkWinRTTests.cpp
Comment on lines +1488 to +1492
WSLC_TEST_METHOD(StopContainerWithInvalidSignal)
{
auto procSettings = WSLCSDK::ProcessSettings();
procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"10"}));

Comment thread src/windows/WslcSDK/winrt/Container.cpp
@florelis florelis requested a review from Copilot May 14, 2026 22:46
@florelis florelis marked this pull request as ready for review May 14, 2026 22:49
@florelis florelis requested a review from a team as a code owner May 14, 2026 22:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 43 out of 43 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (6)

test/windows/CMakeLists.txt:1

  • ${CMAKE_BUILD_TYPE} is empty with multi-config generators (e.g., Visual Studio), which will break the include path for generated WinRT headers. Use a generator expression (e.g., $<CONFIG>) or ${CMAKE_CFG_INTDIR} to select the config-specific output directory.
    src/windows/WslcSDK/winrt/Streams.cpp:1
  • winrt::throw_hresult expects an HRESULT, but ERROR_NOT_SUPPORTED is a Win32 error code (DWORD). This will throw the wrong HRESULT. Use HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) (or throw a WinRT-appropriate HRESULT like E_NOTIMPL) so callers get the correct error.
    src/windows/WslcSDK/winrt/WslcService.cpp:1
  • This callback is marked noexcept but it allocates WinRT objects and calls into user-provided progress handlers (via the progress token). Any exception here will call std::terminate. Wrap the body in a try/catch(...) and swallow/log failures (or redesign to return an HRESULT if the C API supports it).
    test/windows/WslcSdkWinRTTests.cpp:1
  • RunContainerOptions::flags is never applied to containerSettings in RunContainerAndWaitForExit, so callers cannot actually set flags through this helper. Either remove the option or set containerSettings.Flags(options.flags).
    test/windows/WslcSdkWinRTTests.cpp:1
  • Fix duplicated word: 'used used' → 'used'.
    test/windows/WslcSdkWinRTTests.cpp:1
  • Typo in comment: 'Remote' → 'Remove'.

// This takes ownership without increasing the ref count to account for the AddRef in Start().
session.attach(static_cast<Session*>(context));

session->m_terminatedEvent(*session, static_cast<SessionTerminationReason>(reason));
Comment on lines +261 to +289
void CALLBACK Process::OutputCallback(WslcProcessIOHandle ioHandle, _In_reads_bytes_(dataBytes) const BYTE* data, _In_ uint32_t dataBytes, _In_opt_ PVOID context)
{
auto* self = static_cast<Process*>(context);
if (!self->HasExternalReferences())
{
return;
}

winrt::com_ptr<Process> process;
process.copy_from(self);

auto& outputEvent = (ioHandle == WSLC_PROCESS_IO_HANDLE_STDOUT) ? process->m_outputReceivedEvent : process->m_errorReceivedEvent;
if (outputEvent)
{
winrt::array_view<const uint8_t> buffer{data, dataBytes};
(*outputEvent)(*process, buffer);
}
}

void CALLBACK Process::ExitCallback(INT32 exitCode, _In_opt_ PVOID context)
{
winrt::com_ptr<Process> process;

// No other callback should be called after this event, so we no longer need to keep the object alive.
// This takes ownership without increasing the ref count to account for the AddRef in ApplyCallbacksToSettings().
process.attach(static_cast<Process*>(context));

process->m_exitedEvent(*process, exitCode);
}
bool callbacksApplied = false;
if (m_initProcess)
{
callbacksApplied = m_initProcess->ApplyCallbacksToSettings(GetStructPointer(GetImplementation(m_settings)->InitProcess()));
Comment on lines 112 to +125
void Process::Start()
{
throw hresult_not_implemented();
EnsureNotStarted();

if (ApplyCallbacksToSettings(GetStructPointer(m_settings)))
{
auto releaseRef = wil::scope_exit([this] { Release(); });

wil::unique_cotaskmem_string errorMessage;
auto hr = WslcCreateContainerProcess(GetHandle(m_container), GetStructPointer(m_settings), m_process.put(), errorMessage.put());
THROW_MSG_IF_FAILED(hr, errorMessage);

releaseRef.release();
}
Comment on lines 223 to 229
void Process::OutputReceived(winrt::event_token const& token) noexcept
{
assert(false); // TODO: not implemented, but this can't throw
if (m_outputReceivedEvent)
{
m_outputReceivedEvent->remove(token);
}
}
// This takes ownership without increasing the ref count to account for the AddRef in ApplyCallbacksToSettings().
process.attach(static_cast<Process*>(context));

process->m_exitedEvent(*process, exitCode);
Comment on lines +20 to 27
ImageInfo::ImageInfo(WslcImageInfo const& info) : m_info(info)
{
}

hstring ImageInfo::Name()
{
throw hresult_not_implemented();
return winrt::to_hstring(m_info.name);
}
Copy link
Copy Markdown
Member

@JohnMcPMS JohnMcPMS left a comment

Choose a reason for hiding this comment

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

Still need to look over the 3 complex files tomorrow.

return m_container.get();
}

void Container::EnsureCreated()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Per-discussion, want to add a ProcessSettings property that specifies the output handling type so that Container can be created after C SDK call always having the handle. Possibly as a follow up PR, although I'm liking waiting less as I go through this one.

releaseRef.release();
}

void Container::Stop(winrt::Microsoft::WSL::Containers::Signal const& signal, uint32_t timeoutSeconds)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Missed in IDL review: There is a TimeSpan type that we should use and can document the second resolution if needed. Applies to any other time spans.

throw hresult_not_implemented();
if (name.empty())
{
throw hresult_invalid_argument(L"Volume name cannot be empty");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

May need to be localized. I would suggest at least tagging these with some sort of macro for now, like WSLC_LOC_DEBT(L"...") so that they can be found in the future easily.

throw hresult_not_implemented();
if (m_containerNamedVolume)
{
throw hresult_illegal_state_change(L"Cannot change volume name after the options have been applied");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
throw hresult_illegal_state_change(L"Cannot change volume name after the options have been applied");
throw hresult_illegal_state_change(L"Cannot change value after options have been applied");

If these are to be localized, it would probably be better if all of these were the same string, like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants