如何使用C应用程序从WMI获取数据?

8
我有一个纯C应用程序,它通过IOCTL调用与我的适配器驱动程序通信并显示信息,但是这是使用Visual Developer Studio 5(非托管代码)编译的...然而,我需要从我的适配器中获取一些信息,使用WMI来实现通信...我的谷歌搜索结果显示,我需要编写一个使用COM的C++应用程序或者一个使用.NET的C#应用程序才能与WMI进行通信。 a) 真的吗?没有其他方法可以让我的C应用程序实现吗? b) 如果以上是真的,那么我需要做哪些最小级别的更改来完成我的项目/工作区设置?
谢谢 Som
2个回答

15

你可以从C中调用COM。语法比C ++不太友好,但它确实可以工作。最初设计COM时,它可以从C或C ++中工作,原生的C语言支持包含在COM和WMI头文件中。不过需要注意,你的程序将负责分配所有必要的对象,检查每个COM调用的错误条件,并释放其实例化的对象。

当使用以C++为目标的文档时,请转换形式为COM调用:

pSomething->Method(arg1, ...); // C++

目标:

pSomething->lpVtbl->Method(pSomething, arg1, ...); // C

以下是我最短的 C 代码,可以从 WMI 获取一些信息。如果成功,它应该列出计算机上的处理器以及它们的时钟频率(单位为 MHz)。该程序会处理它分配的资源,但它不进行任何错误检查(在继续每个步骤之前,您应该查看这些 hr 值)。

这是一个 Visual Studio 2008 的“Win32 控制台应用程序”,主文件已重命名为 .c 扩展名,并删除了额外的 stdafx 文件。要使程序链接,请确保在项目属性下的“配置属性/链接器/输入/附加依赖项”中包含 wbemuuid.lib。它在我的 Vista 上成功运行。

#define _WIN32_WINNT 0x0400
#define _WIN32_DCOM

#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <wbemidl.h>

void _tmain(int argc, _TCHAR* argv[])
{
    // result code from COM calls
    HRESULT hr = 0;

    // COM interface pointers
    IWbemLocator         *locator  = NULL;
    IWbemServices        *services = NULL;
    IEnumWbemClassObject *results  = NULL;

    // BSTR strings we'll use (http://msdn.microsoft.com/en-us/library/ms221069.aspx)
    BSTR resource = SysAllocString(L"ROOT\\CIMV2");
    BSTR language = SysAllocString(L"WQL");
    BSTR query    = SysAllocString(L"SELECT * FROM Win32_Processor");

    // initialize COM
    hr = CoInitializeEx(0, COINIT_MULTITHREADED);
    hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);

    // connect to WMI
    hr = CoCreateInstance(&CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, &IID_IWbemLocator, (LPVOID *) &locator);
    hr = locator->lpVtbl->ConnectServer(locator, resource, NULL, NULL, NULL, 0, NULL, NULL, &services);

    // issue a WMI query
    hr = services->lpVtbl->ExecQuery(services, language, query, WBEM_FLAG_BIDIRECTIONAL, NULL, &results);

    // list the query results
    if (results != NULL) {
        IWbemClassObject *result = NULL;
        ULONG returnedCount = 0;

        // enumerate the retrieved objects
        while((hr = results->lpVtbl->Next(results, WBEM_INFINITE, 1, &result, &returnedCount)) == S_OK) {
            VARIANT name;
            VARIANT speed;

            // obtain the desired properties of the next result and print them out
            hr = result->lpVtbl->Get(result, L"Name", 0, &name, 0, 0);
            hr = result->lpVtbl->Get(result, L"MaxClockSpeed", 0, &speed, 0, 0);
            wprintf(L"%s, %dMHz\r\n", name.bstrVal, speed.intVal);

            // release the current result object
            result->lpVtbl->Release(result);
        }
    }

    // release WMI COM interfaces
    results->lpVtbl->Release(results);
    services->lpVtbl->Release(services);
    locator->lpVtbl->Release(locator);

    // unwind everything else we've allocated
    CoUninitialize();

    SysFreeString(query);
    SysFreeString(language);
    SysFreeString(resource);
}

嗨,Oren, 非常感谢,这在Win2008上运行得像个冠军。然而,当我第一次在Visual Studio 2005上尝试时,遇到了编译问题,下面是它们:error C2065: 'COINIT_MULTITHREADED' : 未声明的标识符 error C2065: 'EOAC_NONE' : 未声明的标识符有什么解决方法适用于2005吗?谢谢 Som - smam
尝试在包含文件之前添加#define _WIN32_WINNT 0x0400和#define _WIN32_DCOM。如果这不能解决问题,只需将两个缺失的常量替换为0即可。 - Oren Trutner
嗨,Oren, 是的,它与第一个定义符号一起工作,再次非常感谢。 - smam
了解所有细节固然好,但知道有忘记它们的选项可能更好 :-) - Wolf

3
另一个选项,如果您希望尽可能减少对现有C应用程序的影响,则是编写一个DLL,该DLL在内部可以使用C++和COM包装器类来查询所需的WMI信息。
这个DLL可以提供一个纯C接口以适应您的应用程序。这是我会选择的方法。

1
绝对的,用C语言处理所有COM相关的东西不仅很麻烦,而且与实际应用程序相比处于完全不同的抽象层面。 - Wolf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接