Win32 function is declared through P/Invoke although the framework provides an equivalent

ID

vbnet.portability.use_managed_equivalents_of_win32_api

Severity

low

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Interop

Language

VB.NET

Description

Reports a P/Invoke declaration - either a <DllImport> Function/Sub or a legacy Declare statement - whose name is one of a curated set of Win32 entry points for which the .NET class library offers a direct managed equivalent, such as Sleep, GetTickCount, CopyFile, GetComputerName, OutputDebugString, MessageBox, RegOpenKeyEx or PlaySound, including the A and W character-set suffixes. Native declarations that have no managed counterpart are not reported; the list is deliberately restricted to functions whose behaviour the framework reproduces, not merely approximates.

Rationale

Each of these declarations replaces a few lines of framework code with a native call, and takes on the obligations that come with it. Marshalling has to be correct and stays correct only as long as nobody edits the signature: a wrong CharSet, a Boolean left unmarshalled or an Integer where the API expects a pointer-sized value compiles cleanly and then corrupts memory or truncates a handle when the process runs as 64-bit. Errors arrive as a value the caller has to remember to check, followed by Marshal.GetLastWin32Error, so the failure that the managed equivalent would have raised as a typed exception is instead easy to drop silently. The call also demands unmanaged code permission, which is what makes an assembly unusable in a restricted host, and it ties the code to Windows: the same assembly on Linux or macOS fails at the first call with DllNotFoundException, so a project that would otherwise be portable is pinned to one platform by a handful of declarations that were never load-bearing. The managed equivalents are also better documented, unit-testable and consistent with the rest of the code base, which matters more than any of the above for the maintainer who reads the code next year.

The following code illustrates the pattern detected by this rule:

Friend NotInheritable Class NativeMethods

    ' FLAGGED: Win32 function is declared through P/Invoke although the framework provides an equivalent
    <DllImport("kernel32.dll")>
    Friend Shared Function GetTickCount() As Integer
    End Function

Remediation

Delete the declaration and call the framework equivalent: Thread.Sleep for Sleep, Environment.TickCount for GetTickCount, System.IO.File and System.IO.Directory for the file and directory functions, Environment.MachineName for GetComputerName, Environment.GetEnvironmentVariable for the environment functions, Debug.WriteLine for OutputDebugString, MessageBox.Show for MessageBox, Microsoft.Win32.Registry for the Reg* functions, System.Media.SoundPlayer for PlaySound, Process.Start for ShellExecute and DriveInfo for the drive and free-space functions. Where the native call must stay - because it exposes a flag the managed API does not - keep it in a NativeMethods class and wrap it behind a managed method so the rest of the code base does not depend on the platform.

' Before: a native declaration, marshalling to get right, and Windows-only
<DllImport("kernel32.dll")>
Friend Shared Function GetTickCount() As Integer
End Function

' After
Dim elapsed As Integer = Environment.TickCount

Configuration

This detector does not need any configuration.