从传统COM简析WinRT的Async(使用WRL)

WinRT出来有3年了,用的人并不多,用C++来用它的人更是少。
照成这样不外乎Windows 8平台不给力,用户基数少,但是WinRT本身的难用也是一个方面的原因,首先WinRT的所谓大宇50ms的任务全部Async化,这对习惯了传统Win32桌面的程序员来说 —— 你在逗我玩呢。
现在我们来简单看看WinRT的Async模型。

实在没太多必要介绍那些理论知识,就像这种文章:
http://blogs.msdn.com/b/windowsappdev_cn/archive/2012/03/26/windows.aspx
或者这种文章:
http://blogs.msdn.com/b/windowsappdev_cn/archive/2012/04/30/winrt-await.aspx(深入探究 WinRT 和 await)
这些文章充满了浆糊和痛苦,就像MSDN写的函数声明介绍一样。

我希望屏幕前的你对微软的传统COM技术(进程内COM)有了解,知道ATL帷幔之下的COM原始情况,也就是我希望你曾经徒手从IUnknown开始,到QueryInteface,到AddRef、Release写过,然后使用DllGetClassObject来new这个实例,也就是你曾经有过不错的COM开发经验,对COM有较好的认识。
如果你不曾了解这些,这文章还是关了把,上面那2个的连接也许有一个适合你。
嗯,这篇文章是为我们传统的Win32桌面C++程序员准备的!

异步,对我们传统的Win32程序员来说。
不外乎:
SubmitTask
DoWork(new Thread)...
CallbackNotify
EndTask

也就是,我们跑的Task,必需要能同步\异步通知到它的所有者,并且把处理结果交过来。
那WinRT的异步,也不外乎这样。

来我们看代码,下面的代码,请建立控制台工程:
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <string>
#include <iostream>
#include <memory>

#include <Windows.h>

#include <wrl.h>
#include <wrl\client.h>
#include <wrl\wrappers\corewrappers.h>

#include <roapi.h>

#include <windows.storage.h>
#include <windows.storage.streams.h>

using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;

using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Storage;
using namespace ABI::Windows::Storage::Streams;

int main()
{
    RoInitialize(RO_INIT_MULTITHREADED);

    ComPtr<IKnownFoldersStatics> pKnownFolders;
    HRESULT hr = RoGetActivationFactory(HStringReference(RuntimeClass_Windows_Storage_KnownFolders).Get(),
        IID_PPV_ARGS(pKnownFolders.GetAddressOf()));

    ComPtr<IStorageFolder> pFolder;
    pKnownFolders->get_VideosLibrary(&pFolder);

    RoUninitialize();

    getchar();
    return 0;
}
好了,可以看到,我们拿到了用户的视频文件夹,从上面的代码大家可以发现,这些代码,头文件和using多生死,主函数真正有用的代码满打满算就 4行,是不是微软脑残?
恩对,是微软脑残,好了这个不说。

我们看到我们拿到了一个IStorageFolder接口实例,代码目前还算正常。
然后我们想在视频文件夹里面新建立一个文件,IStorageFolder提供了一个名为 CreateFileAsync的脑残方法,我们看看这个方法:

前面2个参数都正常,第三个是什么玩意儿?微软脑袋浆糊了?
好了这个不说,我们还是看下实际情况。

我们F12“转到定义”,然后我们可以看到,浆糊的定义是:
__FIAsyncOperation_1_Windows__CStorage__CStorageFile **operation

那这个浆糊的__FIAsyncOperation_1_Windows__CStorage__CStorageFile是什么鬼玩意儿?
我们再次转到定义:
#define __FIAsyncOperation_1_Windows__CStorage__CStorageFile ABI::Windows::Foundation::__FIAsyncOperation_1_Windows__CStorage__CStorageFile_t

这个操蛋的ABI::Windows::Foundation::__FIAsyncOperation_1_Windows__CStorage__CStorageFile_t又是什么?再次F12,原来就在上面:
typedef IAsyncOperation<ABI::Windows::Storage::StorageFile*> __FIAsyncOperation_1_Windows__CStorage__CStorageFile_t;

我们的三官总算正常了,这玩意儿是一个:IAsyncOperation <ABI::Windows::Storage::StorageFile*>
可你仔细看,不对不对,这东西不正常,还是没有三观。

首先WinRT是COM的模型,IAsyncOperation是一个COM接口对吧,为什么这个COM接口还有模板参数?微软辣条吃多了?
我们在IAsyncOperation上F12,就来到了一个让你脑袋浆糊的一片代码:
namespace ABI { namespace Windows { namespace Foundation {
template <>
struct __declspec(uuid("5e52f8ce-aced-5a42-95b4-f674dd84885e"))
IAsyncOperation<ABI::Windows::Storage::StorageFile*> : IAsyncOperation_impl<ABI::Windows::Foundation::Internal::AggregateType<ABI::Windows::Storage::StorageFile*, ABI::Windows::Storage::IStorageFile*>> {
static const wchar_t* z_get_rc_name_impl() {
return L"Windows.Foundation.IAsyncOperation`1<Windows.Storage.StorageFile>"; }
};

此时我们还是可以继续对着IAsyncOperation_impl按F12,但是还是不用先,先让我们的脑袋粘着。

也就是说,我们要使用CreateFileAsync,我们可以这样写:
    ComPtr<__FIAsyncOperation_1_Windows__CStorage__CStorageFile> pFile;

    pFolder->CreateFileAsync(HStringReference(L"123.mp4").Get(),
        CreationCollisionOption::CreationCollisionOption_ReplaceExisting,
        pFile.GetAddressOf());

或者直观一点:
    ComPtr<IAsyncOperation<ABI::Windows::Storage::StorageFile*>> pFile;

    pFolder->CreateFileAsync(HStringReference(L"123.mp4").Get(),
        CreationCollisionOption::CreationCollisionOption_ReplaceExisting,
        pFile.GetAddressOf());

但是大家是不是感觉很难受?十分十分难受?
难道我还要写一个AsyncOperationCompletedHandler类,来接受这个Async返回值?
真是微软脑残对吧?

我们仔细研究,可以看到:ComPtr<IAsyncOperation<ABI::Windows::Storage::StorageFile*>> pFile;
这个玩意儿,是个ComPtr,也就是IAsyncOperation<ABI::Windows::Storage::StorageFile*>这玩意儿,其实是个COM接口实例,是COM的话,那我们理论上可以这样:
    ComPtr<IUnknown> pUnk;

    pFolder->CreateFileAsync(HStringReference(L"123.mp4").Get(),
        CreationCollisionOption::CreationCollisionOption_ReplaceExisting,
        (__FIAsyncOperation_1_Windows__CStorage__CStorageFile**)pUnk.GetAddressOf());

我相信大家可能舒服了,就像泄了一样。

好,现在我们成功执行CreateFileAsync返回了一个pUnk,但是pUnk没用啊,我们必需拿到点什么。。。。
可能你会想,我进行pUnk->QueryInterface,给他一个IAsyncOperation?不不不,你做不到的,不信你可以试试:

哈哈,IAsyncOperation这接口连IID_IAsyncOperation都没有,我们仔细看看,我们可以发现,微软说IAsyncOperation接口继承来自IAsyncInfo,也就是说,这个pUnk应该是可以拿到IAsyncInfo的,而IAsyncInfo是没有模板参数(返回值)的,好,我们来QI试试:
    ComPtr<IAsyncInfo> pAsyncInfo;
    pUnk->QueryInterface(IID_PPV_ARGS(pAsyncInfo.GetAddressOf()));

我们成功拿到了pAsyncInfo,而IAsyncInfo能干啥呢?它能查询Async操作的返回值。。。
于是我们可以写一个类似C#的await:
    //await
    while (1)
    {
        AsyncStatus asyncStatus;
        HRESULT hr = pAsyncInfo->get_Status(&asyncStatus);

        if (SUCCEEDED(hr) && (asyncStatus == AsyncStatus::Completed))
            break;

        SwitchToThread();
    }

这样我们就成功执行完成CreateFileAsync了,是不是很脑残?

好了我们回来这个IAsyncOperation。
通过上面,大家知道IAsyncOperation继承IAsyncInfo,扩展出来了几个方法:
public interface IAsyncOperation<TResult> : IAsyncInfo
{
    AsyncOperationCompletedHandler<TResult> Completed { get; set; }
    TResult GetResults();
}

浆糊总算解开了一点,可还是感觉不爽。
如果我们不使用IAsyncOperation、AsyncOperationCompletedHandler,就没有GetResult,拿不到返回的接口。
难道我们真得还是的写一个模板类实现AsyncOperationCompletedHandler?这也太脑残了。

这真是浆糊,但是我们要装逼,又不想用ComPtr<IAsyncOperation<ABI::Windows::Storage::StorageFile*>> pFile。
可不管咋样,我们知道,ComPtr<IUnknown> pUnk,这个里面,是肯定有IAsyncOperation的对吧,对吧,对吧?

回到上面那个浆糊的代码:
namespace ABI { namespace Windows { namespace Foundation {
template <>
struct __declspec(uuid("5e52f8ce-aced-5a42-95b4-f674dd84885e"))
IAsyncOperation<ABI::Windows::Storage::StorageFile*> : IAsyncOperation_impl<ABI::Windows::Foundation::Internal::AggregateType<ABI::Windows::Storage::StorageFile*, ABI::Windows::Storage::IStorageFile*>> {
static const wchar_t* z_get_rc_name_impl() {
return L"Windows.Foundation.IAsyncOperation`1<Windows.Storage.StorageFile>"; }
};

结构上面你看到了uuid,你可能想到了什么,没错,我们来尝试:
ComPtr<IAsyncOperation<ABI::Windows::Storage::StorageFile*>> pNewFile;
pUnk->QueryInterface(IID_PPV_ARGS(pNewFile.GetAddressOf()));

然后我们发现我们成功了,于是可以拿返回值了:
ComPtr<IStorageFile> pFile;
pNewFile->GetResults(pFile.GetAddressOf());

好,一切都很有趣,为什么没有IID_IAsyncOperation这个东西,我们却是可以进行IID_PPV_ARGS?我估计你想到了什么,我们把生成的exe拿来反汇编。。。
  PUSH EDX
  PUSH OFFSET ~1.__GUID_5e52f8ce_aced_5a42_95b4_f674dd84885e
  PUSH EAX
  MOV ECX,DWORD PTR DS:[EAX]
  CALL DWORD PTR DS:[ECX] -> QueryInterface

好吧,我们看到,这货给的IID,原来就是上面那个浆糊代码里面的IID啊,我们好像发现了什么奇怪的东西。
好了,现在我们可以对着IAsyncOperation_impl按F12了,我们来到了一个新的地方:
    template <class TResult>
    struct IAsyncOperation_impl : IInspectable
    {
        private:
            typedef typename Windows::Foundation::Internal::GetAbiType<TResult>::type     TResult_abi;
            typedef typename Windows::Foundation::Internal::GetLogicalType<TResult>::type TResult_logical;
        public:
            typedef TResult                                                                 TResult_complex;

            virtual HRESULT STDMETHODCALLTYPE put_Completed( IAsyncOperationCompletedHandler<TResult_logical> *handler) = 0;
            virtual HRESULT STDMETHODCALLTYPE get_Completed( IAsyncOperationCompletedHandler<TResult_logical> **handler) = 0;
            virtual HRESULT STDMETHODCALLTYPE GetResults(  TResult_abi *results) = 0;

    };

IInspectable是WinRT的标准根接口,是一个正常的COM对象,我们可以不需要太关注,可以吧IInspectable想象为IUnknown就行了。
也就是说,IAsyncOperation_impl是一个COM对象,怪不得他能IID_PPV_ARGS呢,来我们把浆糊删掉:
    template <class TResult>
    struct IAsyncOperation_impl : IInspectable
    {
        public:
            virtual HRESULT STDMETHODCALLTYPE put_Completed( IAsyncOperationCompletedHandler<TResult> *handler) = 0;
            virtual HRESULT STDMETHODCALLTYPE get_Completed( IAsyncOperationCompletedHandler<TResult> **handler) = 0;
            virtual HRESULT STDMETHODCALLTYPE GetResults(  TResult *results) = 0;
    };

哇,浆糊一下少了很多,世界开明了。
可能有人还在叫,那这个TResult是什么东西?来我们看上面的代码:
ComPtr<IStorageFile> pFile;
pNewFile->GetResults(pFile.GetAddressOf());

看出来了没?TResult返回了一个ComPtr,也就是,TResult就是一个IUnknown*。
好了我们知道既然是这样的,我们来做一下手术,我们在上面搞一个这样的接口声明:
MIDL_INTERFACE("5e52f8ce-aced-5a42-95b4-f674dd84885e")
IAsyncOperation_ : public IInspectable
{
public:
    virtual HRESULT STDMETHODCALLTYPE put_Completed( IUnknown *handler) = 0;
    virtual HRESULT STDMETHODCALLTYPE get_Completed( IUnknown **handler) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetResults(  IUnknown **results) = 0;
};

然后写代码:
    /*
    ComPtr<IAsyncOperation<ABI::Windows::Storage::StorageFile*>> pNewFile;
    pUnk->QueryInterface(IID_PPV_ARGS(pNewFile.GetAddressOf()));

    ComPtr<IStorageFile> pFile;
    pNewFile->GetResults(pFile.GetAddressOf());
    */

    ComPtr<IAsyncOperation_> pNewFile;
    pUnk->QueryInterface(IID_PPV_ARGS(pNewFile.GetAddressOf()));

    ComPtr<IUnknown> pTempFile;
    pNewFile->GetResults(pTempFile.GetAddressOf());

    ComPtr<IStorageFile> pFile;
    pTempFile->QueryInterface(IID_PPV_ARGS(pFile.GetAddressOf()));

卧槽,我们发现,我们不使用那个脑残的模板了,我们终于可以忽略那个浆糊的代码了。。。

来,这是全部的代码:

#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <string>
#include <iostream>
#include <memory>

#include <Windows.h>

#include <wrl.h>
#include <wrl\client.h>
#include <wrl\wrappers\corewrappers.h>

#include <roapi.h>

#include <windows.storage.h>
#include <windows.storage.streams.h>

using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;

using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Storage;
using namespace ABI::Windows::Storage::Streams;

MIDL_INTERFACE("5e52f8ce-aced-5a42-95b4-f674dd84885e")
IAsyncOperation_ : public IInspectable
{
public:
	virtual HRESULT STDMETHODCALLTYPE put_Completed( IUnknown *handler) = 0;
	virtual HRESULT STDMETHODCALLTYPE get_Completed( IUnknown **handler) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetResults(  IUnknown **results) = 0;
};

int main()
{
	RoInitialize(RO_INIT_MULTITHREADED);

	ComPtr<IKnownFoldersStatics> pKnownFolders;
	HRESULT hr = RoGetActivationFactory(HStringReference(RuntimeClass_Windows_Storage_KnownFolders).Get(),
		IID_PPV_ARGS(pKnownFolders.GetAddressOf()));

	ComPtr<IStorageFolder> pFolder;
	pKnownFolders->get_VideosLibrary(&pFolder);

	ComPtr<IUnknown> pUnk;
	pFolder->CreateFileAsync(HStringReference(L"123.mp4").Get(),
		CreationCollisionOption::CreationCollisionOption_ReplaceExisting,
		(__FIAsyncOperation_1_Windows__CStorage__CStorageFile**)pUnk.GetAddressOf());

	ComPtr<IAsyncInfo> pAsyncInfo;
	pUnk->QueryInterface(IID_PPV_ARGS(pAsyncInfo.GetAddressOf()));

	//await
	while (1)
	{
		AsyncStatus asyncStatus;
		HRESULT hr = pAsyncInfo->get_Status(&asyncStatus);

		if (SUCCEEDED(hr) && (asyncStatus == AsyncStatus::Completed))
			break;

		SwitchToThread();
	}

	/*
	ComPtr<IAsyncOperation<ABI::Windows::Storage::StorageFile*>> pNewFile;
	pUnk->QueryInterface(IID_PPV_ARGS(pNewFile.GetAddressOf()));

	ComPtr<IStorageFile> pFile;
	pNewFile->GetResults(pFile.GetAddressOf());
	*/

	ComPtr<IAsyncOperation_> pNewFile;
	pUnk->QueryInterface(IID_PPV_ARGS(pNewFile.GetAddressOf()));

	ComPtr<IUnknown> pTempFile;
	pNewFile->GetResults(pTempFile.GetAddressOf());

	ComPtr<IStorageFile> pFile;
	pTempFile->QueryInterface(IID_PPV_ARGS(pFile.GetAddressOf()));

	printf("CreateFile Async OK.");

	RoUninitialize();

	getchar();
	return 0;
}

好了,我说了那么多,我估计你应该可以写一个类似C#的await函数了。。。
所以,你还是去用C#吧。

你可能感兴趣的:(从传统COM简析WinRT的Async(使用WRL))