My ultimate objective is to build a class library (Calculator.dll) in C#, containing functions that will be made accessible to Excel via VBA.
I had hoped to avoid the need to register the dll, and rather, use the Declare Function statement in the VBA code, but apparently this is not possible and my research has pointed me to needing to make the class library COM-Visible, then register it, and add it as a reference to the VBA project. It seems like this was a matter of clicking a box in a dialog for the project properties in earlier Visual Studio versions, but I don't see the box in VS2022?!
These are the steps I've taken with a "toy" example, and the problems I've encountered.
(1) I built the following .dll with a class Calculate and a simple method to Add two integers. Since we use 32-bit Excel, I configured it to x86.
namespace ClassLibraryCalculator
{
public class Calculate
{
public int Add(int a,int b){ return a + b; }
}
}
(2) From the command prompt, running as administrator, I attempted to run
regsvr32 "ClassLibraryCalculator.dll" but encountered an error "..was loaded but the entry-point DllRegisterServer was not found"
From searching around, the remedy for this is said to be to modify the project properties, the was (in earlier versions of VS?) a dialog box
with a check box, but I see nothing related to COM in Visual Studio 2022 Project Properties.
First make sure you create a project of type "Class library (.NET Framework)" that uses the classic Windows .net Framework like version 4.8. Do not choose the "Class library" project type that is based on .NET Standard and supports multiple different operating systems.
After creating the project, open the project properties by right-clicking on the project in the Solution Explorer and selecting "Properties".
In the project properties, go to "Application", then click on "Assembly Information".
In the "Assenmbly Information" dialog, enable the "Make assembly COM-Visible" checkbox.
For details see Turn a simple C# DLL into a COM interop component
Note that you can use the setting "Register for COM interop" on the "Build" area of the project properties to register the DLL (VS needs to run with admin privileges for that). When trying to register from command line, don't use "regsvr32", but "regasm". Be aware that there is a 32bit and a 64bit version of regasm available. Use the correct one.
Further important hints:
If you run Office as 64bit executables, choose "Platform target = x64" in the Build area in the project properties.
If you run Office as 32bit executables, choose "Platform target = x86" in the Build area in the project properties.
Note that "Any CPU" will not work. If you want to support both 32bit and 64bit instances of Office, you need to build two variants of your dll and register both.
This is the sample class I use for testing:
using System;
using System.Runtime.InteropServices;
namespace ClassLibrary4
{
[Guid("ECB682F0-8AF1-40EA-B73A-1FACF3C7F742")]
public interface IClass1
{
void Test();
}
[Guid("4EC93EC5-FA8C-4E82-8931-E47D979BDA93")]
public class Class1 : IClass1
{
public void Test()
{
System.Console.Beep();
}
}
}
Note that you cannot use these GUIDs but must use your own ones. Use the "Tools > Create GUID" command in Visual Studio for that.
Here is how I use the class from VBA after activating the reference:
' Declare variable with interface type
Dim c As ClassLibrary4.IClass1
' Create from class type
Set c = New ClassLibrary4.Class1
' Use.
c.Test
Related
After going through a number of different articles and not finding anything especially conclusive that takes me step-by-step through the process, I've come seeking help.
The Scenario
A client of mine is only proficient in development for ASP Classic. They have recently acquired an account for a site originally written in ASP.NET. They are rolling the site into something they can actively maintain, but the site originally included an image handler that took dynamically changing data regarding water levels and outputs an image containing a graphical representation of that data. The requirement is to develop a COM interop library that can be registered on the server and called with CreateObject to generate the same image's byte array for output using Response.BinaryWrite. The COM interop library must be registered at the remote site on a Windows 2000 Server, and I can't make any assumptions about their having access to regasm/gacutil to accomplish that task.
The Difficulty
I've built the class library by creating a Class Library project in Visual Studio 2010, choosing "COM Class" from the template, and inserting my code to generate a class with a single public method to return a byte array when given an integer (well, enumerator, but all the same). Unfortunately, even on my own development machine after building the library and registering (regasm) and caching the assembly (gacutil), I can't make a call through Classic ASP to instantiate the object, receiving instead an "ActiveX component can't create object" error. And, of course, at the server site, the DLL file can't be registered, the response being "Required module was not found."
Resources I've Used
I've already had a look through the following articles and haven't turned up the answers I need:
(Basic steps) Walkthrough: Creating COM Objects with Visual Basic
Build and Deploy a .NET COM Assembly
.NET COM+ Interop Component with Classic ASP
What I Need
Essentially what I need is a bit of hand-holding on a kind of step by step of what it's going to take to meet the requirements and create a COM+ interop module correctly in Visual Studio 2010. Creating the actual class object itself isn't terribly difficult.
However, none of the articles I've looked through really discuss project options or build procedures with Visual Studio 2010 or the .NET 4.0 Framework, nor have any of them really discussed if there are special considerations for deploying to older systems like Windows Server 2000 and the actual registration of the library on a system with only, say, regsvr32 on hand.
It should be fairly straightforward to get a basic .NET assembly exposed to COM - I've never tried the COM Class project template, so this is the way I've managed it in the past:
Create a new (bog standard) .NET class library using C# or VB. Define a COM interface (replace GUIDs with your own):
[ComVisible(true)]
[Guid("8999F93E-52F6-4E29-BA64-0ADC22A1FB11")]
public interface IComm
{
string GetMyGroups();
}
Now define a class that implements that interface (again, replace GUIDs with your own):
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[GuidAttribute("C5C5A1A8-9BFB-4CE5-B42C-4E6688F6840B")]
[ProgId("Test.Comm.1")]
public class Comm : IComm
{
public string GetMyGroups()
{
var comm = new CommunicatorAPI.MessengerClass();
var groups = comm.MyGroups as IMessengerGroups;
return string.Join(", ", groups.OfType<IMessengerGroup>().Select(g => g.Name).ToArray());
}
}
The Prog ID attribute on this class is what you will use to instantiate your component from ASP.
Strongly-name the assembly (Project properties -> "Signing" tab -> "Sign the assembly" -> Create a new strong name key file using the dropdown)
Now, build the assembly, and register using Regasm - if you don't wish to register in the GAC (which i'd recommend, as not GACing keeps the deployment simpler), be sure to use the -Codebase parameter (this just adds a reg entry that tells clients where to find the assembly) - e.g:
regasm ClassLibrary2.dll /codebase "S:\Testing\ClassLibrary2\ClassLibrary2\bin\Debug\ClassLibrary2.dll"
Now you should be able to instantiate the component, and call methods on it - for example (in javascript):
var a = new ActiveXObject("Test.Comm.1");
alert(a.GetMyGroups());
When it comes to deployment, the important work that Regasm and Regsvr32 do is to write various settings into the registry, so that clients can find the COM component (based on Prog ID, or COM Class ID). All you need to do is work out what COM settings are being written when you run Regasm on your local machine, and write these to the registry on the server. You can use ProcMon to monitor what gets written to the registry when Regasm is run.
Generally speaking, you can expect to see something like this written to the registry:
[HKEY_CLASSES_ROOT\Test.Comm.1]
#="ClassLibrary2.Comm"
[HKEY_CLASSES_ROOT\Test.Comm.1\CLSID]
#="{00585504-90C8-4760-A359-67CAF08FFED1}"
[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}]
#="ClassLibrary2.Comm"
[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}\Implemented Categories]
[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}]
[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}\InprocServer32]
#="mscoree.dll"
"ThreadingModel"="Both"
"Class"="ClassLibrary2.Comm"
"Assembly"="ClassLibrary2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cf55d4e60653257a"
"RuntimeVersion"="v4.0.30319"
"CodeBase"="file:///S:/Testing/ClassLibrary2/ClassLibrary2/bin/Debug/ClassLibrary2.DLL"
[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}\InprocServer32\1.0.0.0]
"Class"="ClassLibrary2.Comm"
"Assembly"="ClassLibrary2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cf55d4e60653257a"
"RuntimeVersion"="v4.0.30319"
"CodeBase"="file:///S:/Testing/ClassLibrary2/ClassLibrary2/bin/Debug/ClassLibrary2.DLL"
[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}\ProgId]
#="Test.Comm.1"
Hope this helps :)
I have a Classic ASP web site that uses a VB6 COM object. I wanted to create a new version of the COM object using .NET instead of VB6. This is how I did it (hope this helps). I include instructions for both C# and VB.NET.
[01]
Start Visual Studio 2015 (run as admin).
Create a new "Class Library" project.
Name it: "DotNetCom"
[02] C#
Add a new class, name it "HelloCOM".
Use the following code as starting template
( visit https://msdn.microsoft.com/en-us/library/c3fd4a20.aspx for more info )
using System.Runtime.InteropServices;
namespace DotNetCom
{
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
public interface HelloCOMInterface
{
[DispId(1)]
string Hello();
}
[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface HelloCOMEvents
{
}
[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(HelloCOMEvents))]
public class HelloCOM : HelloCOMInterface
{
public string Hello()
{
return "Hello there!";
}
}
}
[02] VB.NET
Add a new "COM class", name it "HelloCOM".
VB.NET creates the starting template.
Add the following function to the "HelloCOM" class.
Public Function Hello() As String
Return "Hello there!"
End Function
[03] C#
Open the project properties.
Go to "Application".
Click "Assembly Information...".
Check "Make assembly COM-Visible"
Go to "Build".
Select "Platform target: x86".
Check "Register COM for interop"
[03] VB.NET
Open "MyProject".
Go to "Compile".
Select "Target CPU: x86".
[04]
Build the "DotNetCom.dll".
[05]
Open a command prompt (run as admin).
Change directory to your dll.
cd DotNetComTest\DotNetComTest\TX7NGN.COM\bin\Debug
Run RegAsm /codebase.
C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm DotNetComTest.dll /codebase "C:\DotNetComTest\DotNetComTest\bin\Debug\DotNetComTest.dll"
[06]
Start Component Services.
Add a new COM+ application.
Name it: "DotNetCom".
Open the "DotNetCom" properties.
Go to the "Security Tab".
UNCHECK "Enforce access checks for this application".
[07]
Add a new component.
Select "DotNetComTest.tlb" (do NOT select "DotNetComTest.dll").
[08]
Use the COM object from the Classic ASP page.
<%
Dim HelloCOM
Set HelloCOM = Server.CreateObject("DotNetCom.HelloCOM")
Response.Write HelloCom.Hello
%>
I'm developing some AutoCAD add-ons in C# and I was hoping to work/debug my classes in a console application first until I'm ready to implement the functionality inside AutoCAD. /dot net libraries cannot be unloaded and one needs to restart/reload acad each time the code is modified/
Interestingly I'm finding that as soon as I declare a variable which uses an autocad data type my console app refuses to run and Im presented with a "the application is in break mode" screen in visual studio. For example this code does not run:
using System;
using Autodesk.AutoCAD.Geometry;
namespace TestConsole
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hi");
Point2d p;
Console.ReadKey();
}
}
}
Trying this referencing Autodesk C3D 2016 dll libraries. Does anyone have an explanation of what is going on here and/or any workarounds?
thnks
The AutoCAD .NET API is designed to run in-process only.
AutoCAD .NET libraries can only be used to build plug-ins (DLL) which have to be loaded in AutoCAD for execution.
To debug your AutoCAD Add-On code, you need to create a C# Class Library Project referencing the Autodesk CAD 2016 Sdk Libraries, and encapsulate your code on public method that declared with CommandMethodAttribute. With this method declared with CommandMethod it's the be your trigger between the AutoCAD prompt command and your Add-On code.
public class AcadCommands
{
[CommandMethod("TriggerCmd")]
public void TriggerCommand
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("Hi");
Point2d p;
}
}
Note: at your Visual Studio Project Properties, you need to configure to start debug pointed to acad.exe and when the AutoCAD applications starts, open some DWG and prompt the "NETLOAD" command to include your debbugable compiled DLL from your VS Project to AutoCAD Application context.
We have an app A that has a reference to an assembly B that contains some static methods. Assembly B is in the same Visual Studio solution as app A.
We want the users of our app A to be able to write plugins. They build the plugin (at present) by creating a new Class Library solution in Visual Studio, getting the Nuget Package for app A, and adding a reference in their plugin solution to assembly B, so that the plugin code compiles.
They do not need to test their plugin, which is why the code just needs to compile, although it would be nice if they could debug their code when running it in app A.
Once their code has compiled, the DLL for the plugin is put in a share and given to a different team (bureaucracy I know) who put it with the rest of app A's DLLs.
I would like the plugin DLL to use the assembly B DLL that is with all of app A's DLLs.
When I run app A, the Activator class picks up the plugin DLL and correctly creates an instance, but as soon as one of the static methods from assembly B is called, the plugin throws a MissingMethodException.
Things I have tried:
The plugin solution definitely works fine if you create it and compile it within application A's Visual Studio solution.
App A uses framework .Net 4.5, assembly B uses framework .Net 4.0, I have tried building the plugin with both frameworks without success.
The "Specific Version" of the reference to assembly B in the plugin solution was false in all cases tested.
I would welcome immediate solutions to this problem but also broader architectural suggestions on how to get these plugins to work. I apologise if there is a duplicate question of this, I couldn't find one.
For starters you can use "Dotpeek" to decompile dll and see if the method defination exactly matches.
It's a free software available to decompile dotnet libraries.
If you don't have access to .pdb file then i would recommend using "dotnet reflector",or "IL Spy" it will decompile without pdb files.
Also do make sure you are referencing project in visual studio not the output dll.
I managed to fix my problem as follows:
Although the plugin only directly used static methods in Assembly B, these static methods actually made a chain of calls to various OTHER assemblies.
Instead of just adding a reference to Assembly B in my plugin, I did a Nuget command:
Install-Package -Id AppA -ProjectName Plugin
And this downloaded the latest AppA to the packages folder and added a reference to EVERY dll of AppA.
Like before, it compiled, but this time when I dragged the plugin dll into the AppA bin folder, the plugin code ran without throwing an exception.
I have project XTime.Sephaku.GenSync.Plugin that translates SAP IDocs to our internal DB structure. It builds fine, and if I put it's classes into a Console app, that app runs fine, so the plugin code is fine.
I then have Console project Tester, with a reference to the output assembly from the plugin, XTime.Sephaku.GenSync.Plugin*. It doesn't see any of the public types in the plugin, despite that assembly being freshly built, and the public types all visible in Object Browser. I have copy and pasted namespaces for using statements to make sure I don't have a typo. The namespaces and public types are also all visible in dotPeek's decompiled code.
It's like something is telling XTime.Sephaku.GenSync.Plugin to not allow other projects to use it's types.
*** See my Why do I get a warning icon when I add a reference to an MEF plugin project? for why I don't reference the project, but rather the assembly.
Converting my comment to an answer:
Your Tester project cannot have a higher version of .NET than the referenced plugin.
You need to make sure that the referencing project have a version of .Net that is equal or higher to the plugin's version.
you can red more about it here
I have a C# console application in Visual Studio 2010. It has a Main() method as well as a bunch of utility classes. I'd like those utility classes to be available to other solutions. From reading online it seems that I need to compile it as a Class Library (DLL). So here's what I did:
Went in Visual Studio to "Project > [ProjectName] Properties > Application" and changed "Output type" from "Console Application" to "Class Library"
Rebuilt; ProjectName.dll was created in bin/Debug.
Created a new Console Application
Solution Explorer > Add Reference > browse to ProjectName.DLL, select it.
However, neither IntelliSense nor the Object Browser could find the classes inside that DLL.
I tried recompiling several different Console Applications as Class Libraries and got the same result. I also noticed that it works if I initially create the solution as a Class Library, but not if I convert it to one later.
Any tips?
You do not need to build it as a dll. VS 2010 (and IIRC 2008) allow referencing exe assemblies. All you need is for they relevant types to be declared public - top-level classes defualt to internal if you don't add a specifier.
You can switch output type to Class library in project properties as well - then you will have an output as dll instead exe file
What I've always done (since this is what you do with C++ static libraries, which is what I normally use - though I think it has some advantages for C# too) is add the class library's project to the solution, then add a reference to it in the project (or projects) that uses it.
When you go to add a reference, the list of potential references includes items from the solution, so it should be fairly obvious what to do. You should then get intellisense for your library.
One advantage of doing things this way is that if you need to edit files in the library project, it's very straightforward because they are close to hand, and the project then gets rebuilt automatically when you compile the solution.
Make sure that the classes in your dll project are public.
At first, from the point of view of managed libraries it does not matter what kind of Output type is your managed library. I mean that you can successfully reference ConsoleApplication1.exe from ConsoleApplication2.exe project (so you have no reason to convert ConsoleApplication1.exe to ConsoleApplication1.dll).
At second, I've tried to reproduce your situation, but... without effect. My VS displays types/methods from ConsoleApplication1.dll. One reason I can suppose is that you have forgotten to set visibility modifier (public keyword) for your utility classes.