1

This is somewhat of a follow-on to How to use midlrt.exe to compile .idl to .winmd?

I have this in my CMakeLists.txt . My questions are less about the CMake logic and more about the output of the midl and cppwinrt commands, and subsequent errors in compiling and linking. I suspect maybe I'm missing some command-line options.

# Pathnames for WinRT References
set (WINSDKREFDIR "$ENV{WindowsSdkDir}References\\$ENV{WindowsSDKVersion}")

# Remove trailing \ from $ENV{WindowsSDKVersion}
string (REGEX MATCH "[^\\]*" WINSDKVER $ENV{WindowsSDKVersion})

# COMMAND lines wrapped in this post for readability, not wrapped in the actual CMakeLists.txt
add_custom_target (MYLIB_PREBUILD ALL
  COMMAND midl /winrt /ns_prefix /x64 /nomidl
    /metadata_dir
      "${WINSDKREFDIR}windows.foundation.foundationcontract\\3.0.0.0"
    /reference 
      "${WINSDKREFDIR}windows.foundation.foundationcontract\\3.0.0.0\\Windows.Foundation.FoundationContract.winmd"
    /reference
      "${WINSDKREFDIR}Windows.Foundation.UniversalApiContract\\8.0.0.0\\Windows.Foundation.UniversalApiContract.winmd"
    /out "${MYDIR}\\GeneratedFiles" "${MYDIR}\\MyClass.idl"
  COMMAND cppwinrt
    -in "${MYDIR}\\GeneratedFiles\\MyClass.winmd"
    -ref ${WINSDKVER} -component -pch "pch.h" -out "${MYDIR}\\GeneratedFiles"
)

add_dependencies (MYLIB MYLIB_PREBUILD)

In the cppwinrt command, I've tried different forms of -ref [spec] and -pch options, but seem to get the same results regardless. These are the problems I've run into:

  • MIDLRT generates a header file "MyClass.h" with several problems:

    • It #includes <windows.h>, which ultimately #defines preprocessor macros for GetClassName and GetCurrentTime that cause compiler errors in WinRT functions with those names.
    • I spent some hours tracking that down and learning to compile with #define COM_NO_WINDOWS_H to prevent that.
    • It #includes non-existent *.h files from WinRT References Contracts directories instead of the Include directories:
      • #include "C:\Program Files (x86)\Windows Kits\10\References\10.0.18362.0\Windows.Foundation.FoundationContract\3.0.0.0\Windows.Foundation.FoundationContract.h"
      • #include "C:\Program Files (x86)\Windows Kits\10\References\10.0.18362.0\Windows.Foundation.UniversalApiContract\8.0.0.0\Windows.Foundation.UniversalApiContract.h"
    • So I made a copy of this file and replaced those with #include <winrt/Windows.Foundation.h>
  • CPPWINRT generates "module.g.cpp" that #includes "MyNamespace.MyClass.h", but does not also generate that .h file. It does generate "MyNamespace/MyClass.h" (note "/" instead of "."), so I created the former .h and simply #include the latter .h from it.

  • CPPWINRT doesn't generate all of the base headers that I see in Microsoft examples. It generates only headers directly related to MyClass -- e.g., defining the template base class winrt::MyNamespace::implementation::MyClassT<>, the wrapper winrt::MyNamespace::MyClass, etc.

  • winrt::MyNamespace::factory_implementation::MyClass is not defined. MyClassT<> is defined there, but not MyClass. I find a paradigm for that from a Microsoft example and paste it in:

    // Missing from the generated stuff -- derived from a Microsoft example:
    namespace winrt::MyNamespace::factory_implementation
    {
        struct MyClass : MyClassT<MyClass, implementation::MyClass>
        {
        };
    }
  • I received compiler warnings about inconsistent definitions of CHECK_NS_PREFIX_STATE: in some places it was "always" and in other places it was "never". So now I #define MIDL_NS_PREFIX and #define CHECK_NS_PREFIX_STATE="always"

Now the build gets through the compiler, but I have unresolved external symbols in the linker. I think these things are supposed to be defined inline in a "winrt/base.h", but cppwinrt did not export such a file (as I see in Microsoft examples), and the equivalent file in the system directory contains only prototypes, not bodies:

WINRT_GetRestrictedErrorInfo
WINRT_RoInitialize
WINRT_RoOriginateLanguageException
WINRT_SetRestrictedErrorInfo
WINRT_WindowsCreateString
WINRT_WindowsCreateStringReference
WINRT_WindowsDeleteString
WINRT_WindowsPreallocateStringBuffer
WINRT_WindowsDeleteStringBuffer
WINRT_WindowsPromoteStringBuffer
WINRT_WindowsGetStringRawBuffer
WINRT_RoGetActivationFactory
WINRT_WindowsDuplicateString

Am I missing some simple thing that would resolve all of these problems with missing, incomplete, and incorrect generated files?

Theodore Hall
  • 91
  • 1
  • 6

1 Answers1

0

The unresolved external symbol errors indicate that you are missing an import library. In this case you will want to link against the WindowsApp.lib umbrella library, that exports the required symbols.

Note that the symbol names you are observing are an artifact of C++/WinRT's requirement to build with as well as without the Windows SDK headers. It addresses this by declaring the imports (with a WINRT_ prefix to prevent clashes with the SDK header declarations), and then maps the renamed symbols using the /ALTERNATENAME linker switch.

I'm not sure this is going to solve all of your issues, but you certainly would want to add ${MYDIR}\\GeneratedFiles to your additional include directories. That should take care of the inability to include the generated headers from the winrt subdirectory (base.h as well as the projected Windows Runtime type headers).

cppwinrt also writes stub implementations for your own types into ${MYDIR}\\GeneratedFiles\\sources, when it processes the .winmd file previously complied from your .idl(s). It's unfortunate, but there's a manual step involved here: You need to copy the generated .h and .cpp files to your source tree, and implement the skeleton implementations. This is required whenever you modify one of your interface definitions.

As a note, the module.g.cpp files generated for my projects do not include any of my custom type headers. Maybe you are using an older version of C++/WinRT (I'm using v2.0.200203.5). I believe this was changed with the introduction of type-erased factories in C++/WinRT version 2.0. Unless you are doing this already, you should use cppwinrt from the Microsoft.Windows.CppWinRT NuGet package as opposed to the binary that (used to) ship with the Windows SDK.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • Thanks for your quick reply, but there are problems, which I evidently must address in separate comments due to the length restriction: – Theodore Hall Aug 24 '20 at 19:48
  • "link against the WindowsApp.lib umbrella library" -- That did resolve the external symbols, thanks. I was using that for UWP, but not desktop. Still need to test execution. + I still need to be careful about the order of #includes: As I mentioned previously, cppwinrt generates a header that contains: ``` #ifndef COM_NO_WINDOWS_H #include #include #endif /*COM_NO_WINDOWS_H*/ ``` I need to include that first and then #undef GetClassName and #undef GetCurrentTime; else those macros interfere with the definitions or calls to WinRT functions with those names. – Theodore Hall Aug 24 '20 at 20:34
  • If I compile with COM_NO_WINDOWS_H it creates more problems than it solves because other necessary things get omitted. – Theodore Hall Aug 24 '20 at 20:36
  • "you should use cppwinrt from the Microsoft.Windows.CppWinRT NuGet package as opposed to the binary that (used to) ship with the Windows SDK." -- According to cppwinrt /?, I'm using v1.0.190111.3, which I guess came with the SDK. I am unable to get the NuGet version from a CMake project -- see below: – Theodore Hall Aug 24 '20 at 20:40
  • PM> Install-Package Microsoft.Windows.CppWinRT -Version 2.0.200729.8 Install-Package : Project 'Default' is not found. At line:1 char:1 + Install-Package Microsoft.Windows.CppWinRT -Version 2.0.200729.8 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (Default:String) [Install-Package], ItemNotFoundException + FullyQualifiedErrorId : NuGetProjectNotFound,NuGet.PackageManagement.PowerShellCmdlets.InstallPackageCommand – Theodore Hall Aug 24 '20 at 20:41
  • I got the NuGet CppWinRT package in a "typical" Visual Studio solution (with solution and project files), but when I open the Command Line and try cppwinrt /? it still shows v1.0.190111.3. So, even though the VS internal build logic may use v2.0.200729.8, it's not available to the Command Line or to CMake without some finagling. I see that cppwinrt.exe v2.0.200729.8 is "installed" within the VS solution. What happens if I copy and execute that from some place accessible to a CMake project? I guess I'll do the experiment, but it feels dirty. – Theodore Hall Aug 24 '20 at 20:56
  • @the Getting C++/WinRT v2.0 set up is probably something you'd want to address first. The error suggests, that [Install-Package](https://learn.microsoft.com/en-us/nuget/reference/ps-reference/ps-ref-install-package) is unable to find the project for which to install. If you are using CMake 3.15 or later, you can see if setting [VS_PACKAGE_REFERENCES](https://cmake.org/cmake/help/latest/prop_tgt/VS_PACKAGE_REFERENCES.html) works for you (see [this answer](https://stackoverflow.com/a/59307480/1889329)). – IInspectable Aug 25 '20 at 08:04
  • That said, C++/WinRT v2.0 should compile irrespective of whether or when you `#include `, though `GetCurrentTime` is a [known collision](https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/faq#how-do-i-resolve-ambiguities-with-getcurrenttime-andor-try). The documentation entry [Isolation from Windows SDK header files](https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/news#isolation-from-windows-sdk-header-files) has more information. – IInspectable Aug 25 '20 at 08:13
  • I added VS_PACKAGE_REFERENCES, but it had no apparent effect other than to set the property. I don't see v2 cppwinrt.exe installed anyplace. CMake still invokes the v1 that ships with the SDK. According to this thread, "Hmm, apparently C++ projects are not supported": https://stackoverflow.com/questions/61909735/cmakes-vs-package-references-not-adding-a-reference-to-vs2017-project (I'm using vs2019, but it doesn't seem to work there either.) So far, we're not using Visual Studio SLN or VCXPROJ files at all, but I might be forced to give up the fight and resort to that for the GUI stuff. – Theodore Hall Aug 25 '20 at 18:27
  • That's quite the bummer. Another option would be to run [nuget restore](https://learn.microsoft.com/en-us/nuget/consume-packages/install-use-packages-nuget-cli) with a [package.config](https://learn.microsoft.com/en-us/nuget/reference/packages-config) file present (make sure to set `targetFramework="native"`). The package gets installed into the directory passed via the `-OutputDirectory` flag. CMake needs to run cppwinrt.exe from there. – IInspectable Aug 25 '20 at 18:40
  • "GetCurrentTime is a known collision" -- Add to that page: winrt/Windows.UI.Xaml.Automation.Peers.h declares a method named GetClassName, while windows.h (via winuser.h) defines a macro named GetClassName. – Theodore Hall Aug 25 '20 at 19:40