C#调用WinApi?

Posted

技术标签:

【中文标题】C#调用WinApi?【英文标题】:C# call WinApi? 【发布时间】:2018-12-22 19:48:18 【问题描述】:

我正在尝试使用代码 IOCTL_DISK_SET_DISK_ATTRIBUTES 在 C# 中调用 WinAPI 函数 DeviceIoControl 并传递结构 SET_DISK_ATTRIBUTES。我正在尝试使用此代码:

const uint GENERIC_READ = 0x80000000;
const uint GENERIC_WRITE = 0x40000000;
const int FILE_SHARE_READ = 0x1;
const int FILE_SHARE_WRITE = 0x2;

const uint IOCTL_DISK_SET_DISK_ATTRIBUTES = 0x0007c0f4;
const ulong DISK_ATTRIBUTE_READ_ONLY = 0x0000000000000002;

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr CreateFile(
    string lpFileName,
    uint dwDesiredAccess,
    uint dwShareMode,
    IntPtr SecurityAttributes,
    uint dwCreationDisposition,
    uint dwFlagsAndAttributes,
    IntPtr hTemplateFile
);

[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool DeviceIoControl(
    IntPtr hDevice,
    uint dwIoControlCode,
    IntPtr lpInBuffer,
    uint nInBufferSize,
    IntPtr lpOutBuffer,
    uint nOutBufferSize,
    out uint lpBytesReturned,
    IntPtr lpOverlapped
);

struct SET_DISK_ATTRIBUTES

    public uint Version;
    public bool Persist;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public byte[] Reserved1;
    public ulong Attributes;
    public ulong AttributesMask;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public uint[] Reserved2;
;

private bool SetReadonly(IntPtr handle)

    var sda = new SET_DISK_ATTRIBUTES();
    sda.AttributesMask = DISK_ATTRIBUTE_READ_ONLY;
    sda.Attributes = DISK_ATTRIBUTE_READ_ONLY;

    int nPtrQryBytes = Marshal.SizeOf(sda);
    sda.Version = (uint)nPtrQryBytes;

    IntPtr ptrQuery = Marshal.AllocHGlobal(nPtrQryBytes);
    Marshal.StructureToPtr(sda, ptrQuery, false);

    uint byteReturned;
    var res = DeviceIoControl(handle, IOCTL_DISK_SET_DISK_ATTRIBUTES, ptrQuery, (uint)nPtrQryBytes, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero);

    var ex = new Win32Exception(Marshal.GetLastWin32Error());
    MessageBox.Show(ex.Message);

    return res;

我收到错误“参数不正确”。调用DeviceIoControl函数传递结构SET_DISK_ATTRIBUTES的正确方法是什么?

【问题讨论】:

当您将DeviceIoControl 声明为(IntPtr hDevice, uint dwIoControlCode, [In] ref SET_DISK_ATTRIBUTES lpInBuffer, ...) 并仅传递结构时会发生同样的情况吗? 是的 "ref sda" 给出相同的结果 您可能还应该在问题中包含所用常量的值。 啊,显然BOOLEAN Persist 是single byte (typedef BYTE BOOLEAN;),而不是像C# 的bool 或WinAPI 的BOOL 这样的四个字节。使用MarshalAs(UnmanagedType.I1) @RemyLebeau 是applied by default。 【参考方案1】:

SET_DISK_ATTRIBUTES的原定义:

typedef struct _SET_DISK_ATTRIBUTES 
  DWORD     Version;
  BOOLEAN   Persist;
  BYTE      Reserved1[3];
  DWORDLONG Attributes;
  DWORDLONG AttributesMask;
  DWORD     Reserved2[4];
 SET_DISK_ATTRIBUTES, *PSET_DISK_ATTRIBUTES;

使用BOOLEAN 数据类型,即defined 作为unsigned char(1 个字节)的同义词,而不是BOOLint(4 个字节)的同义词。

默认情况下,C# 的 bool 被封送为 BOOL。 你需要force it into one byte:


    ...
    [MarshalAs(UnmanagedType.I1)]
    public bool Persist;
    ...

【讨论】:

【参考方案2】:

最后是设置磁盘只读的代码

const uint GENERIC_READ = 0x80000000;
const uint GENERIC_WRITE = 0x40000000;
const int FILE_SHARE_READ = 0x1;
const int FILE_SHARE_WRITE = 0x2;
const uint FILE_FLAG_WRITE_THROUGH = 0x80000000;
const uint FILE_FLAG_NO_BUFFERING = 0x20000000;

const uint IOCTL_DISK_SET_DISK_ATTRIBUTES = 0x0007c0f4;
const ulong DISK_ATTRIBUTE_READ_ONLY = 0x0000000000000002;

const uint IOCTL_DISK_UPDATE_PROPERTIES = 459072;

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr CreateFile(
    string lpFileName,
    uint dwDesiredAccess,
    uint dwShareMode,
    IntPtr SecurityAttributes,
    uint dwCreationDisposition,
    uint dwFlagsAndAttributes,
    IntPtr hTemplateFile
);

[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool DeviceIoControl(
   IntPtr hDevice,
   uint dwIoControlCode,
   IntPtr lpInBuffer,
   uint nInBufferSize,
   IntPtr lpOutBuffer,
   uint nOutBufferSize,
   out uint lpBytesReturned,
   IntPtr lpOverlapped
);

struct SET_DISK_ATTRIBUTES

   public uint Version;
   [MarshalAs(UnmanagedType.I1)]
   public bool Persist;
   [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
   public byte[] Reserved1;
   public ulong Attributes;
   public ulong AttributesMask;
   [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
   public uint[] Reserved2;
;

public IntPtr CreateHandle(string driveLetter)

   string filename = @"\\.\" + driveLetter[0] + ":";
   return CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, 0x3, FILE_FLAG_WRITE_THROUGH, IntPtr.Zero);


private void SetReadonly(IntPtr handle)

   var sda = new SET_DISK_ATTRIBUTES();
   sda.Persist = true;
   sda.AttributesMask = DISK_ATTRIBUTE_READ_ONLY;
   sda.Attributes = DISK_ATTRIBUTE_READ_ONLY;
   sda.Reserved1 = new byte[3]  0, 0, 0 ;
   sda.Reserved2 = new uint[4]  0, 0, 0, 0 ;

   int nPtrQryBytes = Marshal.SizeOf(sda);
   sda.Version = (uint)nPtrQryBytes;

   IntPtr ptrQuery = Marshal.AllocHGlobal(nPtrQryBytes);
   Marshal.StructureToPtr(sda, ptrQuery, false);

   uint byteReturned;
   bool res = DeviceIoControl(handle, IOCTL_DISK_SET_DISK_ATTRIBUTES, ptrQuery, (uint)nPtrQryBytes, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero);
   bool res2 = DeviceIoControl(handle, IOCTL_DISK_UPDATE_PROPERTIES, IntPtr.Zero, 0, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero);

   var mess = new List<string>();
   mess.Add(new Win32Exception(Marshal.GetLastWin32Error()).Message);
   mess.Add(new Win32Exception(Marshal.GetLastWin32Error()).Message);

   MessageBox.Show(string.Join(" ", mess));


private void button1_Click(object sender, EventArgs e)

   SetReadonly(CreateHandle(textBox1.Text));

【讨论】:

以上是关于C#调用WinApi?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用C#操作WinAPI

(72)C#里怎么样调用WIN API的函数

C#、C++、WinAPI - 从另一个进程获取窗口数

如何在纯 C++ 中高效快速地清理我的 GDI 对象 - winapi(不是 .net,c#)?

C# WinAPI 从静态检查窗口文本

C#。 WinApi。在窗口上绘制