Skip to content

chore: migrate P/Invoke declarations to [LibraryImport] (MODERN-002)#454

Merged
laurentiu021 merged 1 commit into
mainfrom
chore/library-import
May 20, 2026
Merged

chore: migrate P/Invoke declarations to [LibraryImport] (MODERN-002)#454
laurentiu021 merged 1 commit into
mainfrom
chore/library-import

Conversation

@laurentiu021

@laurentiu021 laurentiu021 commented May 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Migrated 10 of 12 [DllImport] declarations to [LibraryImport] source generator across 6 files
  • Added <AllowUnsafeBlocks>true</AllowUnsafeBlocks> to .csproj (required by LibraryImport source gen)
  • Made classes partial where needed: IconExtractorService, PerformanceService, ShortcutCleanerService, KnownFolders
  • Renamed SystemParametersInfo overloads to SystemParametersInfoGet/SystemParametersInfoSet with EntryPoint
  • Renamed ExtractIconW to ExtractIcon with EntryPoint = "ExtractIconW"
  • Kept as DllImport: SHGetFileInfo and SHFileOperation (structs with ByValTStr fields not supported by source generator)

Test plan

  • CI build passes
  • Unit tests pass
  • P/Invoke calls work at runtime (icon extraction, performance tuning, recycle bin)

Summary by CodeRabbit

  • Chores
    • Updated internal interop mechanisms and project configuration for improved code quality and maintainability.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR modernizes Win32 P/Invoke declarations across six source files by migrating from [DllImport] extern methods to [LibraryImport] partial methods, enabling .NET source code generation for interop. The project build configuration is updated to allow unsafe code compilation, six classes are made partial, and interop signatures are updated with explicit marshalling attributes. PerformanceService logic is adjusted to accommodate integer-based marshalling.

Changes

P/Invoke to LibraryImport Modernization

Layer / File(s) Summary
Build configuration and straightforward interop migrations
SysManager.csproj, App.xaml.cs, Helpers/KnownFolders.cs, MainWindow.xaml.cs, Services/ShortcutCleanerService.cs
Enable unsafe code blocks. Convert four classes and one helper to partial to support source-generated interop. Migrate SetForegroundWindow, ShowWindow, IsIconic, SHGetKnownFolderPath, and DefWindowProc from [DllImport] to [LibraryImport] with explicit marshalling attributes.
IconExtractorService interop modernization
Services/IconExtractorService.cs
Make service partial. Replace ExtractIconW [DllImport] with LibraryImport partial method ExtractIcon. Update ExtractShell32Icon call site to use the new wrapper and add UTF-16 string marshalling to the declaration.
PerformanceService interop modernization with marshalling logic updates
Services/PerformanceService.cs
Make service partial. Migrate SystemParametersInfoW to two LibraryImport partial methods with int-based parameter marshalling. Update GetUiEffectsEnabled and SetUiEffects callers to use int values (0/1) and interpret nonzero results as enabled. Migrate EmptyWorkingSet to LibraryImport.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Poem

🐰 P/Invoke takes flight, from extern to source code's light,
LibraryImport shines so bright, marshalling done just right,
Six files now partial stand, with safer interop at hand,
Modern Windows calls expand—a rabbit's helping hand! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: migrating P/Invoke declarations from [DllImport] to [LibraryImport] across multiple files, which is the primary objective of this changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/library-import

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@SysManager/SysManager/Helpers/KnownFolders.cs`:
- Around line 25-30: Change the SHGetKnownFolderPath P/Invoke to return the
HRESULT (int) and use an out IntPtr (pointer) instead of out string so you can
check the result and manage the native memory yourself: update the LibraryImport
signature for SHGetKnownFolderPath to return int and out IntPtr pszPath (remove
StringMarshalling for that out param), then in the caller call
SHGetKnownFolderPath, check the returned HRESULT and call
Marshal.ThrowExceptionForHR(hresult) on failure, convert the returned IntPtr to
a managed string with Marshal.PtrToStringUni on success, and free the native
buffer with Marshal.FreeCoTaskMem (CoTaskMemFree) after conversion to avoid
leaks and ensure correct fallback behavior.

In `@SysManager/SysManager/Services/PerformanceService.cs`:
- Around line 269-271: The code is currently ignoring the BOOL return from the
native calls; update the usages of SystemParametersInfoGet and the corresponding
setter (e.g., SystemParametersInfoSet/SystemParametersInfo with SPI_GETUIEFFECTS
and SPI_SETUIEFFECTS) to check their return value, and if the call fails, do not
assume the seeded/default value or log success—instead log or propagate an error
that includes Marshal.GetLastWin32Error() and return a failure/false result (or
throw) so callers know the operation did not succeed; only treat the operation
as successful when the native BOOL return is true.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7118392c-aa53-4b3b-a6e4-c662af53c678

📥 Commits

Reviewing files that changed from the base of the PR and between 0324ce6 and 7d5218b.

📒 Files selected for processing (7)
  • SysManager/SysManager/App.xaml.cs
  • SysManager/SysManager/Helpers/KnownFolders.cs
  • SysManager/SysManager/MainWindow.xaml.cs
  • SysManager/SysManager/Services/IconExtractorService.cs
  • SysManager/SysManager/Services/PerformanceService.cs
  • SysManager/SysManager/Services/ShortcutCleanerService.cs
  • SysManager/SysManager/SysManager.csproj
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build & unit tests
  • GitHub Check: Analyze (csharp)

Comment on lines +25 to 30
[LibraryImport("shell32.dll", StringMarshalling = StringMarshalling.Utf16)]
private static partial void SHGetKnownFolderPath(
in Guid rfid,
uint dwFlags,
nint hToken,
out string pszPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does C# [LibraryImport] support automatic HRESULT-to-exception translation or a PreserveSig=false equivalent for Win32 APIs like SHGetKnownFolderPath? What is the recommended LibraryImport signature for SHGetKnownFolderPath when the native function returns HRESULT and allocates a PWSTR that must be freed by the caller?

💡 Result:

Yes. When you use source-generated P/Invoke via LibraryImport (LibraryImportAttribute),.NET performs the same kind of HRESULT-to-exception translation that DllImportAttribute does when PreserveSig=false. In that mode, failing HRESULTs are automatically converted into exceptions, and successful S_OK (0) is treated as “no exception”; the managed signature becomes void (or the type of the last [out, retval] parameter) rather than returning the HRESULT directly. [1][2][3] For SHGetKnownFolderPath, the native signature is: HRESULT SHGetKnownFolderPath(..., PWSTR ppszPath); The output string is allocated by the callee and must always be freed by the caller with CoTaskMemFree once you’re done (even on failure). [4] Recommended LibraryImport signature (HRESULT-returning native, PWSTR allocated by callee freed by caller): [LibraryImport("shell32.dll", StringMarshalling = StringMarshalling.Utf16)] internal static partial int SHGetKnownFolderPath( ref readonly Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr ppszPath); Then, call it and translate HRESULT yourself (because you will typically keep the HRESULT return type as int). Example pattern: - declare IntPtr p; - int hr = SHGetKnownFolderPath(ref knownFolderId, flags, IntPtr.Zero, out p); - if (p!= IntPtr.Zero) { string path = Marshal.PtrToStringUni(p)!; Marshal.FreeCoTaskMem(p); } If you want automatic exception translation, you need the LibraryImport signature to be “PreserveSig=false equivalent” (i.e., have.NET treat the HRESULT as not directly returned). Microsoft documents that setting PreserveSig=false causes the managed signature to become void or the type of the last [out, retval] parameter, and failing HRESULTs throw automatically. [1][3] However, for SHGetKnownFolderPath specifically, because the last parameter is a pointer-out (PWSTR), there is no built-in way described in the cited docs to both (a) keep a success/failure automatic exception translation and (b) return the allocated pointer safely without also relying on correct source-generator handling of the parameter as the “retval”/translated return. The most robust, recommended approach grounded in the official allocation/freeing contract is therefore: keep an int HRESULT return and explicitly free the returned PWSTR with CoTaskMemFree/Marshal.FreeCoTaskMem. [4]

Citations:


🏁 Script executed:

# Locate and examine the KnownFolders.cs file
fd -t f "KnownFolders.cs" --exec wc -l {} \;

Repository: laurentiu021/SystemManager

Length of output: 121


🏁 Script executed:

# Read the file to verify the code and fallback logic
fd -t f "KnownFolders.cs" --exec cat -n {} \;

Repository: laurentiu021/SystemManager

Length of output: 3948


Preserve the HRESULT from SHGetKnownFolderPath for explicit error handling.

The current declaration with StringMarshalling = StringMarshalling.Utf16 and void return type relies on automatic HRESULT-to-exception translation. However, this combination is not reliably specified for LibraryImport with out-pointer parameters like PWSTR*. The native function allocates memory that must be freed with CoTaskMemFree; using out string obscures this contract. Return the int HRESULT, explicitly check it with Marshal.ThrowExceptionForHR(), and manually free the allocated pointer to ensure correct fallback behavior and memory safety.

Suggested fix
-    [LibraryImport("shell32.dll", StringMarshalling = StringMarshalling.Utf16)]
-    private static partial void SHGetKnownFolderPath(
+    [LibraryImport("shell32.dll")]
+    private static partial int SHGetKnownFolderPath(
         in Guid rfid,
         uint dwFlags,
         nint hToken,
-        out string pszPath);
+        out nint pszPath);
     private static string GetPath(Guid folderId)
     {
         try
         {
-            SHGetKnownFolderPath(folderId, 0, nint.Zero, out var path);
-            return path;
+            var hr = SHGetKnownFolderPath(folderId, 0, nint.Zero, out var pathPtr);
+            if (hr < 0)
+                Marshal.ThrowExceptionForHR(hr);
+
+            try
+            {
+                return Marshal.PtrToStringUni(pathPtr) ?? string.Empty;
+            }
+            finally
+            {
+                Marshal.FreeCoTaskMem(pathPtr);
+            }
         }
         catch (COMException)
         {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SysManager/SysManager/Helpers/KnownFolders.cs` around lines 25 - 30, Change
the SHGetKnownFolderPath P/Invoke to return the HRESULT (int) and use an out
IntPtr (pointer) instead of out string so you can check the result and manage
the native memory yourself: update the LibraryImport signature for
SHGetKnownFolderPath to return int and out IntPtr pszPath (remove
StringMarshalling for that out param), then in the caller call
SHGetKnownFolderPath, check the returned HRESULT and call
Marshal.ThrowExceptionForHR(hresult) on failure, convert the returned IntPtr to
a managed string with Marshal.PtrToStringUni on success, and free the native
buffer with Marshal.FreeCoTaskMem (CoTaskMemFree) after conversion to avoid
leaks and ensure correct fallback behavior.

Comment on lines +269 to +271
int enabled = 1;
SystemParametersInfoGet(SPI_GETUIEFFECTS, 0, ref enabled, 0);
return enabled != 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For .NET [LibraryImport] with SetLastError = true, should callers use Marshal.GetLastPInvokeError() and explicitly check the BOOL returned by SystemParametersInfoW for SPI_GETUIEFFECTS and SPI_SETUIEFFECTS?

💡 Result:

When using.NET LibraryImport with SetLastError = true, you should primarily rely on the boolean return value of SystemParametersInfoW to determine if the call succeeded [1][2]. According to the Windows API documentation, SystemParametersInfo returns a nonzero value (TRUE) if the function succeeds and zero (FALSE) if it fails [1][2]. You should check this return value first. If the function returns FALSE, you can then use Marshal.GetLastPInvokeError to retrieve the extended error information [3][4]. This is the correct, modern.NET approach for accessing the last Win32 error code after a P/Invoke call configured with SetLastError = true [3][4]. It is important to note that GetLastError (and by extension, Marshal.GetLastPInvokeError) should only be consulted when the function's return value indicates a failure [5][6]. If the function succeeds, the value returned by GetLastPInvokeError is unreliable and may contain stale data from previous operations [5][6]. Therefore, explicitly checking the boolean return value is the standard and required practice for robust error handling with this API [5][6].

Citations:


🏁 Script executed:

# Find and examine the PerformanceService.cs file
find . -name "PerformanceService.cs" -type f

Repository: laurentiu021/SystemManager

Length of output: 125


🏁 Script executed:

# Once we locate it, examine the relevant lines and method signatures
cat -n SysManager/SysManager/Services/PerformanceService.cs | sed -n '250,300p'

Repository: laurentiu021/SystemManager

Length of output: 2423


🏁 Script executed:

# Check the full file size to understand context
wc -l SysManager/SysManager/Services/PerformanceService.cs

Repository: laurentiu021/SystemManager

Length of output: 127


🏁 Script executed:

# Search for the SystemParametersInfoGet and SystemParametersInfoSet declarations
rg -n "SystemParametersInfo(Get|Set)" SysManager/SysManager/Services/PerformanceService.cs

Repository: laurentiu021/SystemManager

Length of output: 356


🏁 Script executed:

# Look for LibraryImport and SetLastError declarations
rg -n -B 2 "SystemParametersInfo" SysManager/SysManager/Services/PerformanceService.cs | head -50

Repository: laurentiu021/SystemManager

Length of output: 1283


Check the SystemParametersInfoW return value before trusting the result.

Both call sites discard the BOOL return. If the GET call fails, Line 271 reports true from the seeded value; if the SET call fails, Line 283 still logs success even though nothing changed.

Suggested fix
     internal static bool GetUiEffectsEnabled()
     {
         int enabled = 1;
-        SystemParametersInfoGet(SPI_GETUIEFFECTS, 0, ref enabled, 0);
+        if (!SystemParametersInfoGet(SPI_GETUIEFFECTS, 0, ref enabled, 0))
+            throw new System.ComponentModel.Win32Exception(Marshal.GetLastPInvokeError());
         return enabled != 0;
     }
@@
     public static void SetUiEffects(bool enabled)
     {
-        SystemParametersInfoSet(SPI_SETUIEFFECTS, 0, enabled ? 1 : 0, SPIF_SENDCHANGE);
+        if (!SystemParametersInfoSet(SPI_SETUIEFFECTS, 0, enabled ? 1 : 0, SPIF_SENDCHANGE))
+            throw new System.ComponentModel.Win32Exception(Marshal.GetLastPInvokeError());
         Log.Information("System: Visual effects {Action}", enabled ? "enabled" : "reduced");
     }

Also applies to: 282-283

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SysManager/SysManager/Services/PerformanceService.cs` around lines 269 - 271,
The code is currently ignoring the BOOL return from the native calls; update the
usages of SystemParametersInfoGet and the corresponding setter (e.g.,
SystemParametersInfoSet/SystemParametersInfo with SPI_GETUIEFFECTS and
SPI_SETUIEFFECTS) to check their return value, and if the call fails, do not
assume the seeded/default value or log success—instead log or propagate an error
that includes Marshal.GetLastWin32Error() and return a failure/false result (or
throw) so callers know the operation did not succeed; only treat the operation
as successful when the native BOOL return is true.

@laurentiu021
laurentiu021 merged commit c293630 into main May 20, 2026
5 checks passed
@laurentiu021
laurentiu021 deleted the chore/library-import branch May 20, 2026 10:45
laurentiu021 added a commit that referenced this pull request May 22, 2026
)

## What does this PR do?

Replaces 2 remaining generic/bare catches found during the full code
audit:

- **LogsViewModel** — `catch (Exception ex)` replaced with specific
`EventLogException`, `UnauthorizedAccessException`,
`InvalidOperationException`
- **ConsoleViewModel** — bare `catch { }` replaced with
`ExternalException` (clipboard lock)

## Type of change
- [x] Code quality / CodeQL (`fix:`)

---------

Co-authored-by: laurentiu021 <laurentiu021@users.noreply.github.com>
laurentiu021 added a commit that referenced this pull request May 22, 2026
…454)

Co-authored-by: laurentiu021 <laurentiu021@users.noreply.github.com>
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.

1 participant