I started with a very sophisticated system of clients and servers with COM references and other things, and I've cut down and down until I realized I can't even get Microsoft sample code to work for registration free COM activation of a managed COM server written in C#.
Server code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace ClassLibrary1
{
[Guid("A7AC6D8C-FF17-4D2C-A3B1-2C8690A8EA04")
,ComVisible(true)]
public interface IClass1
{
[DispId(1)]
string DummyFunction(string inputValue);
}
[Guid("81723475-B5E3-4FA0-A3FE-6DE66CEE211C"),
ClassInterface(ClassInterfaceType.None),
ComDefaultInterface(typeof(IClass1)),
ComVisible(true)]
public class Class1 : IClass1
{
public string DummyFunction(string inputValue)
{
return inputValue.Substring(0, 1) + " Inserted " + inputValue.Substring(1);
}
}
}
Client VB6 Code:
Dim c As ClassLibrary1.Class1
Set c = New Class1
MsgBox c.DummyFunction("Ben")
Client C++ Code:
#include "stdafx.h"
#import <ClassLibrary1.tlb> raw_interfaces_only
using namespace ClassLibrary1;
int _tmain(int argc, _TCHAR* argv[])
{
IClass1Ptr p;
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
hr = CoCreateInstance(__uuidof(Class1), NULL, CLSCTX_INPROC_SERVER, __uuidof(IClass1), (void **)&p);
if (FAILED(hr))
{
_tprintf_s(_T("Error %x\n"), hr);
CoUninitialize();
return 1;
}
_bstr_t b = _T("Bobby");
BSTR b2;
p->DummyFunction(b, &b2);
wprintf_s(L"%s\n", b2);
p->Release();
CoUninitialize();
return 0;
}
Both of the clients work fine when I remove all Reg-Free COM code and register the ClassLibrary1.dll with regasm /codebase.
Then I unregister ClassLibrary1, and try to introduce Reg-Free COM for the VB6 client with the file Project1.exe.manifest:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="Project1" version="1.0.0.0" />
<dependency>
<dependentAssembly>
<assemblyIdentity name="ClassLibrary1" version="1.0.0.0" />
</dependentAssembly>
</dependency>
</assembly>
And ClassLibrary1.manifest:
<?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="ClassLibrary1" />
<clrClass clsid="{81723475-B5E3-4FA0-A3FE-6DE66CEE211C}" name="ClassLibrary1.Class1" tlbid="{F8A2D334-5BBB-4007-8308-A1417052E6D6}"></clrClass>
<file name="ClassLibrary1.dll" ></file>
</assembly>
Now I get Error 429 (ActiveX Component can't create object) sometimes, and (inexplicably) an automation error other times:
Run-time error '-2146234304 (80131040)': Automation Error
then I try to introduce COM Isolation into the C++ client:
Now when I run the C++ client, the output is merely
Error 800401f9
To all my frustrations, BlueMonkMN's explanation was really useful but yet I encountered the
Class not registered
message.I have a .NET 4.6 COM interop Dll and a C++ client which makes use of Registration Free COM by the means of Windows SxS technology. Assembly manifest was embedded inside the COM Dll and the application manifest was put outside, as a separate .manifest file.
Everything was working as advertised but it started getting the
Class not registered
message duringCreateInstance
when any one of the following happens.In this situation, to add insult to injury,
sxstrace
log was empty, application event log (eventvwr
) didn't report any SideBySide error and the Assembly Binding Log Viewer (FUSLOGVW.exe
) didn't show anything from the C++ client application.Let me add another Addendum to BlueMonkMN's answer ablove.
Addendum 6
What I understand from my so far experiments is that, in such a case, the Activation Context for the C++ client application is not getting created.
In such a situation one need to create the Activation context by themselves.
Following code is put inside the C++ client application, after a call to
CoInitialize(0);
and before making any call toCreateInstance
.As a cleanup measure, following piece of code is put just before a call to
CoUninitialize();
Now it sounds good. Side by Side is working very well. If anything goes wrong, then it can be seen in sxstrace or in application event log or in Assembly Binding Log Viewer.
After many trials working through various samples with Microsoft support, I have identified many pitfalls that arise in attempting to implement a managed COM server with an unmanaged C++ COM client. Here are the key pieces of information that I recall, which can be applied to the sample code in the question to make sure it works.
-
Then add a pre-build step like this:
Then in the project settings "Application" tab, change the "Resources" to use ClassLibrary1.res instead of "Icon and manifest". But this comes with problems: firstly, the path to RC.EXE is not easy to define without hard-coding it; secondly, the version information from AssemblyInfoCommon will be ignored because the resources in the RC file totally replace all Win32 resources that would be generated by the .NET compiler.
Another possibility is to simply keep the server COM DLL manifest file separate and not embed it as a resource. I have read that this may not be reliable, but it works on Windows 7 Enterprise 64-bit SP1.
ConsoleApplication1.exe.config
) that defines how to load .NET. For .NET 4.5, I have seen this work:-
While for .NET 3.5, it seems useLegacyV2RuntimeActivationPolicy needs to be switched:
It's important to check that all the framework and CLR versions are in sync. And it's important to understand that the CLR versions are not the same as the framework versions. For example, if you want to build a managed COM server on .NET 3.5, the CLR version (runtimeVersion) should be "2.0.50727", but the .NET version (supportedRuntime) should be "v3.5".
Make sure that the COM server's .NET Framework target version matches the client's supportedRuntime. If it is being built from the command line, it may not be picking up the framework version from the Visual Studio project file (for example, if you are running the C# of VB.NET compiler directly instead of calling MSBuild), make sure that the build is targeting the right version of the framework.
I have not validated all the above yet, but intend to walk through this whole process soon to verify that I caught everything. Here is what I ended up with that I haven't mentioned yet:
ConsoleApplication1.exe.manifest (in source directory, gets copied or embeded into output directory at build time)
ClassLibrary1.manifest
EDIT:
Now to go through and validate every detail with full error message info etc.
I start by creating a single solution containing two projects with all default values and the code shown in the question. I begin with no manifest files nor any of the project settings mentioned in the question, and will explicitly call out when I am making these changes in the process below. These are the steps and errors that are on the path to making this project work.
tlbexp ClassLibrary1.dll
#import <ClassLibrary1.tlb> raw_interfaces_only
with quotes, so it reads#import "ClassLibrary1.tlb" raw_interfaces_only
. Rebuild: Success.Error 80040154
(Class not registered) because we have not registered the component nor set up registration-free COM.Error 800401f9
we'll skip that and just try to create a client manifest. Create a new text file with the following content, and save it as ConsoleApplication1.exe.manifest in the ConsoleApplication1 project directory:-
-
-
The same error would still occur if useLegacyV2RuntimeActivationPolicy were excluded in this case. Unfortunately, I don't fully understand why, but I suspect it has something to do with the newer v4.0 runtime activation policy defaulting to loading CLR v2.0 if the executable being loaded does not explicitly reference .NET 4.0 (which un-managed code does not because it doesn't explicitly reference .NET period).
sn -T ClassLibrary1.dll
from a developer command prompt. After updating ClassLibrary1.manifest and ConsoleApplication1.exe.manifest, remember to rebuild ConsoleApplication1.exe if the manifest is embedded, and to copy ClassLibrary1.manifest to the ConsoleApplication1.exe directory. Run again and?publicKeyToken
and not some other ridiculous name likeprivateKeyToken
; b) Make sure that all the attributes you specified in the assemblyIdentity on the server side manifest match those on the client side manifest, and that you don't havetype="win32"
specified on one but not the other.B Inserted obby
I should also note that the VB6 client also works by using the following files along with the VB6 client:
Project1.exe.config:
Project1.exe.manifest:
However, there does seem to be a tendency to report "ActiveX Component Can't Create Object" (Runtime error 429) in rare cases when the executable is built and run before creating the configuration and manifest files. Rebuilding the EXE file seems to fix it, but then I can't get the problem to come back, so it's hard to identify a specific cause.
I thought I was a reasonably good problem solver, but something about the numerous moving parts and numerous unhelpful error codes and error messages reported in reg-free com configuration problems makes this nearly impossible to figure out without some solid experience, inside knowledge or Microsoft source code. Hopefully this answer will help others acquire similar experience. Please extend this answer if you learn more!
Addendum 1
The managed COM server's manifest can be properly and easily embedded if you use the "Add"->"New Item..." command on the project to add an "Application Manifest File". This adds a file called app.manifest to the project. But the real tricky part is that it does so in a way that cannot be replicated any other way via the Visual Studio UI, except through one screwy work-around. Since the "Manifest" field on the "Application" tab of the project settings window is disabled for "Class Library" type projects, the manifest, which would normally be set here, cannot be set for a class library. But you can temporarily change the project to a Windows Application, change the Manifest selection here, then restore it to a Class Library. The setting will stick so the selected manifest gets properly embedded. You can verify the setting in a text editor by viewing the project file. Look for:
Addendum 2
Error 0x80131040 can still occur with all the above precautions taken. To help narrow down the cause of this, it helps to use the fusion log viewer to see more information about what is happening as assemblies are being loaded and resolved. Google "Fuslogvw" for more information about how to view this log (fuslogvw.exe is a utility provided when Visual Studio is installed). It's also important to realize that this application, by default, apparently does not show any information until you configure it to log information to files, reproduce the problem, then restart the application to read the log files after they are produced. And, according to MSDN documentation, it's also important to remember to run this utility as administrator.
Once you've passed all the hurdles to running fuslogvw.exe, you may see something like this in the log:
Despite the fact that the COM server's manifest file listed the version as 1.0.0.0, that is not the (only) version used when binding from a COM client reference to the server. My client EXE file was trying to reference 1.0.0.0, which exactly matched the version in the COM server's manifest file, but it did not match the .NET version of the DLL. After correcting both the client and server manifest files to reflect the version actually in the .NET server DLL, then error 0x80131040 went away, and fuslogvw.exe was the key to identifying that as the source of the problem.
If the Client manifest is in sync with the actual .NET DLL version, but the server DLL's manifest file does not reflect this version, a different error will occur:
Addendum 3
Error 0xc0150002 or the following message may be reported:
I have seen this occur in a case where the client manifest was embedded in an unmanaged DLL rather than an unmanaged EXE, and the manifest's
assemblyIdentity
element did not exactly match the server'sassemblyIdentity
. The client had an extraprocessorArchitecture="x86"
in it that the server did not specify, causing a mismatch. Unfortunately I don't know how to learn this without luckily thinking to check the manifest files to see that they match (or reading this article). That error doesn't clearly point to a manifest file being the source of the problem, so you just have to be aware that there's a possible correlation between that error message and this cause.Addendum 4
I have seen external manifest files get completely ignored yielding a completely empty sxstrace log, even when the executables involved have no embedded manifests. This can apparently happen as a result of the activation context cache (a problem documented at http://csi-windows.com/blog/all/27-csi-news-general/245-find-out-why-your-external-manifest-is-being-ignored). To work around this problem you can use the following command to touch the date stamp of the file whose manifest is being ignored:
Addendum 5
I have seen another hard-to-explain Class Not Registered error (
0x80040154
-REGDB_E_CLASSNOTREG
) that occurs when calling CoCreateInstance under the following conditions:DllMain
if the/clr
switch is not applied to the CPP file, or during.cctor
if the/clr
switch is applied to the file.regasm
.CoCreateInstance
has the/clr
switch applied to the C++ compiler settings.If any of the last 3 conditions are altered, the problem goes away. (Additionally, if the last condition is altered, you may get a loader lock due to #1 -- read about loader lock and it's relation to CLR at Initialization of Mixed Assemblies). So if you are encountering a Class Not Registered error in similar circumstances, consider whether you can alter any of those last 3 conditions to resolve the error. Note: I'm having a hard time nailing down the behavior of #6. It seems the effect of switching this also depends the state of #1. It looks like calling the constructor (including its
CoCreateInstance
) after the DLL is fully loaded still causes Class Not Registered whereas calling the constructor during the DLL initialization will succeed if the/clr
switch is not specified. My solution for the time being is to re-code the client CPP file in managed C++ since it was a relatively simple interface class between the COM component and the rest of the un-managed code. So now there's no more COM in this client, just a .NET reference.