简体   繁体   中英

How to create a COM object from registration free COM C# managed DLL in a C++ unmanaged/native DLL

I'm in the process of creating a Heartrate Monitor plugin for OBS Studio. The idea is to access Bluetooth GATT services on C# side to get data from a Smart BT HRM, render it and then transfer that to C++ dll that acts as the OBS Studio plugin itself.

I've managed to get everything working, and I can call a function in the C# from C++ side that returns with an array for the image data, which I can then subsequently give to OBS. However, this requires the C# DLL to be registered. I'd rather not pollute the registry with useless DLL information, so I looked into Registration Free COM. (It also would make distribution/install a lot simpler)

After lots of fiddling, I've come to an impasse. With sxstrace, I know the proper library is being picked up by the side-by-side system:

=================
Begin Activation Context Generation.
Input Parameter:
    Flags = 0
    ProcessorArchitecture = AMD64
    CultureFallBacks = en-US;en
    ManifestPath = C:\Program Files\obs-studio\obs-plugins\64bit\HrmPlugin.dll
    AssemblyDirectory = C:\Program Files\obs-studio\obs-plugins\64bit\
    Application Config File = 
-----------------
INFO: Parsing Manifest File C:\Program Files\obs-studio\obs-plugins\64bit\HrmPlugin.dll.
    INFO: Manifest Definition Identity is HrmPlugin,processorArchitecture="AMD64",type="win32",version="1.0.0.0".
    INFO: Reference: OBSBluetoothHeartrate,processorArchitecture="*",type="win32",version="1.0.0.0"
INFO: Resolving reference OBSBluetoothHeartrate,processorArchitecture="*",type="win32",version="1.0.0.0".
    INFO: Resolving reference for ProcessorArchitecture AMD64.
        INFO: Resolving reference for culture Neutral.
            INFO: Applying Binding Policy.
                INFO: No binding policy redirect found.
            INFO: Begin assembly probing.
                INFO: Did not find the assembly in WinSxS.
                INFO: Attempt to probe manifest at C:\Program Files\obs-studio\obs-plugins\64bit\OBSBluetoothHeartrate.DLL.
                INFO: Manifest found at C:\Program Files\obs-studio\obs-plugins\64bit\OBSBluetoothHeartrate.DLL.
            INFO: End assembly probing.
INFO: Resolving reference OBSBluetoothHeartrate.mui,language="*",processorArchitecture="AMD64",type="win32",version="1.0.0.0".
    INFO: Resolving reference for ProcessorArchitecture AMD64.
        INFO: Resolving reference for culture en-US.
            INFO: Applying Binding Policy.
                INFO: No binding policy redirect found.
            INFO: Begin assembly probing.
                INFO: Did not find the assembly in WinSxS.
                INFO: Did not find manifest for culture en-US.
            INFO: End assembly probing.
        INFO: Resolving reference for culture en.
            INFO: Applying Binding Policy.
                INFO: No binding policy redirect found.
            INFO: Begin assembly probing.
                INFO: Did not find the assembly in WinSxS.
                INFO: Did not find manifest for culture en.
            INFO: End assembly probing.
INFO: Parsing Manifest File C:\Program Files\obs-studio\obs-plugins\64bit\OBSBluetoothHeartrate.DLL.
    INFO: Manifest Definition Identity is OBSBluetoothHeartrate,processorArchitecture="AMD64",type="win32",version="1.0.0.0".
INFO: Activation Context generation succeeded.
End Activation Context Generation.

However, when I try to create the class from the COM object itself, I get a class not found error. The code to create it is as follows:

static void Initialize(hrm_source *context)
{
    if (context->init_failed) return;

    try
    {
        HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
        IHrmPluginPtr pHRM(CLSID_HrmPlugin);
        context->pHRM = pHRM;
    }
    catch (_com_error _com_err)
    {
        MessageBox(NULL, _com_err.ErrorMessage(), L"Failed to initialize COM", MB_OK);
        context->pHRM = NULL;
        context->init_failed = (context->pHRM == NULL);
    }
}

The problem is, this whole thing works if either A) I register the assembly or B) I use from an exe. Actually, if I change the output type an .exe, add a main() to the plugin-dll-source and create it from there, I can run the exe and it works properly.

                 | C++ Exe | C++ DLL 
-------------------------------------
Registered       | Works   | Works
Register Free    | Works   | Doesn't Work

There is obviously some configuration flag (well, fingers crossed) or setting or attribute missing from the manifest files.

So, here's the question: how to make registration free COM work with a managed C# COM server and a native C++ DLL as the COM client?

Here are the manifest files as well:

COM Server (C# DLL)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="win32"
                    name="OBSBluetoothHeartrate" 
                    version="1.0.0.0" 
                    processorArchitecture="amd64"/>
  <clrClass clsid="{54667DC4-13CD-4D25-BB9E-C3BDDFAF019F}" 
            progid="OBSBluetoothHeartrate.HrmPlugin" 
            threadingModel="Both" 
            name="OBSBluetoothHeartrate.HrmPlugin" 
            runtimeVersion="v4.0.30319"/>
</assembly>

COM Client (C++ DLL)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="win32"
                    name="HrmPlugin"
                    version="1.0.0.0"
                    processorArchitecture="amd64"/>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32"
                        name="OBSBluetoothHeartrate"
                        version="1.0.0.0"
                        processorArchitecture="amd64"/>
    </dependentAssembly>
  </dependency>
</assembly>

Update 17th Jan 2020

I added an assemblyIdentity to the C++ DLL as well, in case it would help (as per a question in the comments). The only thing it changed is that now when it is loaded by side-by-system, it gets an identity instead of (null) - identity shouldn't be needed as I'm not using the C++ DLL as a COM server. However, I've updated the C++ DLL manifest and the sxstrace log - this is technically "more correct" now, so at least we know this is not the reason.

Update 29th Jan 2020

I had accidentally copied the C# DLL COM Server manifest as the C++ DLL COM Client manifest. This is now updated to the correct one. Made sure to double-check it, but the class is still not found, even though sxstrace says the proper dll is found and loaded. I'll try the manual activation context creation that was provided as an answer and report back (and of course mark it as the correct answer if I can get it working with that)

Here is the skeleton of an answer using manual activation contexts... the function ReportError() is a stub ... do what you want with it...log, trace, message box, or remove it...

   ACTCTX ctx = { sizeof(ctx),  ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, _T("NameOfYourDotNet.manifest")};
   ctx.lpAssemblyDirectory = szPath; // path is drive and directory where your COM DLL is


   HANDLE hCtx = CreateActCtx(&ctx);
   if (hCtx != INVALID_HANDLE_VALUE)
   {
      DWORD_PTR dwCookie = 0;
      BOOL bActivated = ActivateActCtx(hCtx, &dwCookie);
      if (bActivated)
      {
         wcout << L"After activating ActCtx..." << endl;


     // Do stuff with your .NET COM server here

         DeactivateActCtx(0, dwCookie);
      }
      else
      {
         ReportError(L"Could not activate context: ", GetLastError());
      }
      ReleaseActCtx(hCtx);
   }
   else
   {
      ReportError(_T("Problem creating ActCtx: "), GetLastError());

   }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM