我在 Visual Studio 2015 解决方案中有多个项目。其中几个项目执行 P/Invokes,例如:
[DllImport("IpHlpApi.dll")]
[return: MarshalAs(UnmanagedType.U4)]
public static extern int GetIpNetTable(IntPtr pIpNetTable, [MarshalAs(UnmanagedType.U4)]
ref int pdwSize, bool bOrder);
因此,我将所有 P/Invokes 移至单独的类库,并将单个类定义为:
namespace NativeMethods
{
[
SuppressUnmanagedCodeSecurityAttribute(),
ComVisible(false)
]
public static class SafeNativeMethods
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern int GetTickCount();
// Declare the GetIpNetTable function.
[DllImport("IpHlpApi.dll")]
[return: MarshalAs(UnmanagedType.U4)]
public static extern int GetIpNetTable(IntPtr pIpNetTable, [MarshalAs(UnmanagedType.U4)]
ref int pdwSize, bool bOrder);
}
}
在其他项目中,此代码称为:
int result = SafeNativeMethods.GetIpNetTable(IntPtr.Zero, ref bytesNeeded, false);
所有编译都没有错误或警告。
现在在代码上运行 FxCop 会给出警告:
Warning CA1401 Change the accessibility of P/Invoke 'SafeNativeMethods.GetIpNetTable(IntPtr, ref int, bool)' so that it is no longer visible from outside its assembly.
行。将可访问性更改为 internal 为:
[DllImport("IpHlpApi.dll")]
[return: MarshalAs(UnmanagedType.U4)]
internal static extern int GetIpNetTable(IntPtr pIpNetTable, [MarshalAs(UnmanagedType.U4)]
ref int pdwSize, bool bOrder);
现在导致以下硬错误:
Error CS0122 'SafeNativeMethods.GetIpNetTable(IntPtr, ref int, bool)' is inaccessible due to its protection level
那么我怎样才能在没有错误或警告的情况下完成这项工作呢?
提前感谢您的帮助,因为我已经转了几个小时!
最佳答案
可以肯定的是,您会同意 PInvoke 方法不是从 C# 代码中调用的最愉快的事情的说法。
他们是:
IntPtr
和 Byte[]
参数。 Marshal.GetLastError()
是消费者的责任。它。更常见的是,有人忘记了这样做,从而导致难以跟踪的错误。 与这些问题相比,FxCop 警告只是一个微不足道的检查器问题。
所以,你可以做什么?处理这三个问题,FxCop 就会自行解决。
这些是我建议你做的事情:
public static class ErrorHandling
{
// It is private so no FxCop should trouble you
[DllImport(DllNames.Kernel32)]
private static extern void SetLastErrorNative(UInt32 dwErrCode);
public static void SetLastError(Int32 errorCode)
{
SetLastErrorNative(unchecked((UInt32)errorCode));
}
}
IntPtr
如果你可以使用一些 safe handle . Boolean
或 (U)Int32
来自包装方法 - 检查包装方法内的返回类型,并在需要时抛出异常。如果您想以无异常方式使用方法,请提供 Try
-like 版本,它将清楚地表明它是一种无异常方法。public static class Window
{
public class WindowHandle : SafeHandle ...
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport(DllNames.User32, EntryPoint="SetForegroundWindow")]
private static extern Boolean TrySetForegroundWindowNative(WindowHandle hWnd);
// It is clear for everyone, that the return value should be checked.
public static Boolean TrySetForegroundWindow(WindowHandle hWnd)
{
if (hWnd == null)
throw new ArgumentNullException(paramName: nameof(hWnd));
return TrySetForegroundWindowNative(hWnd);
}
public static void SetForegroundWindow(WindowHandle hWnd)
{
if (hWnd == null)
throw new ArgumentNullException(paramName: nameof(hWnd));
var isSet = TrySetForegroundWindow(hWnd);
if (!isSet)
throw new InvalidOperationException(
String.Format(
"Failed to set foreground window {0}",
hWnd.DangerousGetHandle());
}
}
IntPtr
或 Byte[]
如果您可以使用 ref/out
传递的普通结构 .你可能会说这很明显,但是在许多可以传递强类型结构的情况下,我见过 IntPtr
被使用。不要使用 out
面向公众的方法中的参数。在大多数情况下,这是不必要的——您可以只返回该值。public static class SystemInformation
{
public struct SYSTEM_INFO { ... };
[DllImport(DllNames.Kernel32, EntryPoint="GetSystemInfo")]
private static extern GetSystemInfoNative(out SYSTEM_INFO lpSystemInfo);
public static SYSTEM_INFO GetSystemInfo()
{
SYSTEM_INFO info;
GetSystemInfoNative(out info);
return info;
}
}
public static class KeyBoardInput
{
public enum VmKeyScanState : byte
{
SHIFT = 1,
CTRL = 2, ...
}
public enum VirtualKeyCode : byte
{
...
}
[StructLayout(LayoutKind.Explicit)]
public struct VmKeyScanResult
{
[FieldOffset(0)]
private VirtualKeyCode _virtualKey;
[FieldOffset(1)]
private VmKeyScanState _scanState;
public VirtualKeyCode VirtualKey
{
get {return this._virtualKey}
}
public VmKeyScanState ScanState
{
get {return this._scanState;}
}
public Boolean IsFailure
{
get
{
return
(this._scanState == 0xFF) &&
(this._virtualKey == 0xFF)
}
}
}
[DllImport(DllNames.User32, CharSet=CharSet.Unicode, EntryPoint="VmKeyScan")]
private static extern VmKeyScanResult VmKeyScanNative(Char ch);
public static VmKeyScanResult TryVmKeyScan(Char ch)
{
return VmKeyScanNative(ch);
}
public static VmKeyScanResult VmKeyScan(Char ch)
{
var result = VmKeyScanNative(ch);
if (result.IsFailure)
throw new InvalidOperationException(
String.Format(
"Failed to VmKeyScan the '{0}' char",
ch));
return result;
}
}
附:并且不要忘记正确的函数签名(位数和其他问题)、类型的编码、布局属性和字符集(另外,不要忘记使用
DllImport(... SetLastError = true)
是 utmost importance )。 http://www.pinvoke.net/可能通常会有所帮助,但它并不总是提供最佳使用签名。P.S.1:我建议你组织你的
NativeMethods
不要归入一个类,因为它很快就会变成一大堆完全不同的方法,无法管理,而是将它们分组到单独的类中(我实际上使用一个 partial
根类和每个功能区域的嵌套类 - 有点乏味打字,但更好的上下文和智能感知)。对于类名,我只使用 MSDN 用于对 API 函数进行分组的相同分类。喜欢 GetSystemInfo它是“系统信息功能”因此,如果您应用所有这些建议,您将能够创建一个强大、易于使用的原生包装库,它隐藏了所有不必要的复杂性和容易出错的结构,但对于了解原始 API 的任何人来说,这看起来都非常熟悉。
关于c# - 如何在类库中正确调用 P/Invoke 方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35818620/