2

I have an existing program win32 (x86) console app that needs to call managed code (C# from a .Net .dll). The .dll is not exposed to COM, but can be called from a C#/WinRT Component and referenced by C++/WinRT Console Template app, BUT I can't seem to call it from a win32 x86 console app even after installing the C++/WinRT NuGet package. I've built and ran this example but the consuming apps are always using the C++/WinRT template. When I try to reproduce the example with a base win32 app, I get the error REGDB_E_CLASSNOTREG Class not registered.

I found another example showing how to consume a C++/WinRT component from a win32 app, without registering classes. I thought this was my answer. However the process involves going into the application manifest and specify activatable WinRT classes by referencing the outputted .dll file whenever the C++/WinRT component builds.

Here's the problem: C#/WinRT components do not output a .dll file, only the .winmd.(see Edit) With the .winmd file, I can still reference the classes and build my project, But I end up with the same REGDB_E_CLASSNOTREG Class not registered error. I assume both the C++/WinRT and C#/WinRT components would compile into something that is in an Intermediate Language (see comments), but why does C++/WinRT output a .dll and a .winmd, while C#/WinRT only outputs .winmd files? I tried to use WinRT.Runtime.dll in place of the outputted .dll but that didnt work either.

I'm at a loss. I posted another question about the difference between the C++/WinRT template vs win32 with C++/WinRT NuGet package.

Main Problem: Can I use a C# .dll (not COM exposed) in a base win32 console app somehow?

Edit

I realized that I was using a C# Windows Runtime Component template that was specific to UWP. This might be why there was no outputted .dll when built.

enter image description here

Following Simon's reply, I was able to create a C# WinRT component that can be called from a Win32 console app. This C# WinRT component DOES output a .dll as well as .winmd. I followed a bit closer to the article Simon posted about consuming with C++ and managed to get it to work with basic C# functions.

Thomas T
  • 59
  • 6
  • 1
    *"I assume both the C++/WinRT and C#/WinRT components would compile into something that is in an Intermediate Language"* - That is not correct. C++/WinRT compiles to native code. It will not produce a dependency on the CLR. It's strictly the compile-time metadata (.winmd) that repurposes the ECMA-335 file format to store type descriptions. I'm not familiar with C#/WinRT, its build model, or the artifacts it creates. I'm sure that it must produce an assembly holding the code, too. Maybe that gets compiled into the .winmd (which is a PE image after all). – IInspectable Sep 25 '21 at 05:40
  • @IInspectable well that makes a bit more sense. If the `.winmd` is also a PE, then there might be something I can do with that. Thanks. – Thomas T Sep 27 '21 at 13:53

1 Answers1

1

REGDB_E_CLASSNOTREG means the class that you ask for (whatever it is COM/WinRT etc.) is not registered/known to the activation system (hosted in combase.dll).

The problem probably comes from the fact you're trying to use a registration-free WinRT component.

Let's take this sample as a start for the C# component: Walkthrough: Create a C#/WinRT component and consume it from C++/WinRT. So, just create the C# component but don't create the C++/WinRT app. (I use Visual Studio 2019 and net5.0-windows10.0.19041.0).

Note: C#/WinRT does produce a .dll (here SampleComponent.dll), not only metadata.

If you don't build the C++/WinRT app, you still need to build a regular .h file to use the C# component. C++/WinRT does that for you, but since we don't use this tool, we must build it ourselves. For that, we need two other tools winmdidl.exe and midlrt.exe that you'll find from Developer Command Prompt for Visual Studio..See also How to: Use winmdidl.exe and midlrt.exe to create .h files from windows metadata

So from the SampleComponent.winmd that you have if you followed the tutorial, run:

winmdidl SampleComponent.winmd

this will create a SampleComponent.idl file. Now run:

midlrt SampleComponent.idl /metadata_dir "C:\Windows\System32\WinMetadata"

this will create multiple files (proxy, stub, etc.), but we only need SampleComponent.h. Now, create a standard C++ console app like this (I don't use C++/WinRT I still use Wrl to simplify my code, but this is not mandatory):

#include <windows.h>
#include <stdio.h>
#include <wrl.h>
#include <wrl/wrappers/corewrappers.h>
#include "path to SampleComponent.h"

#pragma comment(lib, "runtimeobject.lib")

using namespace Microsoft::WRL; // ComPtr
using namespace Microsoft::WRL::Wrappers; // RoInitializeWrapper, HStringReference, HString
using namespace Windows::Foundation; // GetActivationFactory, ActivateInstance

int main()
{
    RoInitializeWrapper init(RO_INIT_MULTITHREADED);
    HRESULT hr = init;

    // all error checks on hr omitted

    ComPtr<SampleComponent::IExampleClass> cls;
    hr = ActivateInstance(HStringReference(RuntimeClass_SampleComponent_Example).Get(), &cls);
    hr = cls->put_SampleProperty(42);

    INT32 i;
    hr = cls->get_SampleProperty(&i);
    wprintf(L"%u\n", i);

    ComPtr<SampleComponent::IExampleStatic> clsStatic;
    hr = GetActivationFactory(HStringReference(RuntimeClass_SampleComponent_Example).Get(), &clsStatic);

    HString str;
    hr = clsStatic->SayHello(str.GetAddressOf());
    wprintf(L"%s\n", str.GetRawBuffer(nullptr));
}

RuntimeClass_SampleComponent_Example is from SampleComponent.h and should be defined like this:

extern const __declspec(selectany) _Null_terminated_ WCHAR RuntimeClass_SampleComponent_Example[] = L"SampleComponent.Example";

If you compile that and run, hr will be REGDB_E_CLASSNOTREG because the system cannot find the 'SampleComponent.Example' component.

So what you must do is explained here: How Registration-free WinRT Works

You must add a file to the project with the .manifest extension (any name should work with recent versions of Visual Studio), for example like this:

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="CppConsoleApp"/>
  <file name="WinRT.Host.dll">
    <activatableClass
        name="SampleComponent.Example"
        threadingModel="both"
        xmlns="urn:schemas-microsoft-com:winrt.v1" />
  </file>
</assembly>

assemblyIdentity's name is not super important, what is super important is file and activatableClass's name: it must be the same as the host dll name (here it must be WinRT.Host.dll which is provided by C#/WinRT) and class name you're trying to activate (corresponding to RuntimeClass_SampleComponent_Example).

You must also copy all the C#/WinRT files mess needed aside your .exe file. That would be : SampleComponent.dll, Microsoft.Windows.SDK.NET.dll, WinRT.Host.dll, WinRT.Host.runtimeconfig.json, WinRT.Host.Shim.dll, WinRT.Runtime.dll.

Note you can use C++/WinRT to help building WinRT.Host.runtimeconfig.json.

And now, it should work.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • While I still ran into the `REGDB_E_CLASSNOTREG` error, I think that has more with what I messed up on and less with your post. After I fiddled with the instructions a bit I finally got it to work (with basic C# functions)! I diverted from your post by using the C++/WinRT NuGet and integrating that way after trying all of your instructions. My next error is using third party .dll files, which ended up with `result = 0x80131524 : Could not find the specified DllImport Dll.` but I think that is a different problem. – Thomas T Sep 27 '21 at 18:29
  • @ThomasT - my answer specifically answers your initial question "how to call a C#/WinRT Component from a C++ win32 app w/o using C++/WinRT". If you carefully follow the steps, it will work. – Simon Mourier Sep 28 '21 at 06:54