📜 ⬆️ ⬇️

Creating a Windows Runtime component in Visual C ++

The thorny road through the wilds of C # and the thickets of C ++ / CX development for the Windows Runtime at some point led me to the WRL template library, which makes writing WinRT and COM applications and components easier. When working with this library, I wanted to find out what the code hides under itself:

#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); 

and these mysterious macros, templates, library functions.
And I decided to start with the simplest. Write the Windows Runtime component, which has a single “class” of background task, in Visual C ++.

If you are wondering what came out of this, then welcome under cat.
')

Creating and setting up a component project


First, I created an empty solution file in Visual Studio 2013 IDE and added a DLL project for the Windows Store application to it.





For the project, I chose the name NMSPC.TestComponent, where NMSPC is some namespace. I did this for demonstration purposes, since such naming is a fairly common practice when creating projects. Also, changed the default namespace from NMSPC_TestComponent to the corresponding project name.



For files, I prefer shorter names, so I renamed the header file and the source file to TetsComponent. Before proceeding with the implementation of the component in the code, added a few additional files. TestComponent.def is a definition file for functions exported by the dynamic library, TestComponent.idl is an interface description file.





Having added these files to the project, I started to set it up. In order not to change the settings for each configuration separately, it was enough for me to select all configurations and platforms, and then proceed to editing the parameters. The warning level settings have been set, the parameter for generating metadata has been specified, the header file generated by MIDL has been changed by the compiler header, the layout has been added with runtimeobject.lib, and a subsystem has been selected.













Next, set up an additional step of building the project. About him I will tell a little more.





This step is designed to correctly generate project metadata. The command line was set as follows:

 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" 

It consists of a small number of consecutive steps, each of which performs a certain task.
  1. Remove from the destination folder the project metadata file NMSPC.TestComponent.winmd.
  2. We combine our metadata files. The result will be placed in the Output folder in $ (OutDir).
  3. Copy the metadata files from the Ouput folder to the $ (OutDir) folder.
  4. Delete the Output folder along with its contents.
Having done all these preliminary steps, I was finally able to start writing the code.

DEF, MIDL, PCH


Any self-respecting Windows Runtime component library should export two very important functions, DllGetActivationFactory and DllCanUnloadNow, which are used by the runtime. The export of these functions was defined in the TestComponent.def file (they will also need to be implemented in the code, but more on that later).

 EXPORTS DllGetActivationFactory PRIVATE DllCanUnloadNow PRIVATE 

Next, I described the class interface in the TestComponent.idl file.

 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; }; } } 

The first directive imports the file with the description of the background task interface Windows :: ApplicationModel :: Background :: IBackgroundTask. Since this file is sufficient for the MIDL compiler, there is no need to import other interface description files (for the Windows Store 8.1 platform, interface description files and header files are located in C: \ Program Files (x86) \ Windows Kits \ 8.1 \ Include \ winrt) . The namespace for the class was selected according to the project name NMSPC :: TestComponent. The attributes were used to set the class version (version), a sign of the presence of a default constructor (activatable), work with threads (threading) and marshaling (marshaling_behavior). Having compiled the data using the MIDL compiler, I got the header file TetsComponent.h.

To reduce the compilation time, he also issued directives to include the activation.h and new header files in the pch.h file (which is used to generate precompiled header files). The need to include these header files is explained by the dependency on the IActivationFactory interface and the std :: nothrow constant.

 #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> 

It only remained to implement the class, the factory and the exported functions in the code.

Code


First of all, I included in the TestComponent.cpp code file, in addition to the precompiled header file, the TestComponent.h header file generated by MIDL by the compiler. By convention, all interfaces generated by MIDL compilers are located in the ABI namespace, so the interfaces for the class and its declaration will be located in ABI :: NMSPC :: TestComponent, and the interfaces for implementing the background task in ABI :: Windows :: ApplicationModel :: Background ( I did not import the entire namespace, instead indicated the use of only separate interfaces).

 #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; 

The implementation class for the background task is pretty simple. In essence, it was necessary to implement the IUnknown, IInspectable and IBackgroundTask interfaces.

 //   . //  "" 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 }; 

Now that the class was ready, it was necessary to write a class of the object factory. This factory class must implement the IActivationFactory interface, which is defined in the activation.h header file. This interface, in addition to IInspectable inheritance (and therefore IUnknown), defines the method

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

The implementation of the GetRuntimeClassName method should also differ, as described in the documentation for the method on MSDN:

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; } }; 

An attentive reader might have noticed a strange detail in the constructors and destructors of classes, namely, the increment and decrement of the m_objectsCount variable. I declared this variable immediately after using directives before the code of classes. And it is used in the DllCanUnloadNow function exported library:

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

In addition to this function, another DllGetActivationFactory was defined, intended to get the factory by class identifier (in the Windows Runtime, this is a string with all namespaces included).

 //      ,   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; } 

Before we talk about using a component in a C # application, I will also mention the explicit implementation of the DllMain function defined in the dllmain.cpp file. I used it only for diagnostic purposes, but the use cases may be different from mine.

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

This completes the implementation of the component library. And I was able to begin its practical use in the application.

C # application


Having created the NMSPC.CSTestAppp application project using the Blank App template, I added references to the component project and the Microsoft Visual C ++ 2013 Runtime Package to it.







It only remained to edit the application manifest file, add the definition of the background task to it, and write the code that performs the registration of the background task.



The code is placed in the OnLaunched method of the App class. The code is simple: first removes all task registrations, then creates a buiilder object for the task, sets the trigger specified in the manifest, and registers the task.

 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(); 

In order to be able to go to breakpoints in C ++ code, set the Mixed process type (Managed and Native) in the debug settings of the application project. By the way, this setting is also relevant for C ++ / CX applications.



Now it was possible to start the application in debug mode, execute the component registration code and test the launch of the background task using the Lifecycle Events button in the Debug Locations section.



Having done this, I saw the most cherished lines in the Output window, the output of which was programmed in C ++ code using the OutputDebugStringW function.

 Hello from DLL. Hello from background task. 

Conclusion


As it turned out, it is possible to write component code without using WRL. Solving this problem allowed us to better understand the execution mechanisms and principles of interaction between the components of the Windows Runtime environment.
Source code available on github
https://github.com/altk/RuntimeComponent

Source: https://habr.com/ru/post/252759/


All Articles