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