3

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:

regsvr32 error message

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>

Update

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.

New Code

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.


Update 2

If anyone wants to help, I put this test bed project on GitHub here.


Update 3

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.


5
  • Is COMTestBedCS.dll either in your machine's PATH or in the same directory or subdirectory of your application? Being as that you said you're running out of Excel, it will likely need to be in your machine's PATH c:\windows\system32 for example. Commented Apr 10, 2024 at 16:55
  • @AxelKemper, RegASM was removed from .NET after .NET 4. Commented Apr 10, 2024 at 17:05
  • 2
    @SteveDanner, Why do you say that the DLL needs to be on the path? As far as I know, no COM class works this way. Commented Apr 10, 2024 at 17:06
  • 3
    add EnableComHosting to your project and register the <yourfile>.comhost.dll that's been generated instead of the .NET dll itself: learn.microsoft.com/en-us/dotnet/core/native-interop/… Also note no TLB will be generated by .NET Core (any version). Commented Apr 10, 2024 at 17:15
  • @SimonMourier, I tried, but first, the EnableComHosting doesn't appear in my list of possible options in the project editor. And, second, if I add it anyway, I get the following error: The "GenerateClsidMap" task failed unexpectedly. Commented Apr 10, 2024 at 17:44

4 Answers 4

6

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 with GeneratedComInterfaceAttribute.

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
Sign up to request clarification or add additional context in comments.

7 Comments

This got me almost all the way there: the project compiles correctly (with a bit of percussive maintenance 😛), but I can't use it on Excel yet. It can't add it as a reference. This is the only reason why I didn't mark it as a solution.
@PauloSantos - put some reproducible compilable project somewhere and we'll have a look at it.
Just added to GitHub. github.com/PaulStSmith/COMTestBed
Thanks, @simon-mourier. I can register using regsvr32, but my problem still remains. I can't use it on Excel. For this project I need to use it on Excel.
With the dSPACE.Runtime.InteropServices.BuildTasks NuGet package, you can create a Build Task to export and register the TLB automatically at compile time. See dscom README for more details.
|
1

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:

  1. 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.

  2. 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.

  1. As mentioned in this thread, edit the .csproj file from within Visual Studio (Project, edit Project File) and add:
<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.

  1. The VBA then looks like this:
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
  1. 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.

  2. If you STILL wish to make a VBA referencable entry, proceed as follows (also I would NOT recommend this "dirty workaround"):

  • TEMPORARILY declare the interface(s) as public.
  • dscom tlbexport <full path_to_dll> (NOT the .comhost.dll, the simple one).
  • This will generate a .tlb file in the present directory.
  • under ADMIN: dscom tlbregister <path_to_tlb_file>
  • without registering the full project with regsvr32, this should work.
  • do not forget to re-set the interfaces to "internal" instead of public in the source code. However, I would not recommend re-building the project, and re-registering.

3 Comments

That's an interesting take. I will test it out, eventually (Unfortunately, your solution came when the project has moved on from this. It was decided to move back to .NET 4.8 [don't ask, orders from above]).
Well, I have the impression that Microsoft becomes more and more prudent with interoperability, without too much documenting the issues. E.g. the "build as COM library" checkbox has gone, so has automatic registration within the "build process". Library registration will be reserved for MS-internal and/or duly authenticated assemblies. I guess this is due to security concerns with interoperability. Therefore, any tricks or workarounds or "backports" (as the ones mentioned in this thread) , might be be blocked just one sub-version later... The price for security.
If they are so darn worried about this, they should revamp everything from the ground up. Do away with COM altogether and Win32 native P/Invoke calls for good measure and build the entire OS as managed code.
1

Been a while... but after trial and error I managed to actually get something running in .Net8
There is also a separate Registration tool in here for the addin, so no need for regsvr32 or dscom.
https://github.com/HCarlb/DotnetExcelComAddIn

Works great on Win 11 x64, Office 2016 x64 with .Net8.
(but wont load if build on .Net9)

Update: It actually does load in .Net9 (and .Net10).
Found that another addin I also had installed used .net8 and was loaded before my addin.

set COREHOST_TRACE=1
set COREHOST_TRACEFILE=C:\temp\corehost_trace.log
start excel.exe

Running this in in CMD, I got this clue:
"*The specified framework 'Microsoft.NETCore.App', version '9.0.0', apply_patches=1, version_compatibility_range=minor is incompatible with the previously loaded version '8.0.15'."
*
Seems I need to keep all addins on my machine on the same .net version or they may not load properly. First to load wins.

Comments

0
  1. Add the <EnableComHosting>true</EnableComHosting> property into the <PropertyGroup> in the csproj. This will tell dotnet to generate *.comhost.dll.
  2. Make the 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).

3 Comments

I tried, but first, the EnableComHosting doesn't appear in my list of possible options in the project editor. And, second, if I add it anyway, I get the following error: The "GenerateClsidMap" task failed unexpectedly.
IntellySense is just lying to you, don't worry and add the property manually. To fix a build error please change the access modifier for the TestBed class as mentioned in the answer.
EnableComHosting leads to this Microsoft documentation of the topic.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.