Implement WinRT / C# projection for the WSLC SDK API surface#40360
Implement WinRT / C# projection for the WSLC SDK API surface#40360florelis wants to merge 11 commits into
Conversation
|
The |
| // 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); |
| if (m_cmdLine.Size() > 0) | ||
| { | ||
| auto argc = m_cmdLine.Size(); | ||
| m_cmdLineStrings = StringArray{argc}; | ||
| for (auto const& arg : m_cmdLine) |
| WSLC_TEST_METHOD(StopContainerWithInvalidSignal) | ||
| { | ||
| auto procSettings = WSLCSDK::ProcessSettings(); | ||
| procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"10"})); | ||
|
|
There was a problem hiding this comment.
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:1winrt::throw_hresultexpects an HRESULT, butERROR_NOT_SUPPORTEDis a Win32 error code (DWORD). This will throw the wrong HRESULT. UseHRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)(or throw a WinRT-appropriate HRESULT likeE_NOTIMPL) so callers get the correct error.
src/windows/WslcSDK/winrt/WslcService.cpp:1- This callback is marked
noexceptbut it allocates WinRT objects and calls into user-provided progress handlers (via the progress token). Any exception here will callstd::terminate. Wrap the body in atry/catch(...)and swallow/log failures (or redesign to return an HRESULT if the C API supports it).
test/windows/WslcSdkWinRTTests.cpp:1 RunContainerOptions::flagsis never applied tocontainerSettingsinRunContainerAndWaitForExit, so callers cannot actually set flags through this helper. Either remove the option or setcontainerSettings.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)); |
| 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())); |
| 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(); | ||
| } |
| 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); |
| ImageInfo::ImageInfo(WslcImageInfo const& info) : m_info(info) | ||
| { | ||
| } | ||
|
|
||
| hstring ImageInfo::Name() | ||
| { | ||
| throw hresult_not_implemented(); | ||
| return winrt::to_hstring(m_info.name); | ||
| } |
JohnMcPMS
left a comment
There was a problem hiding this comment.
Still need to look over the 3 complex files tomorrow.
| return m_container.get(); | ||
| } | ||
|
|
||
| void Container::EnsureCreated() |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
| 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.
Summary of the Pull Request
This PR the C++/WinRT wrapper for the WSLC SDK, which is then projected to C#.
PR Checklist
Detailed Description of the Pull Request / Additional comments
This builds on top of #40121 and #40212. The relevant changes for this are only the
.hand.cppfiles.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:
implementationtypes that don't have one in the public APIhstringbut as a nativestd::(w)stringto directly back any string pointers passed to the C API.IReference<>(projected to C# as a nullable type) andnullptrto represent the default.ToStruct()orToStructPointer()(depending on usage) to get and retrieve the equivalentstructfor the C API. This nativestructis 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 nativestructwas already created.For the types that are only constructed by the API:
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