C#访问Win 32的一些尝试

使用C#调用Win 32 Api大部分情况下基本只涉及到参数类型的转变,但在遇到Win 32 Api返回LPVOID *lpBuff 时会遇到一些解析遍历难题。lpBuff为二维指针,*lpBuff是指向其内容的数组的首地址,在C/C++中可直接通过数组下标进行访问。但在C#中会有如下问题:

  1. 无法像C/C++那样进行结构体指针强制转变;
  2. 无法进行(*lpBuff)[0]这样的直接取值操作;
  3. 无法执行lptemp++这样的指针累加/累减遍历操作;

在相关文档中,可以看到C#使用IntPtr封装了指针,Marshal提供了一些快捷方法来实现*lpstruct to Struct。但是在IntPtr的遍历上依然需要一些技巧。以获取网络组的Win 32 Api为例:

Win 32 Api:

typedef struct _LOCALGROUP_INFO_1 {
  LPWSTR lgrpi1_name;
  LPWSTR lgrpi1_comment;
} LOCALGROUP_INFO_1, *PLOCALGROUP_INFO_1, *LPLOCALGROUP_INFO_1;

NET_API_STATUS NetLocalGroupEnum(
  _In_    LPCWSTR    servername,
  _In_    DWORD      level,
  _Out_   LPBYTE     *bufptr,
  _In_    DWORD      prefmaxlen,
  _Out_   LPDWORD    entriesread,
  _Out_   LPDWORD    totalentries,
  _Inout_ PDWORD_PTR resumehandle
);

To C# :

[StructLayout(LayoutKind.Sequential)]
internal struct LOCALGROUP_INFO_1
        {
           [MarshalAs(UnmanagedType.LPWStr)]
            public string lgrpi1_name;       [MarshalAs(UnmanagedType.LPWStr)]        public string lgrpi1_comment;

    }
DllImport("Netapi32.dll",CharSet = CharSet.Unicode, SetLastError = true)] internal extern static uint NetLocalGroupEnum([MarshalAs(UnmanagedType.LPWStr)] string servername, int level, out IntPtr bufptr, int prefmaxlen, out int entriesread, out int totalentries, ref int resume_handle);

该函数获取本地机器上的所有网络组,组结构包含了组的名字以及组的描述。 NetLocalGroupEnum函数内部分配了bufptr,返回给调用者。GetGroupList的C#代码如下:

public static IEnumerable<dynamic> GetGroupList(this Vpn v) //函数代码基本上copy自[8]
        {
            int size = 4096;    //Start with 4k
            var bufptr = new IntPtr(size);
            int level = 0;
            int prefmaxlen = 1023;
            int entriesread = 0;
            int totalentries = 0;
            int resume_handle = 0;
            uint err = 0;
            do
            {
                err = NetLocalGroupEnum(
                    null,
                    level,
                    out bufptr,
                    prefmaxlen,
                    out entriesread,
                    out totalentries,
                    ref resume_handle
                );
                switch (err)
                {
                    //If there is more data, double the size of the buffer...
                    case NERR_BufTooSmall:        //NERR_BufTooSmall
                    case ERROR_MORE_DATA:        //ERROR_MORE_DATA
                        size *= 2;
                        bufptr = new IntPtr(size);
                        prefmaxlen = size - 1;    //Increase the size you want read as well
                        resume_handle = 0;    //And reset the resume_handle or you‘ll just pick up where you left off.
                        break;
                    case 2351:    //NERR_InvalidComputer
                        throw new Exception("NERR_InvalidComputer");
                    case 0:        //NERR_Success
                    default:
                        break;
                }
            }
            while (err == ERROR_MORE_DATA);    // and start over

            //GROUP_INFO_0 group=new GROUP_INFO_0(); //See user type above
            LOCALGROUP_INFO_1 group;
            var glist = new List<dynamic>(entriesread);
            var iter = bufptr;

            for (int i = 0; i < entriesread; i++)
            {
                group = Marshal.PtrToStructure<LOCALGROUP_INFO_1>(iter);
                glist.Add(new { Name = group.lgrpi1_name,
                    Commet = group.lgrpi1_comment});
                iter = (IntPtr)(iter.ToInt64() + Marshal.SizeOf<LOCALGROUP_INFO_1>());
            }

            NetApiBufferFree(bufptr);
            return glist;
        }

其中 group = Marshal.PtrToStructure<LOCALGROUP_INFO_1>(iter); 负责将byte[]转换为结构体;iter = (IntPtr)(iter.ToInt64() + Marshal.SizeOf<LOCALGROUP_INFO_1>()); 负责遍历。

在遍历过程中,iter = (IntPtr)(iter.ToInt64() + Marshal.SizeOf<LOCALGROUP_INFO_1>())中获取获取指针数组基地址的函数是iter.ToInt64() 。不论把编译设置为X86,还是X64,ToInt64和ToInt32在任何平台下都可以正常使用。但是ToInt32在64位系统中,可能会溢出。ToInt32与ToInt64与具体的指针类型(Int32* or Int64*)无关,它只是一个逻辑整数,是其基地址的值。IntPtr还有一个扩展方法ToInt(),经测试无法用来遍历。

如上即可进行Win32 API返回缓冲区的遍历,但经过几次测试,发现该方法工作并不稳定。当返回的GROUP_INFO_1实例中存在Comment = NULL时,内存读取会出错。出错时,可能是读取NULL出错;也可能是读取到NULL之后,进行下一次遍历时出错。考虑到可能声明为[MarshalAs(UnmanagedType.LPWStr)]存在出错可能,决定仿照其他一些写法,将其字符串指针LPCWSTR声明为IntPtr进行处理。

改变如下:

[StructLayout(LayoutKind.Sequential)]
internal struct LOCALGROUP_INFO_1
{
    //[MarshalAs(UnmanagedType.LPWStr)]
    //public string grpi1_name;
    public IntPtr grpi1_name;
    //[MarshalAs(UnmanagedType.LPWStr)]
    //public string grpi1_comment;
    public IntPtr grpi1_comment;
}
//解析
for (int i = 0; i < entriesread; i++)
{
    group = Marshal.PtrToStructure<LOCALGROUP_INFO_1>(iter);
    glist.Add(new { Name = Marshal.PtrToStringUni(group.grpi1_name),
    Commet = Marshal.PtrToStringUni(group.grpi1_comment)});
    iter = (IntPtr)(iter.ToInt64() + Marshal.SizeOf<LOCALGROUP_INFO_1>());
}

依然出错,但有好转。entriesread返回当前存在19个组,经查看windows【管理】确实也存在19个组。但与LPWSTR定义一样,当在第9个组出现comment = null之后,接下来的所有组Name & Comment均为null,继而在第18个记录时报错。从Windows的用户组看来,所有的组都存在描述,而遍历时却找不到描述信息,这确实很奇怪。

通过仔细查看遍历记录,发现遍历全部为用户名,而没有描述。突然想到是否是用户组信息的类别出错,果然,错误第使用了样例代码的Level 0,而应该将Level设置为1。设置为1之后,在原代码上工作不正确,提示需要更多内存。修改原代码后能正确工作,并指出其中的一个错误。(示例代码来自[8])

 public static IEnumerable<dynamic> GetGroupList(this Vpn v)
        {
            var bufptr = IntPtr.Zero;//  在[8]中,此处new IntPtr(size)为Intptr的默认句柄值,而不是缓冲区大小。在内存不足时,不是也不能通过扩大Size来增加内存。prefmaxlen才是缓冲区的大小。
            uint level = 1;
            uint prefmaxlen = MAX_PREFERRED_LENGTH; //此处可以设置为最大,并不需要太担心内存问题。这是个最大接收值,内存Win 32是动态创建的。*除非你不想一次获取这么多数据。
            uint entriesread = 0;
            uint totalentries = 0;
            uint resume_handle = 0;
            uint err = 0;
            err = NetLocalGroupEnum(
                null,
                level,
                ref bufptr,
                prefmaxlen,
                ref entriesread,
                ref totalentries,
                ref resume_handle
            );

            LOCALGROUP_INFO_1 group;
            var glist = new List<dynamic>(entriesread.ToInt());
            var iter = bufptr;

            for (int i = 0; i < entriesread; i++)
            {
                group = Marshal.PtrToStructure<LOCALGROUP_INFO_1>(iter);
                glist.Add(new { Name = group.lgrpi1_name,
                    Comment = group.lgrpi1_comment});
                iter = (IntPtr)(iter.ToInt64() + Marshal.SizeOf<LOCALGROUP_INFO_1>());
            }

            NetApiBufferFree(bufptr);
            return glist;
        }

工作正确,输出如下:

Access Control Assistance Operators, 此组的成员可以远程查询此计算机上资源的授权属性和权限。
Administrators, 管理员对计算机/域有不受限制的完全访问权
Backup Operators, 备份操作员为了备份或还原文件可以替代安全限制
Cryptographic Operators, 授权成员执行加密操作。
Distributed COM Users, 成员允许启动、激活和使用此计算机上的分布式 COM 对象。
Event Log Readers, 此组的成员可以从本地计算机中读取事件日志
Guests, 按默认值,来宾跟用户组的成员有同等访问权,但来宾帐户的限制更多
Hyper-V Administrators, 此组的成员拥有对 Hyper-V 所有功能的完全且不受限制的访问权限。
IIS_IUSRS, Internet 信息服务使用的内置组。
Network Configuration Operators, 此组中的成员有部分管理权限来管理网络功能的配置
Performance Log Users, 该组中的成员可以计划进行性能计数器日志记录、启用跟踪记录提供程序,以及在本地或通过远程访问此计算 机来收集事件跟踪记录
Performance Monitor Users, 此组的成员可以从本地和远程访问性能计数器数据
Power Users, 包括高级用户以向下兼容,高级用户拥有有限的管理权限
Remote Desktop Users, 此组中的成员被授予远程登录的权限
Remote Management Users, 此组的成员可以通过管理协议(例如,通过 Windows 远程管理服务实现的 WS-Management)访问 WMI 资源。 这仅适用于授予用户访问权限的 WMI 命名空间。
Replicator, 支持域中的文件复制
System Managed Accounts Group, 此组的成员由系统管理。
Users, 防止用户进行有意或无意的系统范围的更改,但是可以运行大部分应用程序
__vmware__, VMware User Group

参考

[1] https://msdn.microsoft.com/zh-cn/library/fzhhdwae%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396

[2] https://msdn.microsoft.com/zh-cn/library/system.intptr.subtract(v=vs.110).aspx

[3] 指针类型, https://msdn.microsoft.com/zh-cn/library/aa664771(v=vs.71).aspx

[4] 指针类型(C#编程指南), https://msdn.microsoft.com/zh-cn/library/y31yhkeb.aspx

[5] 类型支持, https://msdn.microsoft.com/zh-cn/library/c300dbaa(v=vs.90).aspx

[6] 平台调用教程, https://msdn.microsoft.com/zh-cn/library/aa288468(v=vs.71).aspx

[7] a wiki for .net developers, http://www.pinvoke.net/

[8]NetLocalGroupEnum, http://www.pinvoke.net/default.aspx/netapi32.NetLocalGroupEnum

时间: 2024-10-26 21:15:48

C#访问Win 32的一些尝试的相关文章

apache(OS 10013)以一种访问权限不允许的方式做了一个访问套接字的尝试 ...

今天启动apache时,报了“(OS 10013)以一种访问权限不允许的方式做了一个访问套接字的尝试. : make_sock: could not bind to address 0.0.0.0:80”的错误.从网上查是是端口冲突,仔细检查发现80端口被IIS占用了. 解决方法:改端口号,打开.\apache\conf\httpd.conf,将其中的监听端口,由80改为81, # Change this to Listen on specific IP addresses as shown b

C#面对“重载”的Win 32 函数

在Win32 Api中有很多添加/设置函数在参数上支持多种不同类型的结构体.这些参数定义为LPVOID* 或者LPBYTE,LPVOID*一般由Win32 分配内存空间,在C#从通过System.IntPtr进行遍历[1].LPBYTE空间在函数调用的外部进行分配,之所以定义为LPBYTE,是因为这些结构体可能并不相同——属性.大小不同.那么在C#中如何处理这种LPBYTE接收多种结构体引用的Win32 Api? 先看一个Win 32 API的定义: //The NetUserSetInfo f

未经处理的异常:System.Net.Sockets.SocketException: 以一种访问权限不允许的方式做了一个访问套接字的尝试

报错:未经处理的异常:System.Net.Sockets.SocketException: 以一种访问权限不允许的方式做了一个访问套接字的尝试 → 尝试以"管理员身份"运行程序,不行.→ 点击菜单下的"运行",输入"cmd",来到控制台.→ 输入"netstat -a"查看端口是否被占用,发现应用程序中所使用的端口已被占用.→ 更改应用程序的端口,问题解决.

启动django应用报错 “Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。”

启动django应用时报如下错误 "Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试." 网上查了一下,是8000端口被其他程序占用了,杀掉占用的程序就可以 (1)查找哪个进程占用了8000端口 E:\sign_system\guest>netstat -ano|findstr 8000 (2)找出对应pid的进程详细信息 E:\sign_system\guest>tasklist |findstr 5516 (3)杀掉该进

Linux远程登录访问win 7系统详细配置步骤

首先是建立32位的Linux操作系统,因为我这里的RPM安装包支持32位的操作系统,我使用的是RHEL6.2版本的操作系统. 安装操作系统前面两章博客已经说过了,不在啰嗦. 这里从配置Linux的网卡IP地址服务做起.如图所示配置虚拟机的IP地址. 下图是在输入了命令vi /etc/sysconfig/network-scripts/ifcfg-eth0之后进入的配置状态,按i输入内容,下面是需要注意的几个点. 查看网卡配置服务状态. 在宿主机中打开网卡连接,设置虚拟机连接的网卡和虚拟机在同一个

安装 Apache 出现 &lt;OS 10013&gt; 以一种访问权限不允许的方式做了一个访问套接字的尝试

如下截图: 提示: make_sock: could not bind to address 0.0.0.0:80 这个问题有由于计算机上安装了IIS7,80端口已占用.打开Apache 的配置文件  Apache安装目录下的conf/httpd.conf找到Listen 80 将80改为 88 重启Apache访问 http://localhost:88

(转)安装 Apache 出现 &lt;OS 10013&gt; 以一种访问权限不允许的方式做了一个访问套接字的尝试

在安装Apache的过程中出现: 仔细查看提示: make_sock: could not bind to address 0.0.0.0:80 恍然大悟,计算机上安装了IIS7,80端口已占用. 打开Apache 的配置文件  Apache安装目录下的conf/httpd.conf 找到Listen 80 将80改为 81 重启Apache访问 http://localhost:81

解决Win8设置为快速启动后ubuntu不能访问win下磁盘的方法

Use ntfsfix in the terminal , even if you can't access Windows sudo ntfsfix /dev/XY //Previous wasn't working for me. where XY is the partition e.g sda2 or sdb1 ntfsfix repairs some fundamental NTFS inconsistencies, resets the NTFS journal file and s

Win 32平台SDK中的文件操作

读取文件: HANDLE hFile ; // 声明文件操作内核对象句柄 hFile = CreateFile("1.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) ; char buf[100] ; DWORD num ; ReadFile(hFile, buf, 100, &num, NULL) ; // 第四个参数指定的是实际读取的文件大小 buf[num] = 0 ; //