Home > Enterprise >  How to properly initialize a COM object?
How to properly initialize a COM object?

Time:09-22

I'm trying to get the quota state of a volume in Windows using the win32 api through IDiskQuotaControl interface

The problem that I got into seems to be related the initialization. Please see the code below.

//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()
}

The reason why I think the problem is in the Initialize, is that whatever input I put (using the flag -v) I always get a success return.

Results:

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

The error in GetDefaultQuotaLimit (2147942421 = 0x80070015) translates to "This object has not been initialized"

CodePudding user response:

The IDiskQuotaControl interface documentation:

The IDiskQuotaControl interface inherits from the IUnknown interface.

is wrong! IDiskQuotaControl derives from IConnectionPointContainer , not from IUnknown. You can see that in dskquota.h:

DECLARE_INTERFACE_IID_(IDiskQuotaControl, IConnectionPointContainer, "7988B572-EC89-11cf-9C00-00AA00A14F56")

So, your vtable definition is wrong, you must add two methods between your Release and Initialize methods.

  • Related