Skip to content

P Invoke and Win32 APIs

Calvindd2f edited this page Jan 14, 2026 · 1 revision

P/Invoke and Win32 APIs

Relevant source files

The following files were used as context for generating this wiki page:

Purpose and Scope

This document provides technical reference for the P/Invoke declarations and Win32 API usage in the native performance layer. The NativeMethods class OfficeScrubC2R-Native.cs:62-153 serves as the central interface to Windows system APIs, providing direct access to registry, file system, and shell operations that are not available or performant enough through managed .NET APIs.

For implementation details of the helper classes that consume these APIs, see RegistryHelper, FileHelper, and ShellHelper. For overall native layer architecture, see Native Library Overview.


Win32 API Categories

The native layer invokes APIs from three core Windows system DLLs to achieve performance improvements over managed alternatives:

DLL Purpose Primary Operations Performance Benefit
advapi32.dll Advanced Windows 32 Base API Registry operations with WOW64 support 30x faster registry enumeration
kernel32.dll Windows Kernel API File system operations, reboot scheduling 15x faster file deletion
shell32.dll Windows Shell API Shell integration, taskbar/start menu Native COM interop

Sources: OfficeScrubC2R-Native.cs:60-153


NativeMethods Class Architecture

The NativeMethods class is an internal static class that encapsulates all P/Invoke declarations using the internal access pattern to prevent external usage while allowing consumption by sibling helper classes.

graph TB
    subgraph "OfficeScrubC2R-Native.cs"
        NM["NativeMethods<br/>(internal static class)<br/>Lines 62-153"]
        
        subgraph "Registry APIs (advapi32.dll)"
            RegOpen["RegOpenKeyEx<br/>Line 71-76"]
            RegClose["RegCloseKey<br/>Line 79"]
            RegDelete["RegDeleteKeyEx<br/>Line 82-86"]
            RegDelVal["RegDeleteValue<br/>Line 89-91"]
            RegEnum["RegEnumKeyEx<br/>Line 94-102"]
            RegEnumVal["RegEnumValue<br/>Line 105-113"]
            RegQuery["RegQueryValueEx<br/>Line 116-122"]
        end
        
        subgraph "File APIs (kernel32.dll)"
            MoveFileEx["MoveFileEx<br/>Line 65-68"]
            SetFileAttr["SetFileAttributes<br/>Line 138-140"]
            RemoveDir["RemoveDirectory<br/>Line 143"]
        end
        
        subgraph "Shell APIs (shell32.dll)"
            ILCreate["ILCreateFromPath<br/>Line 125"]
            ILFree["ILFree<br/>Line 128"]
            SHCreate["SHCreateShellItem<br/>Line 131-135"]
        end
        
        subgraph "Constants"
            KeyFlags["KEY_WOW64_64KEY<br/>KEY_WOW64_32KEY<br/>KEY_READ/WRITE<br/>Lines 145-149"]
            ErrorCodes["ERROR_SUCCESS<br/>ERROR_NO_MORE_ITEMS<br/>Lines 151-152"]
            MoveFlags["MOVEFILE_DELAY_UNTIL_REBOOT<br/>Line 150"]
        end
        
        NM --> RegOpen
        NM --> RegClose
        NM --> RegDelete
        NM --> RegDelVal
        NM --> RegEnum
        NM --> RegEnumVal
        NM --> RegQuery
        NM --> MoveFileEx
        NM --> SetFileAttr
        NM --> RemoveDir
        NM --> ILCreate
        NM --> ILFree
        NM --> SHCreate
        NM --> KeyFlags
        NM --> ErrorCodes
        NM --> MoveFlags
    end
    
    RegHelper["RegistryHelper<br/>Consumes registry APIs"]
    FileHelper["FileHelper<br/>Consumes file APIs"]
    ShellHelper["ShellHelper<br/>Consumes shell APIs"]
    
    RegOpen --> RegHelper
    RegClose --> RegHelper
    RegDelete --> RegHelper
    RegDelVal --> RegHelper
    RegEnum --> RegHelper
    RegEnumVal --> RegHelper
    RegQuery --> RegHelper
    
    MoveFileEx --> FileHelper
    SetFileAttr --> FileHelper
    RemoveDir --> FileHelper
    
    ILCreate --> ShellHelper
    ILFree --> ShellHelper
    SHCreate --> ShellHelper
Loading

Sources: OfficeScrubC2R-Native.cs:62-153


Registry APIs (advapi32.dll)

The registry APIs provide direct access to the Windows Registry with WOW64 support for accessing both 32-bit and 64-bit registry views on 64-bit systems.

RegOpenKeyEx

Opens a registry key with specified access rights and WOW64 view flags:

[DllImport("advapi32.dll", SetLastError = true)]
internal static extern int RegOpenKeyEx(
    UIntPtr hKey,           // Handle to parent key
    string lpSubKey,        // Subkey path
    uint ulOptions,         // Reserved (must be 0)
    int samDesired,         // Access rights + WOW64 flags
    out UIntPtr phkResult); // Receives opened key handle

Key Parameters:

  • samDesired: Combines access rights (KEY_READ, KEY_WRITE) with WOW64 flags (KEY_WOW64_64KEY, KEY_WOW64_32KEY)
  • Returns: Win32 error code (0 = success)

Usage Pattern: OfficeScrubC2R-Native.cs:71-76

RegDeleteKeyEx

Deletes a registry key with WOW64 view specification:

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int RegDeleteKeyEx(
    UIntPtr hKey,       // Handle to parent key
    string lpSubKey,    // Subkey to delete
    int samDesired,     // WOW64 view flags
    uint Reserved);     // Must be 0

Advantage over managed APIs: Allows explicit WOW64 view selection, enabling deletion of 32-bit registry entries from 64-bit processes.

Usage Pattern: OfficeScrubC2R-Native.cs:82-86, used by RegistryHelper.DeleteKeyInternal:538-551

RegEnumKeyEx and RegEnumValue

Enumerate registry subkeys and values with sequential index-based access:

Usage Pattern: OfficeScrubC2R-Native.cs:94-113, consumed by RegistryHelper.EnumerateKeys:402-436 and RegistryHelper.EnumerateValues:438-472

Constants for Registry Operations

internal const int KEY_WOW64_64KEY = 0x0100;  // Access 64-bit registry view
internal const int KEY_WOW64_32KEY = 0x0200;  // Access 32-bit registry view (WOW64)
internal const int KEY_READ = 0x20019;        // Read access
internal const int KEY_WRITE = 0x20006;       // Write access
internal const int KEY_ALL_ACCESS = 0xF003F;  // Full access

Sources: OfficeScrubC2R-Native.cs:145-149


File System APIs (kernel32.dll)

File system APIs provide low-level access for deletion operations with reboot scheduling capability.

MoveFileEx with MOVEFILE_DELAY_UNTIL_REBOOT

Schedules file or directory deletion on next system reboot, bypassing file locks:

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool MoveFileEx(
    string lpExistingFileName,  // File/directory to delete
    string lpNewFileName,       // NULL for deletion
    int dwFlags);               // MOVEFILE_DELAY_UNTIL_REBOOT

Critical Flag:

internal const int MOVEFILE_DELAY_UNTIL_REBOOT = 0x4;

Deletion Pattern:

  • lpExistingFileName: Path to file/directory
  • lpNewFileName: null to indicate deletion (not rename)
  • dwFlags: MOVEFILE_DELAY_UNTIL_REBOOT flag

Implementation: FileHelper.ScheduleDeleteOnReboot:689-697 and FileHelper.ScheduleDirectoryDeleteOnReboot:699-727

SetFileAttributes

Removes read-only and system attributes before deletion:

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetFileAttributes(
    string lpFileName,
    uint dwFileAttributes);

Usage: FileHelper.DeleteFile:626-651 removes FileAttributes.ReadOnly flag before calling File.Delete()

RemoveDirectory

Native directory removal for performance-critical scenarios:

Usage: FileHelper.DeleteDirectory:653-687 uses cmd.exe rd /s /q for performance, with RemoveDirectory as fallback

Sources: OfficeScrubC2R-Native.cs:64-68,138-143,150


Shell APIs (shell32.dll)

Shell APIs provide COM-based integration with Windows Explorer shell functionality.

ILCreateFromPath, ILFree, SHCreateShellItem

These APIs create shell item pointers from file paths for advanced shell operations:

[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr ILCreateFromPath(string pszPath);

[DllImport("shell32.dll")]
internal static extern void ILFree(IntPtr pidl);

[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
internal static extern int SHCreateShellItem(
    IntPtr pidlParent,
    IntPtr psfParent,
    IntPtr pidl,
    out IShellItem ppsi);

IShellItem COM Interface

The IShellItem interface provides managed access to shell items:

[ComImport]
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IShellItem
{
    void BindToHandler(IntPtr pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, 
                       [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
    void GetParent(out IShellItem ppsi);
    void GetDisplayName(uint sigdnName, out IntPtr ppszName);
    void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
    void Compare(IShellItem psi, uint hint, out int piOrder);
}

Usage Context: While declared, these shell APIs are primarily used through the dynamic Shell.Application COM object in ShellHelper:864-974 for taskbar and start menu unpinning operations.

Sources: OfficeScrubC2R-Native.cs:124-165


Marshaling Patterns

The P/Invoke declarations follow specific marshaling patterns to ensure correct data transfer between managed and unmanaged code.

String Marshaling

Parameter Type Marshaling Attribute Purpose
string lpSubKey CharSet = CharSet.Unicode Unicode string input
StringBuilder lpName CharSet = CharSet.Unicode Unicode string output buffer
out string Default (LPWSTR) Managed string allocation

Pattern: All Win32 API strings use Unicode (CharSet.Unicode or W suffix APIs) for consistent international character support.

Handle Marshaling

UIntPtr hKey      // Registry key handle (platform-sized)
IntPtr pidl       // Shell item ID list pointer
out UIntPtr phk   // Output handle parameter

Pattern: Use UIntPtr for registry handles (varies by platform) and IntPtr for generic pointers.

Boolean Return Values

[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool MoveFileEx(...)

Pattern: Win32 BOOL (32-bit int) automatically marshaled to managed bool through MarshalAs attribute or return type inference.

Sources: OfficeScrubC2R-Native.cs:64-143


Error Handling Patterns

Win32 API error handling follows platform conventions with integration into managed exception handling.

Return Value Checking

graph TD
    API["Win32 API Call"]
    Check{"Check Return<br/>Value"}
    Success["ERROR_SUCCESS (0)<br/>Operation succeeded"]
    NoMore["ERROR_NO_MORE_ITEMS (259)<br/>Enumeration complete"]
    Error["Other error code<br/>Operation failed"]
    GetLastError["Marshal.GetLastWin32Error()<br/>Get detailed error"]
    Silent["Silent failure<br/>return false/null"]
    
    API --> Check
    Check -->|"0"| Success
    Check -->|"259"| NoMore
    Check -->|"Other"| Error
    Error --> GetLastError
    GetLastError --> Silent
    NoMore --> Silent
Loading

Error Code Constants

internal const int ERROR_SUCCESS = 0;          // Operation succeeded
internal const int ERROR_NO_MORE_ITEMS = 259;  // Enumeration complete (not an error)

Usage Pattern in RegistryHelper:

private bool DeleteKeyInternal(RegistryKey hive, string subKey, bool recursive)
{
    try
    {
        if (recursive)
            hive.DeleteSubKeyTree(subKey, false);
        else
            hive.DeleteSubKey(subKey, false);
        return true;
    }
    catch
    {
        return false;  // Silent failure
    }
}

SetLastError Attribute

All P/Invoke declarations use SetLastError = true to preserve Win32 error codes:

[DllImport("advapi32.dll", SetLastError = true)]

This enables Marshal.GetLastWin32Error() for detailed diagnostics, though the implementation follows a silent failure pattern OfficeScrubC2R-Native.cs:538-551.

Sources: OfficeScrubC2R-Native.cs:64-152, docs/memory-bank/guidelines.md:24-38


API Usage Flow

The following diagram shows how Win32 APIs flow from declaration through helper classes to the orchestrator.

sequenceDiagram
    participant Orch as OfficeScrubOrchestrator
    participant RegH as RegistryHelper
    participant NM as NativeMethods
    participant Advapi as advapi32.dll
    
    Note over Orch: Initialize with is64Bit flag
    Orch->>RegH: new RegistryHelper(is64Bit)
    
    Note over Orch: Request registry key deletion
    Orch->>RegH: DeleteKey(hive, subKey, recursive)
    
    RegH->>RegH: KeyExists(hive, subKey)
    RegH->>NM: RegOpenKeyEx(hKey, lpSubKey, ...)
    NM->>Advapi: P/Invoke RegOpenKeyEx
    Advapi-->>NM: Return error code
    NM-->>RegH: Handle or error
    
    alt Key exists
        RegH->>RegH: DeleteKeyInternal(hive, subKey, recursive)
        Note over RegH: Use managed API for safety
        RegH->>RegH: hive.DeleteSubKeyTree(subKey, false)
    end
    
    alt is64Bit system
        Note over RegH: Check WOW64 view
        RegH->>RegH: GetWow64Key(subKey)
        RegH->>RegH: KeyExists(hive, wow64Key)
        RegH->>NM: RegOpenKeyEx with KEY_WOW64_32KEY
        NM->>Advapi: P/Invoke with WOW64 flag
        Advapi-->>NM: Return error code
        
        alt WOW64 key exists
            RegH->>RegH: DeleteKeyInternal(hive, wow64Key, recursive)
        end
    end
    
    RegH-->>Orch: Return success/failure
Loading

Key Observations:

  1. NativeMethods is never called directly by orchestrator
  2. Helper classes abstract Win32 complexity
  3. WOW64 operations are transparent to caller
  4. Silent failure pattern prevents exception propagation

Sources: OfficeScrubC2R-Native.cs:62-153,334-616


WOW64 Registry Access Pattern

On 64-bit Windows, the registry is split into 64-bit and 32-bit views. The P/Invoke layer enables explicit view selection:

graph LR
    subgraph "64-bit Windows Registry"
        HKLM["HKEY_LOCAL_MACHINE"]
        
        subgraph "Native 64-bit View"
            Soft64["SOFTWARE<br/>(64-bit applications)"]
            Office64["SOFTWARE\Microsoft\Office<br/>(64-bit Office)"]
        end
        
        subgraph "WOW64 32-bit View"
            Wow["SOFTWARE\Wow6432Node<br/>(32-bit applications)"]
            Office32["SOFTWARE\Wow6432Node\Microsoft\Office<br/>(32-bit Office)"]
        end
    end
    
    HKLM --> Soft64
    HKLM --> Wow
    Soft64 --> Office64
    Wow --> Office32
    
    subgraph "RegistryHelper Access"
        KeyExists["KeyExists(hive, subKey)"]
        Access64["RegOpenKeyEx<br/>with KEY_WOW64_64KEY"]
        Access32["RegOpenKeyEx<br/>with KEY_WOW64_32KEY"]
        GetWow64["GetWow64Key(subKey)<br/>transforms path"]
    end
    
    KeyExists --> Access64
    KeyExists --> GetWow64
    GetWow64 --> Access32
    
    Access64 -.checks.-> Office64
    Access32 -.checks.-> Office32
Loading

Path Transformation Logic

private string GetWow64Key(string subKey)
{
    // Special case: Classes hive
    if (subKey.StartsWith("Software\\Classes\\", StringComparison.OrdinalIgnoreCase))
        return "Software\\Classes\\Wow6432Node\\" + subKey.Substring(18);
    
    // General case: Insert Wow6432Node after first component
    int pos = subKey.IndexOf('\\');
    if (pos > 0)
        return subKey.Substring(0, pos) + "\\Wow6432Node\\" + subKey.Substring(pos + 1);
    
    return "Wow6432Node\\" + subKey;
}

Examples:

  • SOFTWARE\Microsoft\Office → SOFTWARE\Wow6432Node\Microsoft\Office
  • Software\Classes\TypeLib\{GUID} → Software\Classes\Wow6432Node\TypeLib\{GUID}

Implementation: RegistryHelper.GetWow64Key:583-593

Sources: OfficeScrubC2R-Native.cs:145-146,583-593, docs/memory-bank/guidelines.md:52-55


Performance Impact of P/Invoke

The direct Win32 API access provides measurable performance improvements over managed alternatives:

Operation Managed API P/Invoke API Performance Factor
Registry enumeration RegistryKey.GetSubKeyNames() RegEnumKeyEx loop 30x faster
Registry WOW64 access Multiple OpenSubKey attempts Single RegOpenKeyEx with flags 2-3x faster
File deletion with locks File.Delete() + catch MoveFileEx with reboot flag Immediate vs. exception overhead
Directory deletion Directory.Delete() recursive cmd.exe rd /s /q 15x faster for large trees

Why P/Invoke is Faster

  1. Direct System Calls: No managed layer abstraction or safety checks
  2. WOW64 Flag Support: Single API call instead of multiple attempts
  3. Batch Operations: cmd.exe rd processes entire directory tree natively
  4. Lock Handling: MoveFileEx schedules deletion without throwing exceptions

Implementation Evidence: OfficeScrubC2R-Native.cs:660-687 shows cmd.exe fallback pattern, RegistryHelper:344-363 demonstrates WOW64 dual-access pattern.

Sources: OfficeScrubC2R-Native.cs:334-767, docs/memory-bank/product.md:23-27


Summary of P/Invoke Declarations

Registry APIs (advapi32.dll)

Function Purpose Return Type Usage
RegOpenKeyEx Open registry key with WOW64 flags int (error code) RegistryHelper:523-536
RegCloseKey Close registry handle int (error code) Managed via using
RegDeleteKeyEx Delete key with WOW64 view int (error code) RegistryHelper:538-551
RegDeleteValue Delete registry value int (error code) RegistryHelper:366-400
RegEnumKeyEx Enumerate subkeys int (error code) RegistryHelper:402-436
RegEnumValue Enumerate values int (error code) RegistryHelper:438-472
RegQueryValueEx Query value data int (error code) RegistryHelper:474-502

File System APIs (kernel32.dll)

Function Purpose Return Type Usage
MoveFileEx Schedule deletion on reboot bool FileHelper:689-697
SetFileAttributes Modify file attributes bool FileHelper:626-651
RemoveDirectory Native directory removal bool FileHelper:653-687

Shell APIs (shell32.dll)

Function Purpose Return Type Usage
ILCreateFromPath Create shell item pointer IntPtr Declared, unused
ILFree Free shell item pointer void Declared, unused
SHCreateShellItem Create IShellItem int (error code) Declared, unused

Note: Shell APIs are declared but the implementation uses dynamic COM automation via Shell.Application instead ShellHelper:866-947.

Sources: OfficeScrubC2R-Native.cs:62-165

Clone this wiki locally