I am creating a COM object in C# .NET Core 6. The COM object has to enable both early and late binding.
I am using Visual Studio and a .NET Core 6 class library, where I created a COM object, a COM interface and an idl-file for generating the type library for early binding. After building the project I get a normal *.dll file, a *.comhost.dll file and a *.comhost.tlb. I can then register the *.comhost.dll with regsvr32. The *.comhost.tlb is registered alongside the *.comhost.dll because I created some register functions in the COM object that manually edit the registry (I wasn't able to find a better way in C# .NET Core 6).
So everything gets registered successfully and I can use the early bound COM object with Delphi (RAD Studio). It works as intended, by importing the COM object through a TLB as a component in RAD Studio.
When I try to use the COM object as a late bound COM object though I get an error in Python and MATLAB.
This is the Python error I receive:
>>> import pythoncom
>>> import pywintypes
>>> from win32com.client import Dispatch
>>> com_unknown = pythoncom.CoCreateInstance(pywintypes.IID('COMObjectEarlyBinding'), None, pythoncom.CLSCTX_ALL, pythoncom.IID_IUnknown)
>>> com_dispatch = Dispatch(com_unknown.QueryInterface(pythoncom.IID_IDispatch))
>>> com_dispatch.Addition
<bound method Addition of <COMObject <unknown>>>
>>> com_dispatch.Addition(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<COMObject <unknown>>", line 2, in Addition
pywintypes.com_error: (-2147352573, 'Member not found.', None, None)
>>>
Python can create the COM object and it seems to recognize the method (not sure though).
Here's the MATLAB error:
>> com_dispatch = actxserver('COMObjectEarlyBinding')
com_dispatch =
COM.COMObjectEarlyBinding
>> com_dispatch.methods
Methods for class COM.COMObjectEarlyBinding:
Addition constructorargs events invoke propedit send
HelloWorld delete get load release set
addproperty deleteproperty interfaces move save
>> invoke(com_dispatch, 'Addition', 1, 2)
Error using COM.COMObjectEarlyBinding/Addition
Error: method or property not found
>> com_dispatch.Addition(1,2)
Error using COM.COMObjectEarlyBinding/Addition
Error: method or property not found
>>
MATLAB can also create the COM object and it seems to be able to recognize the method.
So both programs can create the COM object and it looks to me that they also recognize the method 'Addition'. I don't know why they are having issues with using/invoking the COM object's method though. Unfortunately I wasn't able to find a solution to this problem yet. I am not sure if this is a Python/Matlab error, if it's a C# error or if it is an error that is related to COM in general.
I made my C# class library by following this guide https://github.com/GregReddick/ComTestLibrary/tree/master/ComTestLibrary1 with some modifications. Here is my Code for the classes that I have created inside the C# class library, in case I made an error in C# when creating the COM object.
Edit:
Here is a link to a GitHub repository containing my COM project: https://github.com/Spikxzy/COM-CSharp-.NET-Core-6
There is a line inside the 'MidlOptions' tag of the *.csproj file that needs to be changed. This line /cpp_cmd "C:\CL_Compiler\x64\cl.exe"
needs to point to a cl.exe. This is needed by the Midl-Compiler in order to generate the TLB. The cl.exe is included in Visual Studio but the default path to it ('C:\Program Files (x86)\Microsoft Visual Studio$(VSVersion)\VC\Tools\MSVC$(CLVersion)\bin\Hostx64\x64') will not work, since the path is not allowed to contain spaces. The workaround for this is supposed to be done by choosing a 'DriveLetter' that is not used and call the 'subst' command on it but that didn't work for me.
Also you need to change the 'VSVersion' tag to match your version of Visual Studio.
Here is the link to the new Repository: https://github.com/Spikxzy/ComInDotNETCore
For this to work you needVisual Studio with C++ Tools installed. Afterwards
the first <ProppertyGroup>
in COMEarlyBinding.csproj needs to be changed. Substitute the tags contained in this <ProppertyGroup>
tag so that this path:
'C:\$(VSLocation)\Microsoft Visual Studio\$(VSVersion)\VC\Tools\MSVC\$(CLVersion)\bin\Hostx64\x64' is correct for your Visual Studio installation.
COMEarlyBinding.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<CLVersion>14.31.31103</CLVersion>
<DriveLetter>N:</DriveLetter>
<KitsVersion>10.0.19041.0</KitsVersion>
<VSVersion>2022\Professional</VSVersion>
<VSLocation>Program Files</VSLocation>
</PropertyGroup>
<ItemGroup>
<Compile Include="AssemblyInfo.cs" />
<Compile Include="COMObject.cs" />
<Compile Include="ICOMObject.cs" />
<None Include="definitions.idl" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.321">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<AssemblyName>$(MSBuildProjectName)$(Platform)</AssemblyName>
<Description>Com Test Library</Description>
<EnableComHosting>true</EnableComHosting>
<EnableDefaultItems>false</EnableDefaultItems>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<NETCoreSdkRuntimeIdentifier>win-$(Platform)</NETCoreSdkRuntimeIdentifier>
<Platforms>x64;x86</Platforms>
<TargetFramework>net6.0-windows7.0</TargetFramework>
<PathKitsBin>c:\Program Files (x86)\Windows Kits\10\bin\$(KitsVersion)</PathKitsBin>
<PathKitsInclude>C:\Program Files (x86)\Windows Kits\10\include\$(KitsVersion)</PathKitsInclude>
<MidlOptions>
/cpp_cmd "$(DriveLetter)\cl.exe"
/dlldata nul /h nul /iid nul /proxy nul
/env $(Platform.Replace("x86", "win32").Replace("x64", "win64"))
/I "$(PathKitsInclude)\um\64"
/I "$(PathKitsInclude)\um"
/I "$(PathKitsInclude)\shared"
/out "bin\$(Platform)\$(Configuration)\$(TargetFramework)"
/tlb "$(MSBuildProjectName)$(Platform).comhost.tlb"
definitions.idl
</MidlOptions>
</PropertyGroup>
<Target AfterTargets="PostBuildEvent" Name="PostBuild">
<WriteLinesToFile File="$(TargetDir)midloptions.txt" Overwrite="true" Lines="$(MidlOptions)" />
<Exec IgnoreExitCode="true" Command="subst $(DriveLetter) "C:\$(VSLocation)\Microsoft Visual Studio\$(VSVersion)\VC\Tools\MSVC\$(CLVersion)\bin\Hostx64\x64"" />
<Exec command=""$(PathKitsBin)\x64\midl.exe" @$(TargetDir)midloptions.txt"" />
<!-- The .dll and .tlb can be registered automatically if this <Exec> tag is not commented out.
<Exec command="regsvr32 /s "$(TargetDir)$(TargetName).comhost.dll"" />
-->
</Target>
<Target AfterTargets="BeforeClean" BeforeTargets="CoreClean" Name="RegClean">
<Exec IgnoreExitCode="true" Command="regsvr32 /s /u "$(TargetDir)$(TargetName).comhost.dll"" />
</Target>
</Project>
definitions.idl for creating the type library with the MIDL compiler:
[
uuid(6CAE0A24-8102-4070-A049-7AF0546E1419),
version(1.0),
helpstring("COMEarlyBinding")
]
library ComTestLibrary
{
importlib("STDOLE2.TLB");
[
odl,
uuid(79FA01D0-D8A6-4A5F-93AA-0C194109D08F),
dual,
oleautomation,
nonextensible,
helpstring("ICOMObject"),
object
]
interface ICOMObject : IDispatch
{
[
id(1),
helpstring("Addition")
]
HRESULT Addition(
[in] double firstValue,
[in] double secondValue,
[out, retval] double* ReturnVal);
[
id(2),
helpstring("HelloWorld")
]
HRESULT HelloWorld(
[out, retval] BSTR* ReturnVal);
};
[
uuid(12882A6C-7176-47BF-8F39-7C029B9350A2),
helpstring("COMObject")
]
coclass COMObject
{
[default] interface ICOMObject;
};
}
AssemblyInfo.cs:
using System;
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: ComVisible(true)]
[assembly: Guid(COMEarlyBinding.AssemblyInfo.LibraryGuid)]
namespace COMEarlyBinding
{
internal class AssemblyInfo
{
/// <summary> Id for the class </summary>
internal const string ClassGuid = "12882A6C-7176-47BF-8F39-7C029B9350A2";
/// <summary> Id for the interface.</summary>
internal const string InterfaceGuid = "79FA01D0-D8A6-4A5F-93AA-0C194109D08F";
/// <summary> Id for the library.</summary>
internal const string LibraryGuid = "6CAE0A24-8102-4070-A049-7AF0546E1419";
/// <summary>Gets an assembly attribute.</summary>
/// <typeparam name="T">Assembly attribute type.</typeparam>
/// <returns>The assembly attribute of type T.</returns>
internal static T Attribute<T>()
where T : Attribute
{
return typeof(AssemblyInfo).Assembly.GetCustomAttribute<T>();
}
}
}
ICOMObject:
using System;
using System.Runtime.InteropServices;
namespace COMEarlyBinding
{
[ComVisible(true)]
[Guid(AssemblyInfo.InterfaceGuid)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICOMObject
{
double Addition(double firstValue, double secondValue);
string HelloWorld();
}
}
COMObject:
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace COMEarlyBinding
{
[ComVisible(true)]
[Guid(AssemblyInfo.ClassGuid)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("COMObjectEarlyBinding")]
public class COMObject : ICOMObject
{
// <summary>DLL register server.</summary>
/// <param name="t">A Type to process.</param>
[ComRegisterFunction]
public static void DllRegisterServer(Type t)
{
// Additional CLSID entries
using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(@"CLSID\{" + AssemblyInfo.ClassGuid + @"}"))
{
using (RegistryKey typeLib = key.CreateSubKey(@"TypeLib"))
{
typeLib.SetValue(string.Empty, "{" + AssemblyInfo.LibraryGuid + "}", RegistryValueKind.String);
}
}
// Interface entries
using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(@"Interface\{" + AssemblyInfo.InterfaceGuid + @"}"))
{
using (RegistryKey typeLib = key.CreateSubKey(@"ProxyStubClsid32"))
{
typeLib.SetValue(string.Empty, "{00020424-0000-0000-C000-000000000046}", RegistryValueKind.String);
}
using (RegistryKey typeLib = key.CreateSubKey(@"TypeLib"))
{
typeLib.SetValue(string.Empty, "{" + AssemblyInfo.LibraryGuid + "}", RegistryValueKind.String);
Version version = typeof(AssemblyInfo).Assembly.GetName().Version;
typeLib.SetValue("Version", string.Format("{0}.{1}", version.Major, version.Minor), RegistryValueKind.String);
}
}
// TypeLib entries
using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(@"TypeLib\{" + AssemblyInfo.LibraryGuid + @"}"))
{
Version version = typeof(AssemblyInfo).Assembly.GetName().Version;
using (RegistryKey keyVersion = key.CreateSubKey(string.Format("{0}.{1}", version.Major, version.Minor)))
{
// typelib key for 32 bit
keyVersion.SetValue(string.Empty, AssemblyInfo.Attribute<AssemblyDescriptionAttribute>().Description, RegistryValueKind.String);
using (RegistryKey keyWin32 = keyVersion.CreateSubKey(@"0\win32"))
{
keyWin32.SetValue(string.Empty, Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, ".comhost.tlb"), RegistryValueKind.String);
}
// typelib key for 64 bit
keyVersion.SetValue(string.Empty, AssemblyInfo.Attribute<AssemblyDescriptionAttribute>().Description, RegistryValueKind.String);
using (RegistryKey keyWin64 = keyVersion.CreateSubKey(@"0\win64"))
{
keyWin64.SetValue(string.Empty, Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, ".comhost.tlb"), RegistryValueKind.String);
}
using (RegistryKey keyFlags = keyVersion.CreateSubKey(@"FLAGS"))
{
keyFlags.SetValue(string.Empty, "0", RegistryValueKind.String);
}
}
}
}
/// <summary>DLL unregister server.</summary>
/// <param name="t">A Type to process.</param>
[ComUnregisterFunction]
public static void DllUnregisterServer(Type t)
{
Registry.ClassesRoot.DeleteSubKeyTree(@"TypeLib\{" + AssemblyInfo.LibraryGuid + @"}", false);
Registry.ClassesRoot.DeleteSubKeyTree(@"Interface\{" + AssemblyInfo.InterfaceGuid + @"}", false);
Registry.ClassesRoot.DeleteSubKeyTree(@"CLSID\{" + AssemblyInfo.ClassGuid + @"}", false);
}
public double Addition(double firstValue, double secondValue)
{
return firstValue + secondValue;
}
public string HelloWorld()
{
return "Hello World!";
}
}
}
This object contains the functions that register the COM object, the COM interface and the type library by manually editing the registry. I need these entries for registering the TLB for early binding. Doing the registry editing manually was to only way I found to register a TLB for a COM object in C# .NET Core 6. I think they should work properly since early binding works fine. I am not sure if and then how this could impact the late binding.