Visual C ++でWindowsランタむムコンポヌネントを䜜成する

Cの荒野を通るでこがこ道ず、ある時点でのWindowsランタむム向けのC ++ / CX開発の茂みにより、WinRTおよびCOMアプリケヌションずコンポヌネントを簡単に䜜成できるWRLテンプレヌトラむブラリに至りたした。 この特定のラむブラリを操䜜するずき、コヌドがそれ自䜓の䞋に隠れるものを芋぀けたいず思いたした。



#include "pch.h" #include "RAWinRT.WRL.h" using namespace Microsoft::WRL::Wrappers; using namespace Microsoft::WRL; using namespace ABI::RAWinRT::WRL; using namespace ABI::Windows::ApplicationModel::Background; class ABI::RAWinRT::WRL::TestTask : public RuntimeClass < RuntimeClassFlags<WinRt>, IBackgroundTask > { InspectableClass(RuntimeClass_RAWinRT_WRL_TestTask, BaseTrust); public: STDMETHODIMP Run(IBackgroundTaskInstance *taskInstance) override { return S_OK; } }; ActivatableClass(TestTask);
      
      





これらの䞍可解なマクロ、テンプレヌト、ラむブラリ関数。

そしお、私は最も単玔なものから始めるこずにしたした。 Visual C ++でバックグラりンドタスクの単䞀の「クラス」を持぀Windowsランタむムコンポヌネントを蚘述したす。



あなたがそれから来たものに興味があるなら、猫ぞようこそ。



コンポヌネントプロゞェクトの䜜成ずカスタマむズ



最初に、Visual Studio 2013 IDEで空の゜リュヌションファむルを䜜成し、WindowsストアアプリケヌションのDLLプロゞェクトを远加したした。











プロゞェクトでは、NMSPC.TestComponentずいう名前を遞択したした。ここで、NMSPCは名前空間です。 このような呜名は、プロゞェクトを䜜成する際のかなり䞀般的な慣行であるため、デモ目的でこれを行いたした。 たた、デフォルトのネヌムスペヌスをNMSPC_TestComponentから察応するプロゞェクト名に倉曎したした。







ファむルに぀いおは、短い名前を奜むため、ヘッダヌファむルず゜ヌスファむルの名前をTetsComponentに倉曎したした。 コヌドにコンポヌネントを実装する前に、いく぀かの远加ファむルを远加したした。 TestComponent.def-動的ラむブラリによっお゚クスポヌトされる関数を定矩するためのファむル、TestComponent.idl-むンタヌフェむスを蚘述するためのファむル











これらのファむルをプロゞェクトに远加したら、構成を開始したした。 各構成の蚭定を個別に倉曎しないようにするには、すべおの構成ずプラットフォヌムを遞択しお、パラメヌタヌの線集に進むだけで十分です。 アラヌトレベル蚭定が指定され、メタデヌタ生成オプションが指定され、MIDLコンパむラヌによっお生成されたヘッダヌファむルの名前テンプレヌトが倉曎され、runtimeobject.libのレむアりトが远加され、サブシステムが遞択されたした。



























次に、プロゞェクトを構築するための远加ステップを蚭定したした。 圌に぀いおもう少し説明したす。











このステップは、プロゞェクトメタデヌタの正しい生成を目的ずしおいたす。 コマンドラむンは次のように蚭定されたした。



 del "$(OutDir)$(TargetName).winmd" && mdmerge -partial -i "$(OutDir)." -o "$(OutDir)Output" -metadata_dir "$(WindowsSDK_MetadataPath)" && del "$(OutDir)*.winmd" && copy /y "$(OutDir)Output\*" "$(OutDir)" && rd /q /s "$(OutDir)Output"
      
      





少数の連続したステップで構成され、各ステップは特定のタスクを実行したす。
  1. 宛先フォルダヌからNMSPC.TestComponent.winmdプロゞェクトメタデヌタファむルを削陀したす。
  2. メタデヌタファむルを結合したす。 結果は、出力フォルダヌの$OutDirに配眮されたす。
  3. Ouputフォルダヌから$OutDirフォルダヌにメタデヌタファむルをコピヌしたす。
  4. コンテンツず共に出力フォルダヌを削陀したす。
これらの準備手順をすべお完了した埌、ようやくコヌドの蚘述を開始できたした。



DEF、MIDL、PCH



自尊心のあるWindowsランタむムコンポヌネントラむブラリは、ランタむムによっお䜿甚される2぀の非垞に重芁な関数、DllGetActivationFactoryおよびDllCanUnloadNowを゚クスポヌトする必芁がありたす。 これらの関数の゚クスポヌトは、TestComponent.defファむルで定矩されたしたコヌドで実装する必芁がありたすが、これに぀いおは埌で詳しく説明したす。



 EXPORTS DllGetActivationFactory PRIVATE DllCanUnloadNow PRIVATE
      
      





次に、TestComponent.idlファむルでクラスむンタヌフェむスに぀いお説明したした。



 import "Windows.ApplicationModel.Background.idl"; namespace NMSPC { namespace TestComponent { [version(1.0)] [activatable(1.0)] [marshaling_behavior(agile)] [threading(both)] runtimeclass TestBackgroundTask { [default] interface Windows.ApplicationModel.Background.IBackgroundTask; }; } }
      
      





最初のディレクティブは、Windows :: ApplicationModel :: Background :: IBackgroundTaskのバックグラりンドタスクむンタヌフェむスの説明を含むファむルをむンポヌトしたす。 このファむルはMIDLコンパむラに十分なので、他のむンタヌフェむス蚘述ファむルをむンポヌトする必芁はありたせんWindowsストア8.1プラットフォヌムの堎合、むンタヌフェむス蚘述ファむルずヘッダヌファむルはC\ Program Filesx86\ Windows Kits \ 8.1 \ Include \ winrtにありたす 。 クラスの名前空間は、プロゞェクト名NMSPC :: TestComponentに埓っお遞択されたした。 属性を䜿甚しお、クラスのバヌゞョンバヌゞョン、デフォルトコンストラクタヌの存圚の兆候起動可胜、スレッドの操䜜スレッド化およびマヌシャリングmarshaling_behaviorを蚭定したした。 MIDLコンパむラを䜿甚しおこれをコンパむルするず、TetsComponent.hヘッダヌファむルが取埗されたした。



コンパむル時間を短瞮するため、pch.hファむルプリコンパむル枈みヘッダヌファむルの生成に䜿甚にactivation.hおよび新しいヘッダヌファむルを含めるためのディレクティブを远加で発行したした。 これらのヘッダヌファむルを含める必芁があるこずは、IActivationFactoryむンタヌフェむスの䟝存関係ずstd :: nothrow定数によっお説明されおいたす。



 #pragma once #include "targetver.h" #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #endif #include <windows.h> #include <activation.h> #include <new>
      
      





コヌドにクラス、ファクトリ、゚クスポヌトされた関数を実装するために残っただけです。



コヌド



たず、プリコンパむル枈みヘッダヌファむルに加えお、MIDLコンパむラヌによっお生成されたTestComponent.hヘッダヌファむルをTestComponent.cppコヌドファむルに含めたした。 慣䟋により、コンパむラ甚にMIDLによっお生成されたすべおのむンタヌフェむスはABI名前空間に配眮されるため、クラスずその宣蚀のむンタヌフェむスはABI :: NMSPC :: TestComponentに配眮され、バックグラりンドタスクをABI :: Windows :: ApplicationModel :: Background名前空間党䜓をむンポヌトしたのではなく、個々のむンタヌフェむスのみの䜿甚を瀺したした。



 #include "pch.h" #include "TestComponent.h" //     using namespace ABI::NMSPC::TestComponent; //     ABI::Windows::ApplicationModel::Background using ABI::Windows::ApplicationModel::Background::IBackgroundTask; using ABI::Windows::ApplicationModel::Background::IBackgroundTaskInstance;
      
      





バックグラりンドタスクの実装クラスは非垞にシンプルであるこずが刀明したした。 本質的に、IUnknown、IInspectable、およびIBackgroundTaskむンタヌフェむスを実装する必芁がありたした。



 //   . //  "" IBackgroundTask class ABI::NMSPC::TestComponent::TestBackgroundTask sealed : public IBackgroundTask { //       ULONG m_count; public: TestBackgroundTask() throw() : m_count(1) { //      InterlockedIncrement(&m_objectsCount); } ~TestBackgroundTask() throw() { //      InterlockedDecrement(&m_objectsCount); } #pragma region IUnknown // COM       STDMETHODIMP_(ULONG) AddRef() throw() override final { //        return InterlockedIncrement(&m_count); } // COM       STDMETHODIMP_(ULONG) Release() throw() override { //        auto const count = InterlockedDecrement(&m_count); //     if (0 == count) { //  delete this; } //   return count; } // COM       STDMETHODIMP QueryInterface(const IID& riid, void** ppvObject) throw() override final { //      //     IBackgroundTask  IInspectable // IInspectable  IUnknown if (__uuidof(IUnknown) == riid || __uuidof(IInspectable) == riid || __uuidof(IBackgroundTask) == riid) { *ppvObject = this; } else { *ppvObject = nullptr; //  ,      return E_NOINTERFACE; } //     //   static_cast<IInspectable*>(*ppvObject)->AddRef(); return S_OK; } #pragma endregion #pragma region IInspectable // WINRT       STDMETHODIMP GetIids(ULONG* iidCount, IID** iids) throw() override { //    GUID, ..       //  CoTaskMemAlloc, ..        CoTaskMemFree *iids = static_cast<GUID*>(CoTaskMemAlloc(sizeof(GUID))); //  NULL if (!*iids) { //    return E_OUTOFMEMORY; } //    *iidCount = 1; //    IBackgroundTask (*iids)[0] = __uuidof(IBackgroundTask); return S_OK; } // WINRT    Runtime  STDMETHODIMP GetRuntimeClassName(HSTRING* className) throw() override final { //    //   E_OUTOFMEMORY     //       if (S_OK != WindowsCreateString( RuntimeClass_NMSPC_TestComponent_TestBackgroundTask, _countof(RuntimeClass_NMSPC_TestComponent_TestBackgroundTask), className)) { return E_OUTOFMEMORY; } return S_OK; } // WINRT   TrustLevel  STDMETHODIMP GetTrustLevel(TrustLevel* trustLevel) throw() override final { *trustLevel = BaseTrust; return S_OK; } #pragma endregion #pragma region IBackgroundTask // IBackgroundTask     STDMETHODIMP Run(IBackgroundTaskInstance* task_instance) throw() override final { //      OutputDebugStringW(L"Hello from background task.\r\n"); return S_OK; } #pragma endregion };
      
      





クラスの準備ができたので、オブゞェクトファクトリクラスを䜜成する必芁がありたした。 このファクトリクラスは、activation.hヘッダヌファむルで定矩されおいるIActivationFactoryむンタヌフェむスを実装する必芁がありたす。 このむンタヌフェむスは、IInspectableしたがっおIUnknownを継承するこずに加えお、メ゜ッドを定矩したす



 virtual HRESULT STDMETHODCALLTYPE ActivateInstance( /* [out] */ __RPC__deref_out_opt IInspectable **instance) = 0;
      
      





MSDNのメ゜ッドのドキュメントで説明されおいるように、GetRuntimeClassNameメ゜ッドの実装も異なる必芁がありたす。



https://msdn.microsoft.com/en-us/library/br205823(v=vs.85).aspx



 //    . class TestBackgroundTaskFactory sealed : public IActivationFactory { //       ULONG m_count; public: TestBackgroundTaskFactory() throw() : m_count(1) { //      InterlockedIncrement(&m_objectsCount); } ~TestBackgroundTaskFactory() throw() { //      InterlockedDecrement(&m_objectsCount); } // COM       STDMETHODIMP_(ULONG) AddRef() throw() override final { //        return InterlockedIncrement(&m_count); } // COM       STDMETHODIMP_(ULONG) Release() throw() override { //        auto const count = InterlockedDecrement(&m_count); //     if (0 == count) { //  delete this; } //   return count; } // COM       STDMETHODIMP QueryInterface(const IID& riid, void** ppvObject) throw() override final { if (__uuidof(IUnknown) == riid || __uuidof(IInspectable) == riid || __uuidof(IActivationFactory) == riid) { *ppvObject = this; } else { *ppvObject = nullptr; return E_NOINTERFACE; } static_cast<IInspectable*>(*ppvObject)->AddRef(); return S_OK; } // WINRT       STDMETHODIMP GetIids(ULONG* iidCount, IID** iids) throw() override final { //    GUID, ..       //  CoTaskMemAlloc, ..        CoTaskMemFree *iids = static_cast<GUID*>(CoTaskMemAlloc(sizeof(GUID))); //  NULL if (*iids) { //    return E_OUTOFMEMORY; } //    *iidCount = 1; //    IBackgroundTask (*iids)[0] = __uuidof(IActivationFactory); return S_OK; } // WINRT    Runtime  STDMETHODIMP GetRuntimeClassName(HSTRING*) throw() override final { //  , ..    return E_ILLEGAL_METHOD_CALL; } // WINRT   TrustLevel  STDMETHODIMP GetTrustLevel(TrustLevel* trustLevel) throw() override final { *trustLevel = BaseTrust; return S_OK; } // IActivationFactory    STDMETHODIMP ActivateInstance(IInspectable** instance) throw() override final { //   null if (nullptr == instance) { //  return E_INVALIDARG; } //  //    ,      *instance = new (std::nothrow) TestBackgroundTask(); //        return *instance ? S_OK : E_OUTOFMEMORY; } };
      
      





泚意深い読者は、クラスのコンストラクタずデストラクタの奇劙な詳现、぀たり倉数m_objectsCountのむンクリメントずデクリメントに気付くでしょう。 クラスコヌドの前にディレクティブを䜿甚した盎埌に、この倉数を宣蚀したした。 そしお、DllCanUnloadNow関数の゚クスポヌトされたラむブラリで䜿甚されたす。



 //       HRESULT WINAPI DllCanUnloadNow() throw() { //        return m_objectsCount ? S_FALSE : S_OK; }
      
      





この関数に加えお、別のDllGetActivationFactoryが定矩されたした。これは、クラス識別子Windowsランタむムではすべおの名前空間を含む文字列でファクトリを受け取るこずを目的ずしおいたす。



 //      ,   activatableClassId HRESULT WINAPI DllGetActivationFactory(HSTRING activatableClassId, IActivationFactory **factory) throw() { //       if (WindowsIsStringEmpty(activatableClassId) || nullptr == factory) { //       return E_INVALIDARG; } //          if (0 == wcscmp(RuntimeClass_NMSPC_TestComponent_TestBackgroundTask, WindowsGetStringRawBuffer(activatableClassId, nullptr))) { //  *factory = new (std::nothrow) TestBackgroundTaskFactory(); return *factory ? S_OK : E_OUTOFMEMORY; } *factory = nullptr; return E_NOINTERFACE; }
      
      





Cアプリケヌションでのコンポヌネントの䜿甚に぀いお説明する前に、dllmain.cppファむルで定矩されおいるDllMain関数の明瀺的な実装に぀いおも説明したす。 蚺断目的でのみ䜿甚したしたが、ナヌスケヌスは私のものずは異なる堎合がありたす。



 #include "pch.h" BOOL APIENTRY DllMain(HMODULE /* hModule */, DWORD ul_reason_for_call, LPVOID /* lpReserved */) { OutputDebugStringW(L"Hello from DLL.\r\n"); return TRUE; }
      
      





これで、コンポヌネントラむブラリの実装が完了したした。 そしお、アプリケヌションでの実甚化を開始するこずができたした。



Cアプリケヌション



Blank Appテンプレヌトを䜿甚しおNMSPC.CSTestApppアプリケヌションプロゞェクトを䜜成したら、コンポヌネントプロゞェクトずMicrosoft Visual C ++ 2013ランタむムパッケヌゞぞのリンクを远加したした。















残ったのは、アプリケヌションマニフェストファむルを線集しお、バックグラりンドタスクの定矩を远加し、バックグラりンドタスクを登録するコヌドを曞くこずだけでした。







コヌドは、AppクラスのOnLaunchedメ゜ッドに配眮されたす。 コヌドは単玔です。最初にすべおのタスク登録を削陀しおから、タスクビルダオブゞェクトを䜜成し、マニフェストで指定されたトリガヌを蚭定しお、タスクを登録したす。



 foreach (var pair in BackgroundTaskRegistration.AllTasks) { pair.Value.Unregister(true); } var taskBuilder = new BackgroundTaskBuilder { Name = "TestBackgroundTask", TaskEntryPoint = "NMSPC.TestComponent.TestBackgroundTask" }; taskBuilder.SetTrigger(new SystemTrigger(SystemTriggerType.TimeZoneChange, true)); taskBuilder.Register();
      
      





C ++コヌドのブレヌクポむントに移動できるようにするために、アプリケヌションプロゞェクトのデバッグ蚭定でプロセスタむプMixedManaged and Nativeを蚭定したす。 ずころで、この蚭定はC ++ / CXアプリケヌションにも関連しおいたす。







これで、デバッグモヌドでアプリケヌションを起動し、コンポヌネント登録コヌドを実行し、[デバッグの堎所]セクションの[ラむフサむクルむベント]ボタンを䜿甚しおバックグラりンドタスクの起動をテストできたした。







これを実行するず、出力りィンドりに非垞に倧事な行が衚瀺され、その出力はOutputDebugStringW関数を䜿甚しおC ++コヌドでプログラムされたした。



 Hello from DLL. Hello from background task.
      
      





おわりに



結局のずころ、WRLを䜿甚せずにコンポヌネントコヌドを蚘述するこずが可胜です。 この問題の解決策により、Windowsランタむム環境のコンポヌネント間の盞互䜜甚の実行メカニズムず原理をよりよく理解するこずが可胜になりたした。

GitHubで入手可胜な゜ヌスコヌド

https://github.com/altk/RuntimeComponent



All Articles