2026-07-29 03:06:18 -07:00
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
|
|
|
|
namespace WslcApiDump;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// D13's open question, reduced to one run (docs/WINDOWS_PORT.md §13.2).
|
|
|
|
|
///
|
|
|
|
|
/// The plan routes container enumeration, session/container reattach and the Terminal panel's pty
|
|
|
|
|
/// to the service-internal COM interface `IWSLCSessionManager` (IID 82A7ABC8-…). Reading
|
|
|
|
|
/// `wslc.idl` turned up a problem the plan assumed away: **it declares interfaces and no
|
|
|
|
|
/// activatable class.** There is no CLSID in it, so `CoCreateInstance` has nothing to name, and
|
|
|
|
|
/// the only registered coclasses anywhere in WSL's IDLs belong to the *compat* surface and to the
|
|
|
|
|
/// WSL service proper.
|
|
|
|
|
///
|
|
|
|
|
/// The likely answer is that one of those coclasses also implements the internal interface — COM
|
|
|
|
|
/// objects routinely expose several — and `wslc.exe` simply QIs for it. That is a yes/no question,
|
|
|
|
|
/// and everything downstream depends on it:
|
|
|
|
|
///
|
|
|
|
|
/// * **yes** → write the internal arm (`ListContainers`, `OpenSessionByName`, `OpenContainer`,
|
|
|
|
|
/// `ResizeTty`), and a broker restart stops being fatal to a running sandbox.
|
|
|
|
|
/// * **no** → D13 needs reopening. The realistic alternatives are a durable host-side roster
|
|
|
|
|
/// plus `wsl --shutdown` on restart, or revisiting the rejected `wslc.exe` arm for enumeration.
|
|
|
|
|
///
|
|
|
|
|
/// Two stages, because they carry very different risk:
|
|
|
|
|
///
|
|
|
|
|
/// * <c>--internal</c> activates each candidate class and QIs for the internal IIDs. It never
|
|
|
|
|
/// calls a method, so **it cannot crash on a vtable mismatch** — and QI alone answers the
|
|
|
|
|
/// entry-point question.
|
|
|
|
|
/// * <c>--internal-call</c> additionally calls through the interface (`GetVersion`, then
|
|
|
|
|
/// `ListSessions`). This one CAN take the process down if the real vtable differs from the
|
|
|
|
|
/// IDL — which is itself a finding, and the reason it is opt-in rather than default.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal static class InternalComProbe
|
|
|
|
|
{
|
|
|
|
|
// The only registered coclasses in WSL's IDLs. wslc.idl contributes none — that is the
|
|
|
|
|
// problem — so every candidate here comes from WSLCCompat.idl or wslservice.idl.
|
|
|
|
|
private static readonly (string Name, Guid Clsid, string Source)[] Candidates =
|
|
|
|
|
[
|
|
|
|
|
("WSLCCompatSessionManager", new Guid("a9b7a1b9-0671-405c-95f1-e0612cb4ce8f"),
|
|
|
|
|
"WSLCCompat.idl — what the SDK itself activates, so the likeliest host"),
|
|
|
|
|
("WSLCCompatSessionManagerFactory", new Guid("9fcd2067-9fc6-4efa-9eb0-698169ebf7d3"),
|
|
|
|
|
"WSLCCompat.idl"),
|
|
|
|
|
("LxssUserSession", new Guid("a9b7a1b9-0671-405c-95f1-e0612cb4ce7e"),
|
|
|
|
|
"wslservice.idl — the WSL service proper; note it differs from the compat CLSID "
|
|
|
|
|
+ "only in the last byte (ce7e vs ce8f)"),
|
|
|
|
|
("LxssUserSessionInBox", new Guid("4f476546-b412-4579-b64c-123df331e3d6"),
|
|
|
|
|
"wslservice.idl"),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
private static readonly (string Name, Guid Iid, string Why)[] Interfaces =
|
|
|
|
|
[
|
|
|
|
|
("IWSLCSessionManager", new Guid("82A7ABC8-6B50-43FC-AB96-15FBBE7E8760"),
|
|
|
|
|
"THE one that matters — OpenSessionByName/EnterSession/ListSessions, i.e. reattach"),
|
|
|
|
|
("IWSLCSession", new Guid("EF0661E4-6364-40EA-B433-E2FDF11F3519"),
|
2026-07-29 03:20:15 -07:00
|
|
|
"ListContainers/OpenContainer. EXPECTED to fail here, and its failure is not a gap: a "
|
|
|
|
|
+ "session is RETURNED BY the manager (OpenSessionByName/EnterSession/CreateSession), "
|
|
|
|
|
+ "not QI'd off it. Probed only to confirm the manager is a manager and not a "
|
|
|
|
|
+ "do-everything object"),
|
2026-07-29 03:06:18 -07:00
|
|
|
("IWSLCVirtualMachine", new Guid("B5E2D8F1-9A3C-4E6B-8D1F-7C4A2E9B6D3A"),
|
|
|
|
|
"GetId → the VM GUID an AF_HYPERV bind needs. Per the IDL only a factory the SYSTEM "
|
|
|
|
|
+ "service owns can produce one, so this is EXPECTED to fail — probed anyway, "
|
|
|
|
|
+ "because §13.2 retracted a decision on that reading and it deserves confirming"),
|
|
|
|
|
];
|
|
|
|
|
|
2026-07-29 03:26:09 -07:00
|
|
|
internal static void Run(bool callThrough, string? sessionName = null)
|
2026-07-29 03:06:18 -07:00
|
|
|
{
|
2026-07-29 03:26:09 -07:00
|
|
|
InternalComProbe.sessionName = sessionName;
|
2026-07-29 03:06:18 -07:00
|
|
|
Console.WriteLine();
|
|
|
|
|
Console.WriteLine("internal COM probe (docs/WINDOWS_PORT.md §13.2):");
|
|
|
|
|
Console.WriteLine(" wslc.idl declares no coclass, so the question is whether an EXISTING");
|
|
|
|
|
Console.WriteLine(" class answers a QI for the internal interfaces.");
|
|
|
|
|
Console.WriteLine();
|
|
|
|
|
|
|
|
|
|
var initialized = CoInitializeEx(IntPtr.Zero, CoinitMultithreaded);
|
|
|
|
|
// S_FALSE means already initialized on this thread; RPC_E_CHANGED_MODE means the runtime
|
|
|
|
|
// picked the other apartment. Neither is fatal for an out-of-proc server.
|
|
|
|
|
if (initialized < 0 && (uint)initialized != RpcEChangedMode)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" CoInitializeEx failed: 0x{initialized:X8} — cannot probe");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var bound = false;
|
|
|
|
|
foreach (var (name, clsid, source) in Candidates)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" {name} {{{clsid}}}");
|
|
|
|
|
Console.WriteLine($" ({source})");
|
|
|
|
|
|
2026-07-29 03:10:00 -07:00
|
|
|
var hr = CoCreateInstance(in clsid, IntPtr.Zero, ClsctxAll, in IidIUnknown, out var unknown);
|
2026-07-29 03:06:18 -07:00
|
|
|
if (hr < 0)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" activation failed: {Hresult(hr)}");
|
2026-07-29 03:10:00 -07:00
|
|
|
if ((uint)hr == ENoInterface)
|
|
|
|
|
// Every COM object implements IUnknown, so this combination is impossible
|
|
|
|
|
// unless the IID being passed is not IID_IUnknown. Say so loudly: the first
|
|
|
|
|
// version of this probe did exactly that and reported a confident false
|
|
|
|
|
// negative that read like a real finding about WSL.
|
|
|
|
|
Console.WriteLine(" !! E_NOINTERFACE on an IUnknown activation is IMPOSSIBLE — "
|
|
|
|
|
+ "every COM object implements IUnknown. This probe is passing a wrong IID; "
|
|
|
|
|
+ "treat the whole run as void and fix it, do NOT record a finding.");
|
|
|
|
|
// A class factory is allowed to refuse IUnknown-first activation, so a failure
|
|
|
|
|
// here is not yet an answer: ask for each internal interface directly before
|
|
|
|
|
// concluding anything.
|
|
|
|
|
foreach (var (interfaceName, iid, _) in Interfaces)
|
|
|
|
|
{
|
|
|
|
|
var direct = iid;
|
|
|
|
|
var dhr = CoCreateInstance(in clsid, IntPtr.Zero, ClsctxAll, in direct, out var instance);
|
|
|
|
|
Console.WriteLine($" direct activation as {interfaceName}: "
|
|
|
|
|
+ (dhr >= 0 ? "**YES**" : Hresult(dhr)));
|
|
|
|
|
if (dhr < 0) continue;
|
|
|
|
|
if (interfaceName == "IWSLCSessionManager")
|
|
|
|
|
{
|
|
|
|
|
bound = true;
|
|
|
|
|
if (callThrough) CallThrough(instance);
|
|
|
|
|
}
|
|
|
|
|
Marshal.Release(instance);
|
|
|
|
|
}
|
2026-07-29 03:06:18 -07:00
|
|
|
Console.WriteLine();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
Console.WriteLine(" activated");
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
foreach (var (interfaceName, iid, why) in Interfaces)
|
|
|
|
|
{
|
|
|
|
|
var iidLocal = iid;
|
|
|
|
|
var qi = Marshal.QueryInterface(unknown, in iidLocal, out var candidate);
|
|
|
|
|
if (qi >= 0)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" QI {interfaceName}: **YES**");
|
|
|
|
|
if (interfaceName == "IWSLCSessionManager")
|
|
|
|
|
{
|
|
|
|
|
bound = true;
|
|
|
|
|
if (callThrough) CallThrough(candidate);
|
|
|
|
|
}
|
|
|
|
|
Marshal.Release(candidate);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" QI {interfaceName}: no ({Hresult(qi)})");
|
|
|
|
|
foreach (var line in Wrap(why)) Console.WriteLine($" {line}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
Marshal.Release(unknown);
|
|
|
|
|
}
|
|
|
|
|
Console.WriteLine();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Console.WriteLine(bound
|
|
|
|
|
? " RESULT: IWSLCSessionManager IS reachable. D13's internal arm has an entry point —\n"
|
|
|
|
|
+ " write it (ListContainers, OpenSessionByName, OpenContainer, ResizeTty)\n"
|
|
|
|
|
+ " and record the CLSID that answered in §13.2."
|
|
|
|
|
: " RESULT: IWSLCSessionManager is NOT reachable from any known coclass. D13 needs\n"
|
|
|
|
|
+ " reopening — see §13.2 for the two alternatives. Re-run with\n"
|
|
|
|
|
+ " --internal-call only if a QI above succeeded; it adds nothing here.");
|
|
|
|
|
if (!callThrough && bound)
|
|
|
|
|
Console.WriteLine(" (pass --internal-call to also CALL through it — confirms the vtable "
|
|
|
|
|
+ "matches the IDL, and can crash if it does not)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Prove the vtable is really the IDL's, not just that the IID is recognised. A QI can succeed
|
|
|
|
|
/// against an interface whose layout has since moved — `wslc.idl` says outright that breaking
|
|
|
|
|
/// changes to it are fine — and the first thing that would tell us is a crash here rather
|
|
|
|
|
/// than in the broker.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static void CallThrough(IntPtr manager)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(" calling through (vtable check):");
|
|
|
|
|
IWSLCSessionManager? proxy;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
proxy = (IWSLCSessionManager)Marshal.GetObjectForIUnknown(manager);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" could not build the RCW: {e.GetType().Name}: {e.Message}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var hr = proxy.GetVersion(out var version);
|
|
|
|
|
Console.WriteLine(hr >= 0
|
|
|
|
|
? $" GetVersion() = {version.Major}.{version.Minor}.{version.Revision} "
|
|
|
|
|
+ "— slot 3 matches the IDL"
|
|
|
|
|
: $" GetVersion() failed: {Hresult(hr)}");
|
|
|
|
|
// Cross-check against the compat SDK's own GetVersion: the same service answering
|
|
|
|
|
// both is what says these are two faces of one object rather than a coincidence.
|
|
|
|
|
if (hr >= 0)
|
|
|
|
|
Console.WriteLine(" compare with WslcService.GetVersion() above — they "
|
|
|
|
|
+ "should agree");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" GetVersion() threw {e.GetType().Name}: {e.Message}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var hr = proxy.ListSessions(out var sessions, out var count);
|
|
|
|
|
if (hr < 0)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" ListSessions() failed: {Hresult(hr)}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Console.WriteLine($" ListSessions() = {count} session(s)");
|
|
|
|
|
for (var i = 0; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
var entry = Marshal.PtrToStructure<WslcSessionListEntry>(
|
|
|
|
|
sessions + i * Marshal.SizeOf<WslcSessionListEntry>());
|
|
|
|
|
Console.WriteLine($" #{entry.SessionId} \"{entry.DisplayName}\" "
|
|
|
|
|
+ $"(creator pid {entry.CreatorPid})");
|
|
|
|
|
}
|
|
|
|
|
// The callee allocated it; nobody else will free it.
|
|
|
|
|
if (sessions != IntPtr.Zero) Marshal.FreeCoTaskMem(sessions);
|
2026-07-29 03:26:09 -07:00
|
|
|
if (count == 0)
|
|
|
|
|
// Worth saying, because "it returned S_OK" is weaker evidence than it looks: with
|
|
|
|
|
// no entries, the WSLCSessionListEntry layout — two DWORDs then two INLINE
|
|
|
|
|
// wchar_t buffers — was never actually unmarshalled. ListContainers uses the same
|
|
|
|
|
// `size_is(, *Count)` double-pointer shape, so that layout is still unproven.
|
|
|
|
|
Console.WriteLine(" (0 entries, so the ENTRY STRUCT layout is still "
|
|
|
|
|
+ "untested — run with a session up: --session --keep --internal-call)");
|
2026-07-29 03:06:18 -07:00
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" ListSessions() threw {e.GetType().Name}: {e.Message}");
|
2026-07-29 03:26:09 -07:00
|
|
|
return;
|
2026-07-29 03:06:18 -07:00
|
|
|
}
|
2026-07-29 03:26:09 -07:00
|
|
|
|
|
|
|
|
ProbeSessionHandoff(proxy);
|
2026-07-29 03:06:18 -07:00
|
|
|
}
|
|
|
|
|
|
2026-07-29 03:26:09 -07:00
|
|
|
/// <summary>
|
|
|
|
|
/// The question that decides the SHAPE of D13's internal arm.
|
|
|
|
|
///
|
|
|
|
|
/// `IWSLCSession` (internal, `EF0661E4-…`) and `IWSLCCompatSession` (SDK-facing,
|
|
|
|
|
/// `DD7B2EF9-…`) are different interfaces with different IIDs. `WSLCCompatSessionManager`
|
|
|
|
|
/// already proved one object can wear both faces — so if the session `OpenSessionByName`
|
|
|
|
|
/// returns ALSO answers a QI for the compat interface, then a re-adopted session can be
|
|
|
|
|
/// handed to `Session.FromAbi(ptr)` and driven by the **existing** facade code.
|
|
|
|
|
///
|
|
|
|
|
/// * **YES** → internal COM is used only to *find* things. Reattach costs one QI, and
|
|
|
|
|
/// nothing else in `WslcFacade` changes.
|
|
|
|
|
/// * **NO** → every re-adopted handle is internal-only, and start/stop/delete/exec each need
|
|
|
|
|
/// a second, hand-marshalled implementation. That is a much bigger arm, and it is the point
|
|
|
|
|
/// at which the C++/WinRT shim §13.1 mused about earns its keep.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static void ProbeSessionHandoff(IWSLCSessionManager proxy)
|
|
|
|
|
{
|
|
|
|
|
var name = sessionName;
|
|
|
|
|
if (string.IsNullOrEmpty(name))
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(" (no --session-name given; skipping the reattach handoff test)");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Console.WriteLine();
|
|
|
|
|
Console.WriteLine($" reattach handoff test — OpenSessionByName(\"{name}\"):");
|
|
|
|
|
int hr;
|
|
|
|
|
IntPtr session;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
hr = proxy.OpenSessionByName(name, out session);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" threw {e.GetType().Name}: {e.Message}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hr < 0)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" failed: {Hresult(hr)}");
|
|
|
|
|
Console.WriteLine(" (expected if no session of that name is running — create "
|
|
|
|
|
+ "one first: --session --keep --internal-call)");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Console.WriteLine(" opened — the session half of reattach WORKS");
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var compat = IidCompatSession;
|
|
|
|
|
var qi = Marshal.QueryInterface(session, in compat, out var projected);
|
|
|
|
|
if (qi >= 0)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(" QI IWSLCCompatSession: **YES**");
|
|
|
|
|
Console.WriteLine(" → a re-adopted session can be handed to "
|
|
|
|
|
+ "Session.FromAbi() and driven by the EXISTING compat facade code.");
|
|
|
|
|
Console.WriteLine(" → D13's internal arm shrinks to: find, then hand off.");
|
|
|
|
|
Marshal.Release(projected);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" QI IWSLCCompatSession: no ({Hresult(qi)})");
|
|
|
|
|
Console.WriteLine(" → a re-adopted session is internal-only, so every "
|
|
|
|
|
+ "operation on it needs a second hand-marshalled path.");
|
|
|
|
|
Console.WriteLine(" → that is the case where a C++/WinRT shim is worth it.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
Marshal.Release(session);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string? sessionName;
|
|
|
|
|
|
2026-07-29 03:06:18 -07:00
|
|
|
private static IEnumerable<string> Wrap(string text)
|
|
|
|
|
{
|
|
|
|
|
var words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
var line = new System.Text.StringBuilder();
|
|
|
|
|
foreach (var word in words)
|
|
|
|
|
{
|
|
|
|
|
if (line.Length + word.Length + 1 > 88 && line.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
yield return line.ToString();
|
|
|
|
|
line.Clear();
|
|
|
|
|
}
|
|
|
|
|
if (line.Length > 0) line.Append(' ');
|
|
|
|
|
line.Append(word);
|
|
|
|
|
}
|
|
|
|
|
if (line.Length > 0) yield return line.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string Hresult(int code) => (uint)code switch
|
|
|
|
|
{
|
|
|
|
|
0x80040154 => "REGDB_E_CLASSNOTREG — that class is not registered on this machine",
|
|
|
|
|
0x80004002 => "E_NOINTERFACE — the class does not implement it",
|
|
|
|
|
0x80070005 => "E_ACCESSDENIED — registered, but this token may not activate it",
|
|
|
|
|
0x800401F0 => "CO_E_NOTINITIALIZED",
|
|
|
|
|
_ => $"0x{code:X8}",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// MARK: - Interop
|
|
|
|
|
|
2026-07-29 03:10:00 -07:00
|
|
|
/// <summary>
|
|
|
|
|
/// `IID_IUnknown`, spelled out. **Do not** reach for `typeof(object).GUID` here — that is the
|
|
|
|
|
/// CLR's type GUID for `System.Object`, not `IID_IUnknown`, and activating with it asks every
|
|
|
|
|
/// class factory for an interface nothing implements. The failure is maximally misleading:
|
|
|
|
|
/// each registered class answers `E_NOINTERFACE` *at activation*, which reads exactly like
|
|
|
|
|
/// "this class does not expose the interface you wanted" and is really "you asked for
|
|
|
|
|
/// gibberish". That mistake produced a false negative on this very probe.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static readonly Guid IidIUnknown = new("00000000-0000-0000-C000-000000000046");
|
|
|
|
|
|
2026-07-29 03:26:09 -07:00
|
|
|
/// `IWSLCCompatSession` from WSLCCompat.idl — the SDK-facing session interface, and the one
|
|
|
|
|
/// the C#/WinRT `Session` projection wraps. Distinct from the internal `IWSLCSession`
|
|
|
|
|
/// (`EF0661E4-…`); whether one object answers both is the reattach-handoff question.
|
|
|
|
|
private static readonly Guid IidCompatSession = new("DD7B2EF9-AA01-4F21-8A3A-29D394CBB579");
|
|
|
|
|
|
2026-07-29 03:06:18 -07:00
|
|
|
private const uint ClsctxAll = 0x17; // INPROC_SERVER|HANDLER|LOCAL_SERVER|REMOTE_SERVER
|
|
|
|
|
private const uint CoinitMultithreaded = 0;
|
|
|
|
|
private const uint RpcEChangedMode = 0x80010106;
|
2026-07-29 03:10:00 -07:00
|
|
|
private const uint ENoInterface = 0x80004002;
|
2026-07-29 03:06:18 -07:00
|
|
|
|
|
|
|
|
[DllImport("ole32.dll")]
|
|
|
|
|
private static extern int CoInitializeEx(IntPtr reserved, uint coInit);
|
|
|
|
|
|
|
|
|
|
[DllImport("ole32.dll")]
|
|
|
|
|
private static extern int CoCreateInstance(
|
|
|
|
|
in Guid clsid, IntPtr outer, uint clsContext, in Guid iid, out IntPtr instance);
|
|
|
|
|
|
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
|
private struct WslcVersion
|
|
|
|
|
{
|
|
|
|
|
public uint Major;
|
|
|
|
|
public uint Minor;
|
|
|
|
|
public uint Revision;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Matches `WSLCSessionListEntry` in wslc.idl: two 32-bit fields then two INLINE wide-char
|
|
|
|
|
/// buffers (not pointers), which is why these are ByValTStr and why the sizes must be exact.
|
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
|
|
|
private struct WslcSessionListEntry
|
|
|
|
|
{
|
|
|
|
|
public uint SessionId;
|
|
|
|
|
public uint CreatorPid;
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string DisplayName;
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)] public string Sid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Declared only as far as <c>ListSessions</c> — but every preceding method must still be
|
|
|
|
|
/// declared with the right parameter count, because a vtable is addressed by slot. The
|
|
|
|
|
/// unused ones take <c>IntPtr</c> placeholders for their struct and interface pointers.
|
|
|
|
|
/// <c>PreserveSig</c> throughout, so a failing HRESULT is a value to report rather than an
|
|
|
|
|
/// exception to decode.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[ComImport, Guid("82A7ABC8-6B50-43FC-AB96-15FBBE7E8760"),
|
|
|
|
|
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
|
|
|
private interface IWSLCSessionManager
|
|
|
|
|
{
|
|
|
|
|
[PreserveSig] int GetVersion(out WslcVersion version);
|
|
|
|
|
[PreserveSig] int CreateSession(IntPtr settings, uint flags, IntPtr warningCallback, out IntPtr session);
|
|
|
|
|
[PreserveSig] int EnterSession(
|
|
|
|
|
[MarshalAs(UnmanagedType.LPWStr)] string displayName,
|
|
|
|
|
[MarshalAs(UnmanagedType.LPWStr)] string storagePath,
|
|
|
|
|
IntPtr warningCallback, out IntPtr session);
|
|
|
|
|
[PreserveSig] int ListSessions(out IntPtr sessions, out uint count);
|
|
|
|
|
[PreserveSig] int OpenSession(uint id, out IntPtr session);
|
|
|
|
|
[PreserveSig] int OpenSessionByName(
|
|
|
|
|
[MarshalAs(UnmanagedType.LPWStr)] string displayName, out IntPtr session);
|
|
|
|
|
}
|
|
|
|
|
}
|