You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确初始化COM对象?IDiskQuotaControl初始化异常排查

问题

尝试通过Win32 API的IDiskQuotaControl接口获取Windows卷的配额状态,但遇到初始化相关问题。无论传入何种卷路径(包括无效路径),Initialize方法均返回成功,但后续调用GetQuotaState时提示“系统找不到指定路径”,调用GetDefaultQuotaLimit返回错误码2147942421(0x80070015,含义为“该对象未初始化”)。

相关Go语言代码如下:

//go:build windows && amd64

package main

import (
    "flag"
    "fmt"
    "runtime"
    "sync"
    "syscall"
    "unsafe"

    "golang.org/x/sys/windows"
)

// START OF basic stuff

const COINIT_APARTMENTTHREADED = 0x2

const CLSCTX_INPROC_SERVER = 0x1
const CLSCTX_INPROC_HANDLER = 0x2
const CLSCTX_LOCAL_SERVER = 0x4

var (
    modole32 = syscall.NewLazyDLL("ole32.dll")

    procCoInitializeEx   = modole32.NewProc("CoInitializeEx")
    procCoUninitialize   = modole32.NewProc("CoUninitialize")
    procCoCreateInstance = modole32.NewProc("CoCreateInstance")
)

func CoInitializeEx(pvReserved uintptr, dwCoInit uint32) (r1, r2 uintptr, lastErr error) {
    r1, r2, lastErr = procCoInitializeEx.Call(
        uintptr(pvReserved),
        uintptr(dwCoInit),
    )
    return
}

func CoUninitialize() (r1, r2 uintptr, lastErr error) {
    r1, r2, lastErr = procCoUninitialize.Call()
    return
}

func CoCreateInstance(rclsid *GUID, pUnkOuter *byte, dwClsContext uint32, riid *GUID, ppv *uintptr) (r1, r2 uintptr, lastErr error) {
    r1, r2, lastErr = procCoCreateInstance.Call(
        uintptr(unsafe.Pointer(rclsid)),
        uintptr(unsafe.Pointer(pUnkOuter)),
        uintptr(dwClsContext),
        uintptr(unsafe.Pointer(riid)),
        uintptr(unsafe.Pointer(ppv)),
    )
    return
}

type GUID struct {
    Data1 uint32
    Data2 uint16
    Data3 uint16
    Data4 [8]byte
}

type IDiskQuotaControl struct {
    lpVtbl *IDiskQuotaControlVtbl
}

type IDiskQuotaControlVtbl struct {
    QueryInterface uintptr
    AddRef         uintptr
    Release        uintptr

    Initialize                     uintptr
    SetQuotaState                  uintptr
    GetQuotaState                  uintptr
    SetQuotaLogFlags               uintptr
    GetQuotaLogFlags               uintptr
    SetDefaultQuotaThreshold       uintptr
    GetDefaultQuotaThreshold       uintptr
    GetDefaultQuotaThresholdText   uintptr
    SetDefaultQuotaLimit           uintptr
    GetDefaultQuotaLimit           uintptr
    GetDefaultQuotaLimitText       uintptr
    AddUserSid                     uintptr
    AddUserName                    uintptr
    DeleteUser                     uintptr
    FindUserSid                    uintptr
    FindUserName                   uintptr
    CreateEnumUsers                uintptr
    CreateUserBatch                uintptr
    InvalidateSidNameCache         uintptr
    GiveUserNameResolutionPriority uintptr
    ShutdownNameResolution         uintptr
}

func (x *IDiskQuotaControl) AddRef() (r1, r2 uintptr, lastErr error) {
    r1, r2, lastErr = syscall.SyscallN(
        x.lpVtbl.AddRef,
        uintptr(unsafe.Pointer(x)),
    )
    return
}

func (x *IDiskQuotaControl) Release() (r1, r2 uintptr, lastErr error) {
    r1, r2, lastErr = syscall.SyscallN(
        x.lpVtbl.Release,
        uintptr(unsafe.Pointer(x)),
    )
    return
}

func (x *IDiskQuotaControl) Initialize(pszPath *uint16, bReadWrite int32) (r1, r2 uintptr, lastErr error) {
    r1, r2, lastErr = syscall.SyscallN(
        x.lpVtbl.Initialize,
        uintptr(unsafe.Pointer(x)),
        uintptr(unsafe.Pointer(pszPath)),
        uintptr(bReadWrite),
    )
    return
}

func (x *IDiskQuotaControl) GetQuotaState(pdwState *uint32) (r1, r2 uintptr, lastErr error) {
    r1, r2, lastErr = syscall.SyscallN(
        x.lpVtbl.GetQuotaState,
        uintptr(unsafe.Pointer(x)),
        uintptr(unsafe.Pointer(pdwState)),
    )
    return
}

func (x *IDiskQuotaControl) GetDefaultQuotaLimit(pllLimit *int64) (r1, r2 uintptr, lastErr error) {
    r1, r2, lastErr = syscall.SyscallN(
        x.lpVtbl.GetDefaultQuotaLimit,
        uintptr(unsafe.Pointer(x)),
        uintptr(unsafe.Pointer(pllLimit)),
    )
    return
}

var CLSID_DiskQuotaControl = &GUID{0x7988b571, 0xec89, 0x11cf, [8]byte{0x9c, 0x0, 0x0, 0xaa, 0x0, 0xa1, 0x4f, 0x56}}
var IID_IDiskQuotaControl = &GUID{0x7988b572, 0xec89, 0x11cf, [8]byte{0x9c, 0x0, 0x0, 0xaa, 0x0, 0xa1, 0x4f, 0x56}}

// END OF basic stuff

func getVolumeQuota(wg *sync.WaitGroup, volume string) {
    defer wg.Done()

    runtime.LockOSThread()
    defer runtime.UnlockOSThread()

    // Init COM
    r1, r2, lastErr := CoInitializeEx(
        0, // must be NULL
        COINIT_APARTMENTTHREADED,
    )
    defer CoUninitialize()
    fmt.Println("CoInitializeEx", r1, r2, lastErr) // Print results for debug

    // Create a COM instance
    var ppv uintptr
    r1, r2, lastErr = CoCreateInstance(
        CLSID_DiskQuotaControl,
        nil,
        CLSCTX_INPROC_SERVER,
        IID_IDiskQuotaControl,
        &ppv,
    )
    fmt.Println("CoCreateInstance", r1, r2, lastErr)

    diskQuotaControl := (*IDiskQuotaControl)(unsafe.Pointer(ppv))
    defer diskQuotaControl.Release()

    pszPath, err := windows.UTF16PtrFromString(volume)
    if err != nil {
        panic(err)
    }
    // Initialize seems to fail even the return is a success
    r1, r2, lastErr = diskQuotaControl.Initialize(
        pszPath,
        0, // false => read only
    )
    fmt.Println("Initialize", r1, r2, lastErr)

    var pdwState uint32
    r1, r2, lastErr = diskQuotaControl.GetQuotaState(
        &pdwState,
    )
    fmt.Println("GetQuotaState", r1, r2, lastErr)
    fmt.Println(pdwState)

    var pllLimit int64
    r1, r2, lastErr = diskQuotaControl.GetDefaultQuotaLimit(
        &pllLimit,
    )
    fmt.Println("GetDefaultQuotaLimit", r1, r2, lastErr)
    fmt.Println(pllLimit)
}

func main() {
    volume := flag.String("v", `C:\`, "volume")
    flag.Parse()
    fmt.Println("Volume", *volume)

    var wg sync.WaitGroup
    wg.Add(1)
    go getVolumeQuota(&wg, *volume)
    wg.Wait()
}

测试结果:

Volume C:\
CoInitializeEx 0 0 The operation completed successfully.
CoCreateInstance 0 0 The operation completed successfully.
Initialize 0 9298000 The operation completed successfully.
GetQuotaState 2147942403 9298000 The system cannot find the path specified.
0
GetDefaultQuotaLimit 2147942421 9298000 The operation completed successfully.
0 

Volume \\?\C:\
CoInitializeEx 0 0 The operation completed successfully.
CoCreateInstance 0 0 The operation completed successfully.
Initialize 0 9103168 The operation completed successfully.
GetQuotaState 2147942403 9103168 The system cannot find the path specified.
0
GetDefaultQuotaLimit 2147942421 9103168 The operation completed successfully.
0


Volume Invalid:Volume://+
CoInitializeEx 0 0 The operation completed successfully.
CoCreateInstance 0 0 The operation completed successfully.
Initialize 0 8709600 The operation completed successfully.
GetQuotaState 2147942403 8709600 The system cannot find the path specified.
0
GetDefaultQuotaLimit 2147942421 8709600 The operation completed successfully.
0
解决方案

问题出在COM调用的错误处理逻辑和调用约定的正确性上,以下是具体修复点:

1. 正确处理COM HRESULT返回值

Win32 COM方法的返回值是HRESULT,需要用windows.SUCCEEDED()或windows.FAILED()判断调用是否成功,而非依赖syscall.Call返回的lastErr——lastErr仅反映系统级调用错误,无法正确捕获COM内部的失败状态。

比如Initialize方法看似返回"成功",但实际可能已经失败,只是lastErr没有正确体现,导致后续调用因对象未初始化而报错。

2. 使用正确的stdcall调用函数

IDiskQuotaControl的所有方法都是stdcall调用约定,需要根据参数个数选择对应的syscall.Syscall/Syscall2/Syscall3等函数,避免因参数传递错误导致的隐性失败。

3. 修复后的关键代码修改

修改COM方法调用逻辑

以Initialize、GetQuotaState为例,调整调用方式并正确判断HRESULT:

func (x *IDiskQuotaControl) Initialize(pszPath *uint16, bReadWrite int32) (hresult uintptr, err error) {
    hresult, _, _ = syscall.Syscall3(
        x.lpVtbl.Initialize,
        3,
        uintptr(unsafe.Pointer(x)),
        uintptr(unsafe.Pointer(pszPath)),
        uintptr(bReadWrite),
    )
    if windows.FAILED(hresult) {
        err = windows.Errno(hresult & 0xFFFF)
    }
    return
}

func (x *IDiskQuotaControl) GetQuotaState(pdwState *uint32) (hresult uintptr, err error) {
    hresult, _, _ = syscall.Syscall2(
        x.lpVtbl.GetQuotaState,
        2,
        uintptr(unsafe.Pointer(x)),
        uintptr(unsafe.Pointer(pdwState)),
    )
    if windows.FAILED(hresult) {
        err = windows.Errno(hresult & 0xFFFF)
    }
    return
}

调整调用处的错误检查

在getVolumeQuota函数中,增加对Initialize返回值的检查:

hresult, err := diskQuotaControl.Initialize(pszPath, 0)
if err != nil {
    fmt.Printf("Initialize failed: %v (HRESULT: 0x%X)\n", err, hresult)
    return
}
fmt.Println("Initialize succeeded")

4. 额外注意事项

  • 确保运行程序时拥有管理员权限:IDiskQuotaControl的部分操作需要管理员权限才能访问卷配额信息。
  • 路径格式使用卷根路径:如C:\或\\?\C:\,避免使用相对路径或非卷根的路径。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 11:10:24