Microsoft官方WMI示例中的访问违规



我试图了解WMI是如何工作的,但是到目前为止给出的默认示例都很糟糕。

下面是调用Win32_Process类的Create方法的示例:

https://learn.microsoft.com/en-us/windows/win32/wmisdk/example--calling-a-provider-method

我已经为此添加了适当的错误处理,我们将每个调用的HRESULT存储在变量hres中,并检查调用是否失败。比如:

hres = pInParamsDefinition->SpawnInstance(0, &pClassInstance);
if (FAILED(hres))
{
wprintf("Failed to get class. Error code = 0x%lxn", hres);
return hres;
}

代码执行正确,直到这里:

// Create the values for the in parameters
VARIANT varCommand;
varCommand.vt = VT_BSTR;
varCommand.bstrVal = _bstr_t(L"notepad.exe");
// Store the value for the in parameters
hres = pClassInstance->Put(L"CommandLine", 0,
&varCommand, 0);
wprintf(L"The command is: %sn", V_BSTR(&varCommand));

pClassInstance->Put抛出'ol ' c5.

在这一点上,hresS_OKSpawnInstance调用,但这些是我们为类实例的指针:

+pClass 0x000001c04e73fca0 IWbemClassObject *
-   pClassInstance  0x000001c04e749d60 IWbemClassObject *
-       IUnknown    {...}   IUnknown
-       __vfptr 0x00007ff9f8d0ee98 {fastprox.dll!const CWbemInstance::`vftable'{for `_IWmiObject'}} {0x00007ff9f8c6f450 {fastprox.dll!CWbemObject::QueryInterface(void)}, ...}  void * *
[0x00000000]    0x00007ff9f8c6f450 {fastprox.dll!CWbemObject::QueryInterface(void)} void *
[0x00000001]    0x00007ff9f8c907d0 {fastprox.dll!CWbemObject::AddRef(void)} void *
[0x00000002]    0x00007ff9f8c8ffd0 {fastprox.dll!CWbemObject::Release(void)}    void *
+pInParamsDefinition 0x000001c04e743ca0 IWbemClassObject *

AndvarCommand:

+varCommand BSTR = 0x000001c04e74ffe8 tagVARIANT

调用栈:

oleaut32.dll!SysAllocString()
vfbasics.dll!AVrfpSysAllocString()
wbemcomn.dll!CVar::SetVariant()
fastprox.dll!CWbemInstance::Put()
>   Ele.exe!WMIConnection::InvokeMethod()

所以似乎bstrVal没有被正确设置,我认为?我尝试先用VariantInit初始化它,我还尝试在堆上动态分配它。

VARIANT varCommand;
VariantInit(&varCommand);
varCommand.vt = VT_BSTR;
varCommand.bstrVal = _bstr_t(L"notepad.exe");

我还尝试手动将变量缓冲区归零,但没有效果。当访问冲突发生时,bstrVal在内存转储中是这样的:

bstrVal 0x000001c04e74ffe8 <Error reading characters of string.>    wchar_t *
<Unable to read memory>                  wchar_t

这一行:

varCommand.bstrVal = _bstr_t(L"notepad.exe");

代码创建了一个临时_bstr_t对象,该对象超出了作用域,在分配给varCommand.bstrVal之后立即销毁分配的BSTR内存。因此,当将varCommand传递给pClassInstance->Put()时,varCommand.bstrVal将被悬空,指向无效内存。这是未定义行为

使用这个来保持BSTR存活,直到你真正使用它:

_bstr_t str(L"notepad.exe"); 
VARIANT varCommand;
varCommand.vt = VT_BSTR;
varCommand.bstrVal = str;
// use varCommand as needed...
// DO NOT call VarClear() or SysFreeString()!
// You don't own the BSTR memory...
//VarClear(&varCommand);

另外:

VARIANT varCommand;
varCommand.vt = VT_BSTR;
varCommand.bstrVal = SysAllocString(L"notepad.exe");
// use varCommand as needed...
// You DO own the BSTR memory, so free it!
VarClear(&varCommand);

否则,考虑使用_variant_t代替,让它为您管理内存:

_variant_t varCommand(L"notepad.exe");
hres = pClassInstance->Put(L"CommandLine", 0, &varCommand, 0);
wprintf(L"The command is: %sn", V_BSTR(&varCommand));

我明白了。互联网上有几个论坛帖子询问这个例子,没有给出解决方案,所以我很高兴现在提供这个。

Microsoft的例子使用了不正确的类。

在Microsoft的例子中,他们试图调用Win32_Process的类实例上的Put方法来设置参数。

这是不正确的。我们需要通过首先获取Win32_Process::Create的类方法定义来设置参数,然后在Win32_Process::Create新实例中设置其参数。

我们还需要构造一个Win32_ProcessStartup类对象的实例,因为它是Win32_Process::Create所需的输入参数。

在下面的例子中,我将填充Win32_ProcessStartup类实例的一个字段,您可以计算出其余的。

那么这段来自微软例子的代码:

IWbemClassObject* pClass = NULL;
hres = pSvc->GetObject(ClassName, 0, NULL, &pClass, NULL);
IWbemClassObject* pInParamsDefinition = NULL;
hres = pClass->GetMethod(MethodName, 0, 
&pInParamsDefinition, NULL);
IWbemClassObject* pClassInstance = NULL;
hres = pInParamsDefinition->SpawnInstance(0, &pClassInstance);
// Create the values for the in parameters
VARIANT varCommand;
varCommand.vt = VT_BSTR;
varCommand.bstrVal = _bstr_t(L"notepad.exe");
// Store the value for the in parameters
hres = pClassInstance->Put(L"CommandLine", 0,
&varCommand, 0);
wprintf(L"The command is: %sn", V_BSTR(&varCommand));

变成(无错误处理可读性):

// Get the class object
hres = pClass->GetMethod(lpwMethodName, 0,
&pInParamsDefinition, NULL);
hres = pInParamsDefinition->SpawnInstance(0, &pClassInstance);
// Get the Win32_ProcessStartup class object
IWbemClassObject* pStartupObject = NULL;
hres = pSvc->GetObject(_bstr_t(L"Win32_ProcessStartup"), 0, NULL, &pStartupObject, NULL);
// Create an instance of the Win32_ProcessStartup class object
IWbemClassObject* pStartupInstance = NULL;
hres = pStartupObject->SpawnInstance(0, &pStartupInstance);
// Create the value for the ShowWindow variable of the Win32_ProcessStartup class
VARIANT varParams;
VariantInit(&varParams);
varParams.vt = VT_I2;
varParams.intVal = SW_SHOW;

// And populate it
hres = pStartupInstance->Put(_bstr_t(L"ShowWindow"), 0, &varParams, 0);
// Get the method definition for Win32_Process::Create and store it in pInParamsDefinition
hres = pClass->GetMethod(_bstr_t(lpwMethodName), 0, &pInParamsDefinition, NULL);
// Spawn an instance of the Create method and store it in pParamsInstance
IWbemClassObject* pParamsInstance = NULL;
hres = pInParamsDefinition->SpawnInstance(0, &pParamsInstance);
// Now we can set the parameters without error
hres = pParamsInstance->Put(_bstr_t(L"CurrentDirectory"), 0, pvCurrentDirectory, 0);

请注意,应该检查所有这些hres返回是否失败。

Win32_ProcessStartup的定义如下:

https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-processstartup

Win32_ProcessCreate方法定义:

https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process