用纯C语言来调用Windows Runtime

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 头文件。但是这里 IUriRuntimeClassFactoryIID 虽然有声明,但是并没有找到对应的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 需要手动处理的问题很多,包括对象创建、字符串类型转换、基础类型创建、异步操作的处理等等。

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据