Reputation: 11587
I need to create a COM class in .NET8 that needs to be accessible to Excel.
After watching this video, I implemented the following test bed class:
namespace COMTestBedCS
{
[Guid("26a0aa6d-5aba-458f-92b4-b9a30ae0c65c")]
[GeneratedComInterface]
public partial interface ITestBed
{
int GetXPTO();
void SetXPTO(int value);
}
[Guid("3e178f98-522e-4e95-8a9c-6d80dc48b7d5")]
[GeneratedComClass]
public partial class TestBed : ITestBed
{
private int _XPTO = 1024;
public int GetXPTO() => _XPTO;
public void SetXPTO(int value)=>_XPTO = value;
}
}
The project compiles correctly, without errors. However, when I try to reference this test bed in Excel, I get the following error: Can't add a reference to the specified file.
If I try to use regsvr32
, I get the following error:
What am I doing wrong?
For completion sake, here's the project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
I was able to compile the code correctly and register it using regsvr32
. However, I'm still unable to reference it in Excel.
First, I can't find it on the reference list. If I try to browse for the dll
(either the assembly or the comhost), it fails with the message: Can't add a reference to the specified file.
All that's left now it to add it as a Reference in the Excel VBA.
Project
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>COMTestBedCS</RootNamespace>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>False</AllowUnsafeBlocks>
<EnableComHosting>true</EnableComHosting>
<RegisterForComInterop>True</RegisterForComInterop>
<Platforms>x86</Platforms>
<RegisterAssemblyMSBuildArchitecture>x86</RegisterAssemblyMSBuildArchitecture>
</PropertyGroup>
</Project>
Code
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
namespace COMTestBedCS
{
[Guid("26a0aa6d-5aba-458f-92b4-b9a30ae0c65c")]
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITestBed
{
int GetXPTO();
void SetXPTO(int value);
}
[Guid("3e178f98-522e-4e95-8a9c-6d80dc48b7d5")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
internal class TestBed : ITestBed
{
private int _XPTO = 1024;
public int GetXPTO() => _XPTO;
public void SetXPTO(int value)=>_XPTO = value;
}
}
OBS.: To compile successfully, I had to, for some reason, build the project twice. The first time Visual Studio is unable to successfully build, with the following error:
MSB3217
Cannot register assembly "<assembly file>" Could not load file or
assembly 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies.
The system cannot find the file specified.`
Then I have to rebuild it again without cleaning the project. Only then the project compile successfully.
If anyone wants to help, I put this test bed project on GitHub here.
After following Simon Mourier's suggestion of using dscom
to create and register a TypeLib, I was able to add it as a Reference in Excel.
However, when I try to execute a simple test...
Sub Test()
Dim xpto As New COMTestBedCS.TestBed
Debug.Print xpto.GetXPTO()
End Sub
it blows up on me, with the following error: Class not registered
.
Upvotes: 2
Views: 2857
Reputation: 27
I am working on the same problem since days. And I think, I finally got it, using hints from this precious thread. I am working on Windows11, 64bit, .Net8, and also Excel 64bit, Visual Studio and everything with the latest version.
If anybody is interested, I might come up with my full example solution, but here some hints that got me there:
Make sure there is no GUID panick resulting from previous failed tests. The best way to find out is to scan REGEDIT for your GUIDs in use. Erase them with Regedit or - quick and dirty: Generate new GUIDs for your project. Scanning the registry for your GUIDs is a good idea to find out what happened with your registration using regsvr32.
In the C# code, The INTERFACE must be declared internal, in my experience:
internal partial interface ...
The CLASS however, must be public, otherwise it does not get registered.
<PropertyGroup>; <EnableComHosting>;true</EnableComHosting> </PropertyGroup>;
This will generate a <myproject>.comhost.dll which you can register with regsvr32, as it is mentioned above. The registration name remains <myproject> which is the same as the name of your VisualStudio project.
Dim O As Object Set O = CreateObject("<MyClassLibraryName>.<MyClassName>")
You can then use the object's function: My COM-Class, contains test function AddThem(a,b) that simply adds two integers and returns the result.
C# within the COM Class:
public int AddThem(int a, int b) { return (a + b); }
VBA:
debug.print O.AddThem(3,10) 13
The TLB generation using DSCOM worked well with me ONCE. Thanks for the DSCOM hint in this thread! For use with Excel or VBSCRIPT etc. It is NOT mandatory, if you set the object as in 4).
HOWEVER: If you declare the Interface "Internal", DSCOM won't be able to add it. This is in contradiction with point 2). If you declare it public, the COM class will not register properly.
The TLB is necessary ONLY if you with to be able to add the REFERENCE in Excel/Word VBA. Doing so, you will ease the coding by VBA intellisense.
Be aware that the type library is completely independent from the Class. Setting up the type library does in no way guarantee that the Class will work.
MY SUGGESTION: Drop the reference and tlb thing altogether. Much complication for the sole advantage of VBA intellisense. Additionally, opening the object in VBA code with CreateObject("..") makes the VBA module more portable, not depending on VBA reference which must be added manually in each new .xlsm.
If you STILL wish to make a VBA referencable entry, proceed as follows (also I would NOT recommend this "dirty workaround"):
Upvotes: 1
Reputation: 139187
First, you must follow what's detailed here Expose .NET Core components to COM, Generate the COM host:
Open the .csproj project file and add
<EnableComHosting>true</EnableComHosting>
inside a<PropertyGroup></PropertyGroup>
tag.
But doing this will raise "The "GenerateClsidMap" task failed unexpectedly." error, as explained here:
SYSLIB diagnostics for COM interop source generation
SYSLIB1098 .NET COM hosting with
EnableComHosting
only supports built-in COM interop. It does not support source-generated COM interop withGeneratedComInterfaceAttribute
.
GeneratedComInterfaceAttribute
is a new to .NET 8 feature, that you can't use here in this scenario (exposing and hosting your own interfaces), so you'll have to replace your code by something like this to revert back to "built-in" COM interop:
[ComVisible(true)]
[Guid("26a0aa6d-5aba-458f-92b4-b9a30ae0c65c")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public partial interface ITestBed
{
int GetXPTO();
void SetXPTO(int value);
}
[ComVisible(true)]
[Guid("3e178f98-522e-4e95-8a9c-6d80dc48b7d5")]
[ClassInterface(ClassInterfaceType.None)]
public partial class TestBed : ITestBed
{
private int _XPTO = 1024;
public int GetXPTO() => _XPTO;
public void SetXPTO(int value) => _XPTO = value;
}
Once the project is build, you can call regsvr32 <myfile>.comhost.dll
(which has been generated by .NET Core) with sufficient rights.
You cannot use the RegisterForComInterop
.csproj directive anymore as it's reserved for .NET Framework (it internally uses the same code as Regasm
which you cannot use either).
Now, if you want to use this type of object with Excel (VBA), you need a type library, either as a .TLB file or embedded in a .DLL (usually the COM server one). Unfortunately, .NET Core doesn't create one, so you must build it by yourself.
There are many ways, the official one is to create a .IDL file and use the MIDL Compiler. Another way is to continue to use .NET Framework just for regasm's capability to create a .TLB from a .cs file. Another one is to use this unofficial dscom tool (or dscom32 if you use x86) so, run
dscom tlbexport c:\mypath\COMTestBedCS.dll
and then
dscom tlbregister c:\mypath\COMTestBedCS.tlb
Upvotes: 5
Reputation: 4696
<EnableComHosting>true</EnableComHosting>
property into the <PropertyGroup>
in the csproj. This will tell dotnet
to generate *.comhost.dll
.TestBed
class internal
instead of public
. This will solve the build problem (I don't know why, but from a general point of view, the implementation (the TestBed
class) should not be accessible outside the assembly in any way other than the generated factory).Upvotes: 0