|
| 1 | +# Converting Marshaled Interop to CsWin32 Unmarshaled Interop |
| 2 | + |
| 3 | +This repository is moving trim-unsafe manual interop toward source-generated CsWin32 interop. |
| 4 | +When a user asks to convert marshaled interop code into unmarshaled interop, prefer updating `NativeMethods.txt` and the call sites instead of preserving manual `DllImport`/`LibraryImport` declarations or wrapping them with more local P/Invoke code. |
| 5 | + |
| 6 | +Removing Vanara interop usage is also part of this direction since it internally has trim-unsafe interop code. |
| 7 | + |
| 8 | +## Goal |
| 9 | + |
| 10 | +Remove interop code that relies on runtime marshalling, especially declarations using `DllImport`, `ComImport` and the Vanara package. |
| 11 | + |
| 12 | +The target shape is: |
| 13 | + |
| 14 | +- Add the native API, COM interface, enum, or struct name to `src/Files.App.CsWin32/NativeMethods.txt`. |
| 15 | +- Use `Windows.Win32.PInvoke` and generated CsWin32 types at the call site. |
| 16 | +- Delete the manual declarations and Vanara references. |
| 17 | +- Keep `Win32PInvoke` only for definitions that are not yet converted or cannot be generated by CsWin32. |
| 18 | + |
| 19 | +## Workflow |
| 20 | + |
| 21 | +1. Locate manual interop: |
| 22 | + |
| 23 | + ```powershell |
| 24 | + git grep -n "DllImport\|MarshalAs\|StringBuilder" -- src |
| 25 | + git grep -n "Win32PInvoke\." -- src/Files.App |
| 26 | + git grep -n "using Vanara\|Vanara\.PInvoke\|Kernel32\.\|Shell32\.\|User32\." -- src/Files.App |
| 27 | + ``` |
| 28 | + |
| 29 | +2. Add the API and related generated types to `src/Files.App.CsWin32/NativeMethods.txt`. |
| 30 | + |
| 31 | + Include both the function and any dependent structs, enums, or COM interfaces that the call site needs. For example: |
| 32 | + |
| 33 | + ```text |
| 34 | + RmStartSession |
| 35 | + RmRegisterResources |
| 36 | + RmGetList |
| 37 | + RmEndSession |
| 38 | + RM_PROCESS_INFO |
| 39 | + SHBrowseForFolder |
| 40 | + BROWSEINFOW |
| 41 | + SHGetPathFromIDList |
| 42 | + SHCreateItemFromParsingName |
| 43 | + SHCreateStreamOnFileEx |
| 44 | + ``` |
| 45 | + |
| 46 | +3. Build the CsWin32 project or the app project to refresh generated signatures: |
| 47 | + |
| 48 | + ```powershell |
| 49 | + dotnet build src/Files.App.CsWin32/Files.App.CsWin32.csproj -c Debug -p:Platform=x64 |
| 50 | + ``` |
| 51 | + |
| 52 | +4. Update callers to use generated APIs directly. |
| 53 | + |
| 54 | + Prefer generated safe overloads when they exist, such as `Span<char>`, `SafeHandle`, `ComPtr<T>`, generated enums, and generated structs. Use unsafe raw overloads only when the generated API naturally exposes pointers or when COM pointer identity is required. |
| 55 | + |
| 56 | +5. Remove the manual definition only after all callers have moved. |
| 57 | + |
| 58 | + Use targeted checks: |
| 59 | + |
| 60 | + ```powershell |
| 61 | + git grep -n "Win32PInvoke\.RmStartSession\|Win32PInvoke\.SHBrowseForFolder" -- src/Files.App |
| 62 | + git grep -n "RM_PROCESS_INFO\|BROWSEINFO" -- src/Files.App |
| 63 | + ``` |
| 64 | + |
| 65 | +6. Build and confirm there are no C# errors. |
| 66 | + |
| 67 | + In this repo, WinUI XAML compiler failures may appear independently of interop changes. Separate `error CS*` failures from `MSB3073` XAML compiler failures when reporting verification. |
| 68 | + |
| 69 | +## Manual Definition Conversion Notes |
| 70 | + |
| 71 | +- `StringBuilder` output buffers should usually become `Span<char>` or fixed `char*` buffers. |
| 72 | +- `IntPtr` handles should become `SafeFileHandle`, `SafeHandle`, `HANDLE`, or generated handle structs where practical. |
| 73 | +- Some APIs such as `CoCreateInstance` and `SHCreateItemFromParsingName` often require unsafe pointer overloads: |
| 74 | + |
| 75 | + ```csharp |
| 76 | + void* raw; |
| 77 | + Guid iid = SomeInterfaceIid; |
| 78 | + HRESULT hr = PInvoke.CoCreateInstance(&clsid, null, CLSCTX.CLSCTX_LOCAL_SERVER, &iid, &raw); |
| 79 | + IntPtr instance = (IntPtr)raw; |
| 80 | + ``` |
| 81 | + |
| 82 | +- Shell APIs that return PIDLs or allocated strings still require explicit lifetime management, but the API declaration should come from CsWin32: |
| 83 | + |
| 84 | + ```csharp |
| 85 | + var pidl = PInvoke.SHBrowseForFolder(ref browseInfo); |
| 86 | + Marshal.FreeCoTaskMem((nint)pidl); |
| 87 | + ``` |
| 88 | + |
| 89 | +- Restart Manager APIs can use generated `RM_PROCESS_INFO` and `Span<char>` session keys instead of local struct definitions. |
| 90 | +- Do not keep a local `LibraryImport` copy when the API can be represented in `NativeMethods.txt`. The requested direction is to update callees to CsWin32-generated interop. |
| 91 | + |
| 92 | +## Vanara Conversion Notes |
| 93 | + |
| 94 | +Treat Vanara removal the same way as manual P/Invoke removal when Vanara is only wrapping a native API. Add the API to `NativeMethods.txt`, inspect the generated CsWin32 signature, then update the call site to generated types. |
| 95 | + |
| 96 | +Example conversion: |
| 97 | + |
| 98 | +```csharp |
| 99 | +// Before |
| 100 | +var lib = Kernel32.LoadLibrary(file); |
| 101 | +StringBuilder result = new(2048); |
| 102 | +_ = User32.LoadString(lib, number, result, result.Capacity); |
| 103 | +Kernel32.FreeLibrary(lib); |
| 104 | +return result.ToString(); |
| 105 | + |
| 106 | +// After |
| 107 | +using var lib = PInvoke.LoadLibrary(file); |
| 108 | +Span<char> result = stackalloc char[2048]; |
| 109 | +int length = PInvoke.LoadString(lib, (uint)number, result, result.Length); |
| 110 | +return result[..length].ToString(); |
| 111 | +``` |
| 112 | + |
| 113 | +Useful heuristics: |
| 114 | + |
| 115 | +- Start with simple `Kernel32.*`, `User32.*`, or `Shell32.*` calls that map directly to one Win32 function. |
| 116 | +- Remove `using Vanara.PInvoke` only when no remaining types in the file depend on it. |
| 117 | +- Prefer generated `Span<T>` overloads over `StringBuilder` buffers when available. |
| 118 | +- Prefer generated handle types and `SafeHandle` overloads where CsWin32 provides them. |
| 119 | +- Watch for CsWin32 convenience overloads. Some APIs generate safer shapes than the underlying Win32 signature; for example, `LoadLibrary` returns a disposable `FreeLibrarySafeHandle`, so the call site can use `using var` instead of a separate `FreeLibrary` call. |
| 120 | + |
| 121 | +## Verification Checklist |
| 122 | + |
| 123 | +- `NativeMethods.txt` contains every newly used API/type. |
| 124 | +- No caller remains for the deleted manual method or struct. |
| 125 | +- The generated CsWin32 types are used at call sites. |
| 126 | +- `dotnet build src/Files.App.CsWin32/Files.App.CsWin32.csproj -c Debug -p:Platform=x64` succeeds. |
| 127 | +- `dotnet build src/Files.App/Files.App.csproj -c Debug -p:Platform=x64` has no new `error CS*` errors. |
| 128 | +- Any remaining build failure is called out explicitly, especially XAML compiler failures unrelated to interop. |
0 commit comments