How to create a WinRT WRL C++ Component from scratch that can be consumed by .NET components

If you have to develop Hybrid WinRT Components that expose both COM and WinRT components you have to either write bare metal C++ or use the new Windows Runtime Template Library, which supports you in dealing with COM. More about WRL can be found at http://msdn.microsoft.com/en-us/library/hh438466(v=vs.110).aspx

Microsoft provided a few sample WRL C++ Projects. However they did not include a WRL C++ Project Template in the Visual Studio 2011 Consumer Preview. I reverse-engineered the Sample Projects to find out what is necessary in order to develop a WRL Component from scratch as I didn’t want to use the available Sample Projects as my Project foundation. I also modified the Project in a way that the resulting DLL contains not only the COM component itself but also the proxy/stub implementation.

These are the steps I came up with:

  1. Create a new “Visual Studio C++ – Windows Metro Style – WinRT Component DLL” Project. I called it “WRLTemplate”.
    (Please make sure that the solution folder does not contain any white spaces.)
  2. Delete the automatically created WinRTComponent.cpp/h files.
  3. Add Module-Definition File “WRLTemplate.def”

    Set the content to the following, describing all exported methods of the DLL

    EXPORTS
    DllCanUnloadNow         PRIVATE
    DllGetActivationFactory PRIVATE
    DllGetClassObject       PRIVATE
    
  4. Add the MIDL File “WRLTemplate.idl”. The content will be added later, when we define the modules’ functionality.
  5. Open the Project Settings from the Solution Exporer and adjust the following settings
  6. C/C++ General Tab
     
    • Additional Include Directories: $(IntermediateOutputPath);$(ProjectDir);$(OutDir);%(AdditionalIncludeDirectories)
    • Consume Windows Runtime Extension: No
  7. C/C++ Preprocessor Tab

    • Preprocessor Definitions:
      ENTRY_PREFIX=Prx;REGISTER_PROXY_DLL;PROXY_CLSID_IS={ 0x24352b56, 0xa2ea, 0x4327, { 0xba, 0xcb, 0xe9, 0xea, 0x33, 0x8d, 0xb3, 0x9d } };_WINRT_DLL;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)

      Those are mainly necessary for the Proxy/Stub, which will be part of the same component. ENTRY_PREFIX defines the Prefix of the generated exported DLL entry methods of the Proxy/Stub and is necessary to avoid naming conflicts

  8. Linker – Input Tab.
    Add rpcrt4.lib as an Additional Dependency
  9. MIDL General Tab
    • Additional Include Directories: $(LibraryWPath);$(ProjectDir);%(AdditionalIncludeDirectories)
    • Additional Metadata Directories: $(LibraryWPath);%(AdditionalMedataDirectories)
  10. MIDL Output Tab
    • Metadata File:$(OutDir)%(Filename).winmd
    • Header File:%(Filename).h
    • IID File:%(Filename)_i.c
    • Proxy File:%(Filename)_p.c
  11. MIDL Command Line
    • Additional Options: /ns_prefix
      This will add the “ABI” Prefix to the generated namespaces in the proxy/stub files.
  12. Custom Build Step General Tab

    • Command Line: mdmerge -partial -i “$(OutDir).” -o “$(OutDir)Output” -metadata_dir “$(WindowsSDK_MetadataPath)” && copy /y $(OutDir)Output\* $(OutDir)
    • Outputs: $(OutDir)%(TargetName).winmd
    • Execute After: Midl
  13. Add common includes to the generated pch.h file
    #pragma once
    
    #include <SDKDDKVer.h>
    
    #define WIN32_LEAN_AND_MEAN
    
    //Windows Header Files:
    #include <windows.h>
    #include <assert.h>
    #include <tchar.h>
    #include <Strsafe.h>
    
    //WRL
    #include <wrl\client.h>
    #include <wrl\implements.h>
    #include <wrl\ftm.h>
    #include <wrl\event.h> 
    #include <wrl\wrappers\corewrappers.h>
    #include <wrl\module.h>
    
  14. Set the WRLTemplate.idl content, which describes what functionality your Component will expose
    import "Windows.Foundation.idl";
    
    #include <sdkddkver.h>
    
    namespace WRLTemplate
    {
    	runtimeclass MyComponent;
    
    	[version(NTDDI_WIN8), uuid(9789F754-FF6E-4AB5-9868-C1430D294A1B)]
    	interface IMyCallbackProvider : IInspectable
    	{
    		HRESULT Call([in] HSTRING callbackValue);
    	}
    
    
    	[version(NTDDI_WIN8), uuid(1589F754-FF6E-4AB5-9868-C1430D294A1B), exclusiveto(MyComponent)]
    	interface IMyComponent : IInspectable
    	{
    		HRESULT Foo([in] HSTRING someString, [in]IMyCallbackProvider* myCallback);
    	}
    
    
    	[version(NTDDI_WIN8), activatable(NTDDI_WIN8)]
    	runtimeclass MyComponent
    	{
    		[default] interface IMyComponent;
    	}
    }
    
  15. Add MyComponent.h
    #include "pch.h"
    #include "WRLTemplate.h" //Generated by the MIDL compiler
    
    using namespace Microsoft::WRL;
    using namespace ABI::WRLTemplate;
    
    namespace WRLTemplate
    {
    	class DECLSPEC_UUID("A23CF192-F869-4DFE-9477-4045A73CA2AB") MyComponent : 
    		public RuntimeClass<
    			// WRL				
    			RuntimeClassFlags<RuntimeClassType::WinRtClassicComMix>,
    			FtmBase	,
    			// Custom
    			IMyComponent>
    	{
    		InspectableClass(RuntimeClass_WRLTemplate_MyComponent, TrustLevel::BaseTrust);
    
    	public:	
    		// IMyComponent
    		IFACEMETHOD (Foo)(HSTRING someString, IMyCallbackProvider* myCallback);
    	};
    }
    
  16. Add MyComponent.cpp
    #include "pch.h"
    #include "MyComponent.h"
    
    namespace WRLTemplate
    {
    	IFACEMETHODIMP MyComponent::Foo (HSTRING someString, IMyCallbackProvider* myCallback)
    	{
    		myCallback->Call(someString);
    
    		return S_OK;
    	}
    }
    
  17. Add WRLTemplate.cpp
    #include "pch.h"
    #include "MyComponent.h"
    
    // COM proxy/stubs
    extern "C" HRESULT WINAPI PrxDllGetClassObject(REFCLSID, REFIID, _Deref_out_ LPVOID*);
    extern "C" BOOL WINAPI PrxDllMain(_In_opt_ HINSTANCE, DWORD, _In_opt_ LPVOID);
    extern "C" HRESULT WINAPI PrxDllCanUnloadNow();
    
    
    BOOL WINAPI DllMain( __in_opt HINSTANCE hInstance, __in DWORD dwReason, __in_opt LPVOID lpReserved )
    {
        if( DLL_PROCESS_ATTACH == dwReason )
        {
    
            //
            //  Don't need per-thread callbacks
            //
            DisableThreadLibraryCalls( hInstance );
    
            Microsoft::WRL::Module<Microsoft::WRL::InProc>::GetModule().Create();
        }
        else if( DLL_PROCESS_DETACH == dwReason )
        {
            Microsoft::WRL::Module<Microsoft::WRL::InProc>::GetModule().Terminate();
        }
    
        PrxDllMain( hInstance, dwReason, lpReserved );
    
        return TRUE;
    }
    
    STDAPI DllGetActivationFactory(_In_ HSTRING activatibleClassId, _COM_Outptr_ IActivationFactory** factory)
    {
        auto &module = Microsoft::WRL::Module<Microsoft::WRL::InProc>::GetModule();
        return module.GetActivationFactory(activatibleClassId, factory);
    }
    
    STDAPI DllCanUnloadNow()
    {
        auto &module = Microsoft::WRL::Module<Microsoft::WRL::InProc>::GetModule();
        return module.Terminate() ? S_OK : S_FALSE;
    }
    
    STDAPI DllGetClassObject( __in REFCLSID rclsid, __in REFIID riid, __deref_out LPVOID FAR* ppv )
    {
        auto &module = Microsoft::WRL::Module<Microsoft::WRL::InProc>::GetModule();
        HRESULT hr = module.GetClassObject( rclsid, riid, ppv );
        if (FAILED(hr))
        {
            hr = PrxDllGetClassObject( rclsid, riid, ppv );
        }
        return hr;
    }
    
    namespace WRLTemplate {
    	ActivatableClass(MyComponent)
    }
    
  18. Try to compile. It will fail.
  19. Add the Proxy Stub files to the solution: Select “Show all files” in the Solution explorer and include dlldata.c + WRLTemplate_i.c, WRLTemplate_p.c to the solution
  20. Deactivate the usage of Precompiled Headers on these 3 files by going to their properties (right click and select Properties) and changing the Precompiled Header to Not Using Precompiled Headers
  21. Recompile – should work fine. Now use the Component from a C# project
  22. Create a new C# project

  23. Add Reference to the winmd file. Make sure you do NOT select WRLTemplate.winmd from the Output folder. Although they are binary equal you will get an exception at runtime, telling you that the Type is not registered.
  24. Instantiate the COM component and use it for example in your BlanPages’ OnNavigatedTo-Method
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                var comp = new MyComponent();
                var callback = new Callback();
    
                const string testString = "Test";
                comp.Foo(testString, callback);
    
                Debug.Assert(callback.ReceivedValue == testString);
            }
    
            class Callback : IMyCallbackProvider
            {
                public string ReceivedValue { get; set; }
    
                public void Call(string callbackValue)
                {
                    ReceivedValue = callbackValue;
                }
            }
    
  25.  If you followed all steps correctly, the solution should compile and work as expected :-)

If you have specific questions regarding any of these settings please post a comment and I will get back to you.
I attached the example solution (WRLTemplate). Please note that you will have to compile the project “WRLTemplate” and again reference its WRLTemplate.winmd afterwards in the ConsumerApp project.

10 thoughts on “How to create a WinRT WRL C++ Component from scratch that can be consumed by .NET components

  1. Pingback: Interesting Windows 8 Links – 2012-04-16 | Dan Rigby

  2. Great. For your info, I created a project template for Visual Studio, starting from your initial settings.
    I changed few of them and created the configurations for x86 and x64.

    It’s published here: http://winrt.codeplex.com
    Your name/this blog post is referenced in the readme.txt for credits.

  3. Thanks for this article,
    but now, i try it on Window 8 Release Preview (Build 8400), i implement folow this article, because, at step 19, i not found 3 files: dlldata.c + WRLTemplate_i.c, WRLTemplate_p.c.
    I can’t compile WRLTemplate.idl file, too. I have some error:
    error D8003: missing source filename
    error MIDL1003: error returned by the C preprocessor (2)

    Help me, please!

  4. Thanks for writing up such great step by step tutorial. It helps a lot.

    I have a question. Is there any reason for us to set “Consume Windows Runtime Extension: No”

    I am asking this, as I have a C# consumed C++ component, which needs to access WinRT APIs. When I turn on the feature, I get compilation error as follow. “Fixing” compilation error will further lead me to runtime error.

    The details is as follow

    thanks.

  5. Thanks for the walkthrough. You called out adding rpcrt4.lib as an additional dependency, but not runtimeobject.lib. For me runtimeobject.lib wasn’t a dependency of my dll by default (or I must have cleared it accidentally), so I had to manually add it. If anyone has linker errors (error LNK2019: unresolved external symbol _WindowsCreateString@4 …) for WindowsCreateString, WindowsDeleteString, or WindowsGetStringRawBuffer, this is the cause.

  6. Thanks for the post. In DllCanUnloadNow, are you sure you should call module.Terminate(), rather than module.GetObjectCount()? I see that Terminate() actually returns GetObjectCount() == 0 ? S_OK : S_FALSE, but it actually cleans up some stuff, where DllCanUnloadNow normally just checks if the object count == 0 and doesn’t do clean up. Also Terminate is called from DllMain if DLL_PROCESS_DETACH == dwReason, so should we be calling it from DllCanUnloadNow? The VS templates don’t use Terminate either, they all use module.GetObjectCount() == 0 ? S_OK : S_FALSE. Not sure myself, so I am hoping someone can clear it up.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>