You need to enable JavaScript to run this app. 如何仅使用ctypes在Python中正确调用Win32 API的NetUserEnum函数
优惠活动
大模型
产品
解决方案
定价
更多

如何仅使用ctypes在Python中正确调用Win32 API的NetUserEnum函数

基于ctypes直连调用NetUserEnum的核心实现逻辑

核心认知纠正(解决LPBYTE *bufptr卡点)

很多人卡在这里本质是混淆了这类网络管理API的参数传递逻辑:
NetUserEnum的LPBYTE *bufptr是纯输出参数,和你之前用过的GlobalMemoryStatusEx的传入结构参数完全不同:你不需要提前为它预分配任何类型的缓冲区,只需要定义一个空的指针变量,把这个变量的地址传给API,API内部会自行调用系统内存分配逻辑,把申请好的内存块的起始地址写到你传入的指针变量里,你后续只需要基于这个地址做解析、最后调用对应的内存释放函数就行。

分步实现流程

1. 提前定义好所有函数签名

ctypes默认的参数类型推断在64位环境下会出现指针截断问题,必须手动声明每个函数的入参、返回值类型:

import ctypes
from ctypes import wintypes

# 加载对应系统库
netapi32 = ctypes.WinDLL("netapi32")

# 绑定NetUserEnum函数签名
NetUserEnum = netapi32.NetUserEnum
NetUserEnum.restype = wintypes.DWORD
NetUserEnum.argtypes = [
    wintypes.LPCWSTR,               # servername,本机调用传None
    wintypes.DWORD,                 # 信息结构级别
    wintypes.DWORD,                 # 枚举用户过滤标志
    ctypes.POINTER(wintypes.LPBYTE),# 核心输出参数:接收API分配的内存指针
    wintypes.DWORD,                 # 最大返回缓冲长度,传-1让系统自动分配
    wintypes.LPDWORD,               # 实际读到的条目数
    wintypes.LPDWORD,               # 符合过滤条件的总条目数
    wintypes.LPDWORD                # 分页续传句柄,单次全量枚举传None
]

# 绑定配套的内存释放函数签名,必须加
NetApiBufferFree = netapi32.NetApiBufferFree
NetApiBufferFree.restype = wintypes.DWORD
NetApiBufferFree.argtypes = [wintypes.LPVOID]

2. 定义和API匹配的用户信息结构

以枚举级别0(仅获取用户名)为例,对应Win32的USER_INFO_0结构,需要在ctypes里做完全对齐的映射:

class USER_INFO_0(ctypes.Structure):
    _fields_ = [
        ("usri0_name", wintypes.LPWSTR) # 用户名的宽字符串指针
    ]

如果要使用1级、2级等更高信息级别,只需要按照Win32头文件里的结构定义,把上面的类替换成对应字段的Structure即可,不影响后续的参数传递逻辑。

3. 正确传入bufptr参数完成调用

# 初始化空指针变量,不需要提前分配任何内存
buf_ptr = wintypes.LPBYTE()
entries_read = wintypes.DWORD()
total_entries = wintypes.DWORD()

# 调用API,核心是用byref把空指针变量的地址传进去
call_status = NetUserEnum(
    None,
    0,
    0x3, # 枚举普通用户域账号
    ctypes.byref(buf_ptr), # 这就是LPBYTE *bufptr的正确传法
    -1, # 对应MAX_PREFERRED_LENGTH,跳过自定义缓冲大小的限制
    ctypes.byref(entries_read),
    ctypes.byref(total_entries),
    None
)

4. 解析返回的内存块并释放资源

API成功调用后,buf_ptr指向的是一段连续存放的结构数组,需要把指针强制转换成对应结构类型的数组才能正确遍历,必须手动调用NetApiBufferFree释放API申请的非托管内存,Python的垃圾回收管不到这块内存:

if call_status == 0: # NERR_Success 代表调用成功
    # 将字节指针转换为对应长度的结构数组指针
    user_array = ctypes.cast(
        buf_ptr,
        ctypes.POINTER(USER_INFO_0 * entries_read.value)
    )[0]

    # 遍历所有枚举到的用户
    for user in user_array:
        print(f"枚举到本地用户:{user.usri0_name}")

    # 释放系统API申请的内存,杜绝内存泄漏
    NetApiBufferFree(buf_ptr)
else:
    print(f"调用失败,错误码:{call_status}")

常见踩坑提醒

  • 不要强行给bufptr参数预分配缓冲区:这类操作既会浪费Python侧的内存,还会被API覆盖掉你传入的指针值,最后漏释放API申请的内存造成泄漏
  • 不要漏掉函数签名的类型声明:64位Windows下如果把bufptr参数类型错写成wintypes.LPBYTE而不是POINTER(wintypes.LPBYTE),会直接因为栈对齐错误导致调用崩溃
  • 不要只解析单个结构:返回的内存块是连续的结构数组,必须按照实际读到的条目数做数组类型转换,否则只能拿到第一个用户的信息,甚至触发越界访问
  • 不要手动设置过小的prefmaxlen值:新手如果强行把这个参数设为1024、2048之类的小值,会触发ERROR_MORE_DATA的分页枚举逻辑,额外增加代码复杂度,直接传-1即可让系统自动分配足够的内存块

内容的提问来源于stack exchange,提问作者LTWood

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.04 13:06:03