Windows Runtime 是微软在 Windows 8 之后引入的一套新的 API,部分功能只能通过 Windows Runtime 来调用。微软官方提供了 C++ 、.NET、JavaScript等语言下的SDK。这里我尝试使用纯C语言的方式来调用 Windows Runtime,一方面是想要了解原理,另一方面希望能用于在调用少量功能的场景下,避免引入体积较大的SDK。同时,这里使用C语言的调用方法,供其他语言参考也相对容易。
接下来通过调用 Windows.Web.Http.HttpClient
类的 GetStringAsync
方法的例子,来介绍如何用纯C语言调用Windows Runtime。
首先是初始化运行时环境。只需要调用 RoInitialize
即可。该函数定义在roapi.h
文件中。导入库为runtimeobject.lib
。在combase.dll
中导出。调用代码如下:
HRESULT hr = RoInitialize(RO_INIT_MULTITHREADED);
接下来来创建一个 Windows.Web.Http.HttpClient
对象。Windows Runtime对象与COM对象类似。但创建方式稍有不同。将HSTRING
类型的对象类名传入RoActivateInstance
方法来创建对象。调用代码如下:
LPWSTR className = L"Windows.Web.Http.HttpClient"; HSTRING_HEADER classNameHeader; HSTRING classNameHstr; WindowsCreateStringReference(className, lstrlenW(className), &classNameHeader, &classNameHstr); IInspectable* httpClient; hr = RoActivateInstance(classNameHstr, &httpClient);
这里用到了 WindowsCreateStringReference
函数来构造 HSTRING
,这个函数定义在 winstring.h。得到的结果为 IInspectable
类型的 COM 接口。 IInspectable
是所有 Windows Runtime
对象的公共接口。
接下来准备调用 GetStringAsync
方法的参数,该方法需要一个 Windows.Foundation.Uri
类型的参数。首先来创建作为参数的对象。这个对象需要借助 IUriRuntimeClassFactory
这一工厂来创建。构造 Windows.Foundation.Uri
的代码如下:
LPWSTR uriClassName = L"Windows.Foundation.Uri"; HSTRING_HEADER uriClassNameHeader; HSTRING uriClassNameHstr; __x_ABI_CWindows_CFoundation_CIUriRuntimeClassFactory* uriFactory; WindowsCreateStringReference(uriClassName, lstrlenW(uriClassName), &uriClassNameHeader, &uriClassNameHstr); hr = RoGetActivationFactory(uriClassNameHstr, &IID___x_ABI_CWindows_CFoundation_CIUriRuntimeClassFactory, (void**)&uriFactory); LPWSTR uriContent = L"https://blog.sdlsj.net"; HSTRING_HEADER uriContentHeader; HSTRING uriContentHstr; WindowsCreateStringReference(uriContent, lstrlenW(uriContent), &uriContentHeader, &uriContentHstr); __x_ABI_CWindows_CFoundation_CIUriRuntimeClass* uri; hr = uriFactory->lpVtbl->CreateUri(uriFactory, uriContentHstr, &uri);
上面的代码用到了 Windows Runtime 相关接口的定义,需要引用 windows.foundation.h
头文件。但是这里 IUriRuntimeClassFactory
的 IID
虽然有声明,但是并没有找到对应的lib库,这里我只能自己再定义一下。定义如下:
const IID IID___x_ABI_CWindows_CFoundation_CIUriRuntimeClassFactory = { 0x44a9796f, 0x723e, 0x4fdf, { 0xa2, 0x18, 0x03, 0x3e, 0x75, 0xb0, 0xc0, 0x84 } };
接下来,就使用刚才创建出来的 Uri 调用 GetStringAsync 。需要根据上面创建的HttpClient对象,获取到对应的 Windows.Web.Http.IHttpClient
接口,然后调用 GetStringAsync
方法。代码如下:
__x_ABI_CWindows_CWeb_CHttp_CIHttpClient* IHttpClient; hr = httpClient->lpVtbl->QueryInterface(httpClient, &IID___x_ABI_CWindows_CWeb_CHttp_CIHttpClient, &IHttpClient); __FIAsyncOperationWithProgress_2_HSTRING_Windows__CWeb__CHttp__CHttpProgress* operation; hr = IHttpClient->lpVtbl->GetStringAsync(IHttpClient, uri, &operation);
这里同样缺少一个IID,定义如下:
const IID IID___x_ABI_CWindows_CWeb_CHttp_CIHttpClient = { 0x7FDA1151, 0x3574, 0x4880, { 0xa8, 0xba, 0xe6, 0xb1, 0xe0, 0x06, 0x1f, 0x3d } };
由于该方法是一个异步方法,所以此时拿到的是一个 IAsyncOperationWithProgress
接口。接下来需要调用其 put_Completed
方法,传入一个对应 handler,即可在该异步操作完成时得到结果。相当于这里需要手动使用纯C实现一个COM接口。我简易实现了这个接口的代码,不考虑生存期。只是为了能在Invoke
方法里拿到异步操作的结果。需要注意的是,要在 QueryInterface
中处理实现 IAgileObject
,否则不会回调Invoke
方法,导致不能得到结果。代码如下:
__FIAsyncOperationWithProgressCompletedHandler_2_HSTRING_Windows__CWeb__CHttp__CHttpProgress handler = { 0 }; __FIAsyncOperationWithProgressCompletedHandler_2_HSTRING_Windows__CWeb__CHttp__CHttpProgressVtbl handlerVtbl = { 0 }; handler.lpVtbl = &handlerVtbl; handlerVtbl.QueryInterface = &QueryInterface; handlerVtbl.AddRef = &AddRef; handlerVtbl.Release = &Release; handlerVtbl.Invoke = &Invoke; operation->lpVtbl->put_Completed(operation, &handler);
HRESULT __stdcall QueryInterface(void* This, REFIID riid, void** ppvObject) { if (IsEqualIID(riid, &IID_IAgileObject)) { *ppvObject = This; return S_OK; } if (IsEqualIID(riid, &IID___FIAsyncOperationWithProgressCompletedHandler_2_HSTRING_Windows__CWeb__CHttp__CHttpProgress)) { *ppvObject = This; return S_OK; } return E_NOINTERFACE; } ULONG __stdcall AddRef(void* This) { return 0; } ULONG __stdcall Release(void* This) { return 0; } HRESULT __stdcall Invoke(void* This, __FIAsyncOperationWithProgress_2_HSTRING_Windows__CWeb__CHttp__CHttpProgress* asyncInfo, AsyncStatus asyncStatus) { HSTRING result; HRESULT hr = asyncInfo->lpVtbl->GetResults(asyncInfo, &result); return S_OK; }
const IID IID___FIAsyncOperationWithProgressCompletedHandler_2_HSTRING_Windows__CWeb__CHttp__CHttpProgress = { 0x98ab9acb, 0x38db, 0x588f, { 0xa5, 0xf9, 0x9f, 0x48, 0x4b, 0x22, 0x00, 0xcd } };
此时,我们在Invoke方法里打上断点,启动,就可以看到终于成功拿到了结果。同时也可以看到,回调是发生在线程池的后台线程中。效果如下:
综上可以看出,想要用纯C调用 Windows Runtime 需要手动处理的问题很多,包括对象创建、字符串类型转换、基础类型创建、异步操作的处理等等。