I have upgraded from Windows 7/Visual Studio 2015 to Windows10/Visual Studio 2019.
I wish to create a C# COM object which I can call from Excel(365) VBA code.
Using Win 7/Visual Studio 2015 the following very simple code compiles as a C# class library and creates a COM object that I can call successfully from Excel 2013 VBA:-
using System.Runtime.InteropServices;
namespace TestLib {
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class C_Hello {
public string Hello() {
return "Hello";
}
}
}
In the Assembly Info I have ticked the checkbox "Make the Assembly COM Visible" and in the Build info I have ticked the checkbox "Register for COM interop"
The Excel 2013 VBA code is equally simple:
Sub Test()
Dim x As TestLib.C_Hello
Set x = New TestLib.C_Hello
ActiveSheet.Range("C3").Value = x.Hello
End Sub
where TestLib is the C# COM module created in Visual Studio.
In the Win 7/VS2015/Excel 2013 environment everything works fine, but under after transferring and compiling under Win 10/ VS2019 the Excel 365 VBA code errors at the line:-
Set x = New TestLib.C_Hello
with the error message
Run Time error '-2147221164 (80040154)'
Class not registered
Can anyone tell me what I have to do to get this working in the Windows 10 / VS2019 / Excel 365 environment.? Is it Windows 10 or Visual Studio 2019 that is doing things differently?
Make sure the bitness of excel matches your build in VS. You will likely need to add an x86 or x64 build for your project.
It is possible to register an AnyCpu assembly, but visual studio does not do so with the “Register for COM interop” for both. You can manually do so by running both the 32- and 64-bit versions of regasm manually or from a post-build script.
Failing that, you might use ProcMon to check what registration or file Excel is looking for and not finding. Warning, though, that unless you are familiar with the “normal” errors which occur when running it is easy to troubleshoot a red herring. I would recommend filtering by “Path” contains “TestLib.C_Hello” then chase it from there.
The solution in the Win10 / VS2019 environemnt was to change the Platform Target in VS's Build Tab from Any CPU to x64, and then it worked.
In the Win7 / VS2015 environment a Platform Target of Any CPU worked fine.
Related
Goal: Create a C# Assembly called TestDLL.dll that can be installed to any computer such that MS Access VBA can use it via COM.
Environment:
Windows 7 64-bit.
MS Office Professional Plus 2010 Version: 14.0.1753.5000 (64-bit).
Visual Studio 2010 Professional.
TestDLL.dll assembly code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace TestDLL
{
[ComVisible(true)]
[Guid("7CAAEF3F-F867-445B-B078-5837A833620A")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IGreeting
{
string SayHello();
}
[ComVisible(true)]
[ProgId("TestDLL.Greeting")]
[Guid("73D4187A-F71D-4E45-832A-6DD9F88CC59B")]
[ClassInterface(ClassInterfaceType.None)]
public class Greeting : IGreeting
{
[ComVisible(true)]
public string SayHello()
{
return "Hello, World!";
}
}
}
A WinForms project added to the solution successfully calls the SayHello() method.
TestDLL project properties:
Application / Target Framework - .NET Framework 4
Application / Assembly Information / Make assembly COM-visible: false (I only want certain public classes within the assembly to be COM-visible, not ALL public classes. Even though for this demo there's just one class and I do want it to be COM-visible. The code above should have taken care of that.)
Application / Assembly Information / Title and Description and Company and Product are all "TestDLL".
Build / Platform: Active (any CPU)
Build / Platform target: x86
Build / Register for COM interop: false (I don't want it to work on MY computer only but ALL computers. Thus I want to register the assembly when it is INSTALLED, not when it is BUILT.)
Signing / Sign the assembly: false (I want the assembly to live in the install folder, not in the GAC.)
A peek at AssemblyInfo.cs reveals:
[assembly: ComVisible(false)]
[assembly: Guid("6bf701f9-3953-43bb-a8af-1bdf7818af3c")]
The assembly is built.
Then a type library is created using the Visual Studio Command Prompt (run as Administrator) with this command:
tlbexp "C:\(path)\bin\Release\TestDLL.dll" /win32 /out:"C:\(path)\bin\Release\TestDLL.tlb"
A Visual Studio Installer project called SetupTestDLL is added to the solution.
On its File System tab, Application Folder, TestDLL.dll is added. This automatically also adds TestDLL.tlb.
Right-clicking TestDLL.dll in that Application Folder allows opening a properties window.
There, Register: vsdraCOM
When right-clicking TestDLL.tlb in that Application folder to get the properties window:
Register: vsdrfCOM
(I'm guessing that vsdraCOM means register the assembly and vsdrfCOM means register a file for COM.)
One more file is added to the Application folder: TestDLL.pdb.
SetupTestDLL is built.
Browsing to its output folder, reveals setup.exe and setupTestDLL.msi.
Right-click setup.exe and Run as administrator.
A dialog box displays the correct install path and the correct "Install for everyone" option.
The install completes successfully.
In the Control Panel / Programs and Features, TestDLL is now listed. Its publisher is listed as "XYZ". Where did that come from? Evidently from the "Manufacturer" property of the SetupTestDLL project's property window. I created that value only there in the entire solution.
In C:\Program Files (x86) there is now an "XYZ" folder, under which is a TestDLL folder, and in that are the three files.
Launch MS Access. Open an existing database and its existing code module.
From the Access code window toolbar, choose Tools / References.
TestDLL is found in the Available References listbox. Click its check box and click OK.
Click the Object Browser button on the code window toolbar.
is selected in a dropdown list. Change it to TestDLL.
The class "Greeting" is shown with its method "SayHello". So far, so good.
Close the Object Browser.
Create this procedure in the code module and try to run it.
Public Sub Test2()
' Dim o As New TestDLL.Greeting
' The above is early binding. It should also work
' since we set a reference.
Dim o As Variant
Set o = CreateObject("TestDLL.Greeting")
' The above is late binding.
Debug.Print o.SayHello()
Set o = Nothing
End Sub
Result:
Whether early or late bound,
ActiveX Component can't create object.
What's wrong?
I was just going to add a comment, but I don't have enough reputation points so I'll just post this as an answer and remove it if necessary.
I'm not familiar with Visual Studio Installer projects, so I'm not sure if it is registering the assembly correctly. Have you tried using regasm to register TestDLL? Something like:
regasm /codebase TestDLL.dll /tlb:TestDLL.tlb
64-bit MS Office cannot use a 32-bit COM DLL early bound, but with a reg hack involving DLLSurrogate, it can use it late-bound. I got that to work.
I am trying to build a project using opencvsharp v2.4 64x in Visual Studio 2012. The project is built using .net 4.5 and x64 target. I've installed VS08+VS10 sp1 prerequisites. i've included all the opencv dlls (version 2.4.0 64x) in my project to copy to executable directory including TBB.dll (and checked that they actually end up in the correct folder).
However, on running the software i get the error: An exception has occurrred because of P/Invoke. Please check the following ... System.DllNotFoundException: Unable to load DLL 'opencv_core204' ... etc
any ideas why this is happening? maybe VS2012 is not supported to build opencvsharp 2.4?
Seems like i still missed out a few native dll's which code204 was dependent on.
here is the complete list that is required. It now works in visual studio 2012 with 64-bit and .net 4.5 - however a few examples wont run (delaunay) using 4.5 but only in 3.5. no idea why...
OpenCvSharpExtern.dll
OpenCvSharpExternGpu.dll
opencv_calib3d240.dll
opencv_calib3d240d.dll
opencv_contrib240.dll
opencv_contrib240d.dll
opencv_core240.dll
opencv_core240d.dll
opencv_features2d240.dll
opencv_features2d240d.dll
opencv_ffmpeg240.dll
opencv_ffmpeg240_64.dll
opencv_flann240.dll
opencv_flann240d.dll
opencv_gpu240.dll
opencv_gpu240d.dll
opencv_highgui240.dll
opencv_highgui240d.dll
opencv_imgproc240.dll
opencv_imgproc240d.dll
opencv_legacy240.dll
opencv_legacy240d.dll
opencv_ml240.dll
opencv_ml240d.dll
opencv_nonfree240.dll
opencv_nonfree240d.dll
opencv_objdetect240.dll
opencv_objdetect240d.dll
opencv_photo240.dll
opencv_photo240d.dll
opencv_stitching240.dll
opencv_stitching240d.dll
opencv_ts240.dll
opencv_ts240d.dll
opencv_video240.dll
opencv_video240d.dll
opencv_videostab240.dll
opencv_videostab240d.dll
QtCore4.dll
QtGui4.dll
tbb.dll
tbbmalloc.dll
tbbmalloc_debug.dll
tbbmalloc_proxy.dll
tbbmalloc_proxy_debug.dll
tbb_debug.dll
tbb_preview.dll
tbb_preview_debug.dll
When deploying and registering a .Net Excel.dll on another computer, I get an error Can't add a reference to the specified file when attempting to add reference to DLL in VBA editor.
I have created the Excel.dll in C# in Visual Studio that runs fine on my machine with Windows 7 and Office 2010. No problem adding reference to the dll in Excel VBA editor on my computer. My problem is deploying on another machine which is running Vista and Excel 2007. I copied dll to this computer and used regasm to register the dll.
Can anyone point me in the right direction? Here is code and regasm:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe excelDll.dll
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace TestDll
{
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Test
{
public string HelloWorld
{
get
{
return "Hello World";
}
}
public void sayGoodbye1()
{
MessageBox.Show("Say Goodbye");
}
public string sayGoodbye2()
{
return "Say Goodbye";
}
}
}
You need to register the type library for excel to see your dll in References.
i.e. regasm.exe excelDll.dll /tlb:excelDll.tlb
Mark.
I recently encountered and managed to solve exactly this problem - though I can't claim to understand exactly why my solution worked.
Both my systems are running Windows 7 x64. One has Excel 2010, the other Excel 2007.
All the C# assemblies are set to "Platform Target: Any CPU". The primary assembly is set to "Register for COM Interop". The whole thing is installed using an MSI created by a Visual Studio Installer project.
I found that if I set the Visual Studio Installer project "Target Platform" to "x64", then it works in Excel 2010, but not in Excel 2007. Conversely, if I set the Visual Studio Installer project "Target Platform" to "x86", it works in Excel 2007 but not in Excel 2010.
Sadly, I'm not in a position to test both Excel versions on the same machine at the same time - but at least this might get it working for you!
We've created a C# class library assembly and made it COM visible to be able to call its methods from PHP. This used to work fine, but now we wanted to install it on a Windows Server 2008 server and we keep walking into the error "Class not registered".
To rule out any dependency problems I made a tiny little test class library in C#. The class library is built for Any CPU and it is COM visible (also set COMVisible to true in AssemblyInfo.cs). The test class library only contains one class with one method. The class is called TestLib and the namespace is also called TestLib. The method is called Test and only returns a string.
What we have done is the following:
- built the TestLib.dll
- copied it to the Windows Server 2008 machine
- registered the dll with: regasm /codebase TestLib.dll
- the regasm tool returns a success message
- in PHP we simply try to create a new COM instance:
try
{
$test = new COM("TestLib.TestLib");
}
catch (Exception $e)
{
die($e->getMessage());
}
when we call this test script from either the browser or the commandline (php -f test.php) we get the error "Class not registered" in both cases
I also tried adding TestLib to the GAC by using gacutil -i, but to no avail; still the class not registered error.
Then I tried compiling the testlibrary with .NET 2.0 instead of 4.0 as the target framework, same result. The .NET framework 4.0 is installed on the server by the way.
Any ideas?
Okay, so after some more research I figured it out. The php.exe process is 32 bit. The COM visible assembly is compiled for Any CPU so it should be accessible to both 32 and 64 bit applications.
The problem is that on a 64 bit OS php.exe, and any 32 bit process for that matter, searches in HKEY_CLASSES_ROOT\Wow6432Node\CLSID instead of HKEY_CLASSES_ROOT\CLSID and in HKEY_LOCAL_MACHINE\Software\Classes\Wow6432Node\CLSID instead of HKEY_LOCAL_MACHINE\Software\Classes\CLSID. The registry entries in the Wow6432 keys aren't created by regasm that is shipped with .NET framework v4 on Windows Server 2008. On Windows 7 they are created, don't ask me why.
It also turned out that if I create a little test assembly for .NET v2.0 and register it with regasm that ships with .NET framework v2.0 that it does create the Wow6432Node entries on Windows 2008. Strange.
So my solution is to create a basic registry file on the server using:
regasm /regfile MyClassLib.dll
This creates a file MyClassLib.reg with only the 'normal' 64 bit entries. Then I exported the Wow6432Node keys from a Windows 7 machine and added it to that .reg file. Now when I import that reg file into the registry on Windows 2008 everything works fine.
For more info on the Wow6432Node entries check out: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724072%28v=vs.85%29.aspx
Hope this saves someone else some time and headaches.
If you are trying to call a 32-bit COM DLL on 64-bit Windows, you will need to register it.
Copy your 32-bit DLL to C:\Windows\sysWOW64
Run C:\Windows\sysWOW64\regsvr32.exe your_com_32.dll
A bit more info with screenshots.
I have a custom .Net interface written in C++/CLI:
public interface class IBackgroundExtractor
{
};
In my C# application, the interface is used by:
private IBackgroundExtractor extractor = null;
The above code runs smoothly in my computer (which installed visual studio) but it crashed in another one (without installing visual studio):
AppName: speedtest.exe AppVer: 1.0.0.0 ModName: kernel32.dll
ModVer: 5.1.2600.5512 Offset: 00012aeb
If I remove the null assignment, the code will run in both computer:
private IBackgroundExtractor extractor;
However, I made another interface in pure C#. Setting the interface to null will not make the program to crash:
interface IAnotherInterface
{
}
private IAnotherInterface test = null;
What's wrong in my C++/CLI interface?
[Remarks]
I've create two 'clean' new projects for testing, the first one is a C++/CLI Class Library (New Project -> Visual C++ -> CLR -> Class Library). Add the following lines into the .h file of the library:
public interface class ITest {
};
Then create a Windows Form Application project (Net Project -> Visual C# -> Windows -> Windows Forms Application) and declares an ITest variable in the main function:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
ITest xxx = null;
}
The program will run in the development computer (installed visual studio) but crash in two other computers. One of the crashed computers is physical machine and the other one is an virtual machine.
I am using Windows XP Pro SP3, Visual Studio 2010 and .Net Framework 4.0.
Have you checked to make sure the right version of the Microsoft Visual C++ Runtime is installed on the target machines? (Your development environment will already have this installed, but no current version of Windows includes this runtime by default).
If you set up your C++/CLI project to use "SAFE" mode, it will not reference the Microsoft Visual c++ runtime at all (just .NET). See this for reference: Pure and Verifiable Code. If you need to do native stuff, then there's a very high chance that you need to have the latest Visual C++ runtime installed. You can pick up the redistributable here: Microsoft Visual C++ 2010 Redistributable Package.
If you want to verify that this is the problem, you can use the sxstrace tool to diagnose these issues (helpful tutorial).
The null assigment is probably not the error but the trigger for the error. This assigment to null is probably first line of code that accesses that the C++/CLI assembly. So before the that null assigment is even executed, the unmanaged part of C++/CLI assembly is initialized. That is probably where the error is occuring.