COM access to VB.Net dll without strong name signing - vb.net

I'm converting a VB6 dll to VB.Net using Visual Studio 2008 Express. I want to use the same .dll to integrate with Excel via Excel-DNA, but also to be available via COM (I need to be able to call it from VBScript and VBA).
If I leave the assembly unsigned, I have access to all of the ExcelDNA functionality but no COM access.
If I sign the assembly with a strong name, then when I try to build the .dll I get the following error:
Unable to emit assembly: Referenced assembly 'ExcelDna.Integration' does not have a strong name
What are my options?

You don't have to strong-name a [ComVisible] assembly. It is only required when you want to install it in the GAC. Not strictly necessary although not a bad idea to fight DLL Hell. You need to register it with Regasm.exe using the /codebase option. Visual Studio already does that automatically, although the option might be missing in the Express edition.
Fixing the second problem shouldn't be hard either. Just rebuild the Excel-DNA solution from the source code you can download from Codeplex.

Excel-DNA has an option to expose your .NET classes to COM directly, so you can use them directly from VBA as regular COM classes.
To do this your class must be ComVisible (or the whole assembly must be ComVisible), and you must mark the ExternalLibrary as ComServer='true' in the .dna file, something like:
<DnaLibrary RuntimeVersion='v4.0' />
<ExternalLibrary Path='MyAddIn.dll' ComServer='true' />
</DnaLibrary>
Then you have some options for registration:
either call "Regsvr32.exe MyAddInDna.xll"
or in the AutoOpen of your add-in, call ExcelDna.Integration.ComServer.RegisterServer()
Both of these set the registry entries so that the .xll file becomes the COM server for your classes. You can then access them late-bound from VBA, with CreateObject("MyNameSpace.MyClass").
You need another step to set up a type library (to give you intellisense in VBA). For this you generate the type library with tlbexp.exe. The regsvr32 / ComServer.RegisterServer call will find the type library and register it too.
You can also pack the .dll into your .xll file (adding a pack='true' attribute to the ExternalLibrary tag) and the type library will also be packed if present, and registered from the resource in the .xll file when you call Regsvr32.exe
So the end result could be a single file .xll add-in that is both an Excel Add-In with UDFs, ribbons, RTD servers and also a COM server that you can access from VBA.
More info in the discussions here: http://exceldna.codeplex.com/discussions/252721 and here: http://groups.google.com/group/exceldna/browse_frm/thread/4c5a71efbe96d885.

Related

How do I expose a .netstandard2.0 library with COM for use in VB6?

I have a dotnet core library, a framework 4.7.2 library and a vb6 application.
I want to write a common library for them all to access and so choose .netstandard2.0
I tried a the 4.7.2 framework wrapper library between .netstandard2.0 library and vb6.
However I ran into assembly binding problems
Looking at the docs I see
In .NET Core, the process for exposing your .NET objects to COM has been significantly streamlined in comparison to .NET Framework.
However no mention .netstandard2.0
I decided to try following the docs anyway even though my project is using .netstandard2.0
I got up to the instructions on Generating the COM Host in which case the output files ProjectName.dll, ProjectName.deps.json, ProjectName.runtimeconfig.json and ProjectName.comhost.dll should build.
However the ProjectName.comhost.dll and ProjectName.runtimeconfig.json do not create.
I see in this dotnet standard issue that Microsoft plans on having tooling support in "Preview 4"
I am running VS 16.4.5
[Update]
I decided to try making a .net core wrapper library and enabling it for com.
I was able to add my .netstandard to the wrapper library via a nuget package (I build the .netstandard library using azure devops)
When I build my wrapper library the .dll, .deps.json, .pdb, .runtimeconfig.dev.json and .runtimeconfig.json files are created in a bin\Debug\netcoreapp3.1 folder.
However none of the .netstandard library files appear in the bin\debug folder.
I copied the .netstandard library and the .netcore wrapper libraries to the same folder and ran
regsvr32 MyCoreComWrapper.comhost.dll
However no .tlb file is created which I need to be able to use from VB6
I note the following in the docs
Unlike in .NET Framework, there is no support in .NET Core for
generating a COM Type Library (TLB) from a .NET Core assembly. The
guidance is to either manually write an IDL file or a C/C++ header for
the native declarations of the COM interfaces.
I found some information on github but would love a step by step guide to making the .tlb
I thought about using latebinding instead but am unsure of how to use it with a com library.
[Update]
I put a sample project on GitHub including some VB6 files.
With VB6 referencing the .tlb referenced with the framework library.
When I try to run that I get
Could not load file or assembly 'Microsoft.EntityFrameworkCore, Version=3.1.2.0,
Culture=neutral, PublicKeyToken=adb9793829ddae60' or one of its dependencies. The system cannot find the file specified.
So I copied all the files from my framework test project to my vb6 folder, rebuilt and ran.
Then I got the error
Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.1.0.0,
Culture=neutral, PublicKeyToken=adb9793829ddae60' or one of its dependencies. The system cannot find the file specified.
I see the file Microsoft.Extensions.DependencyInjection.dll is present with File version 3.100.220.6706
Regarding the .NET standard, I may be wrong but I think this is not applicable here because the COM interop stuff are at a higher level than the one .NET standard is targeting; we can only talk about either .NET Core or .NET Framework for COM interop.
If you want to generate a type library, you have few options.
By far, the easiest method is just to use .NET Framework. The fact that you are wanting to create a type library negates the advantages of .NET Core already because several COM, especially the "Automation" features are Windows-only. Using framework will be fine at least until .NET Core 5 comes out.
That said, if you have a business reason for using .NET Core but still need COM support, including the type library, then based on this GitHub comment, you should be able to compile your own IDL. Note that requires you to install C++ build tools because the MIDL compiler is not really a standalone thing that you can get without the rest of the C++ build tools.
It is strongly suggested to have had read the documentation on how .NET Core handles COM activation.
Assuming having the C++ build tools is not a barrier for you, the steps would be the following:
1) Create a .idl file that defines all your COM interfaces in the IDL format. That requires some translation between the .NET interface and the COM interface. Here's a partial example of how you'd need to translate between your C# interface and COM interface as defined in IDL:
[
Guid("<some gooey>"),
InterfaceType(ComInterfaceType.InterfaceIsDual)
]
public interface IFoo
{
string Bar { get; }
string Baz(int Fizz);
}
Would be translated into IDL:
[
uuid(<assembly gooey>),
version(1.0)
]
library myFoo
{
[
uuid(<some gooey>),
object,
dual
]
interface IFoo : IDispatch {
[propget] HRESULT Bar([out, retval] BSTR* retVal);
HRESULT Baz([in] long Fizz, [out, retval] BSTR* retVal);
}
}
Once you've defined the .idl file and it is an accurate representation, you can then use MIDL to compile the .idl file into a .tlb file. Usually something like midl foo.idl /tlb: foo.tlb. You should make use of the MIDL language reference to help you write the .idl file. As a quick way to get started, you could copy your C# interfaces to a .NET framework project, use tlbexp, then use oleview (available via Visual Studio Developer Command Prompt) or olewoo to view the resulting IDL file to get you started.
The next step is to then create registry keys so that your CLSID can reference the type library. You will need to have your assembly's GUID handy and it must be used as the library's uuid in the .idl file as well.
Using IFoo interface example, you would need to create the registry similar to below (using .reg format for easy sharing/comprehension and assuming per-user installation, rather than per-machine):
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}]
#="IFoo"
[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}\ProxyStubClsid32]
#="{00020424-0000-0000-C000-000000000046}"
[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}\TypeLib]
#="{assembly gooey}"
"Version"="1.0"
You will also need to create the registry in the CLSID, Interface, TypeLib, and Record as needed. This article provides a good overview of all registry keys but keep in mind it's assuming .NET framework, not .NET Core, so not all keys are applicable, especially under the CLSID branch.
Note that when you run the regsvr32, it will normally create the keys in the CLSID and Interface branches but you will need to add the TypeLib keys under the Interface's branch and also an entry to the TypeLib branch. You also will need to create the ProgId keys, too if you want to support CreateObject functionality.
Initially, you can start with just a .reg file that you can manually update & maintain but if you have several objects, then it becomes desirable to automate this. This can be also managed via the DllRegisterServer call so that when you execute regsvr32, it will take care of registering the keys. On the other hand, you're now polluting your codebase with registration code. Some elect to use installers to do the registry keys write instead.
I hope that helps you get started!
The issue is due to assembly binding resolution that fails when ran from VB6 (IDE or compiled .exe file).
Here are the steps to solve it:
Compile the VB project, for example, let's assume the compiled file is Project1.exe.
Copy all .NET assemblies (including x86 and x64 directories, and languages directory if localized version is important) aside the compiled VB6 file
Now run Project1.exe, you will get an error like this:
The error is clearly a mismatch between the version of your assemblies aside the Project1.exe file and the version of referenced assemblies (not references you've created yourself but reference embedded in these assemblies... ). You don't see that when you start a .NET program because resolution is a very complex process that depends on a lot of parameters (and it's not getting any better with .NET Core, Framework, Standard, nugets, etc.).
To futher check it's a mismatch error, you can also use the Fuslogvw.exe (Assembly Binding Log Viewer) tool from the SDK.
Now we know it's an assembly version mismatch issue, what you can do is create a file named Project1.exe.config aside Project1.exe and add assembly binding redirects to it.
The easiest way to configure it is to redirect all possible versions to the ones present in the directory that contains your program, so in your case (and as of today, as all these can evolve...), it would be something like this, possibly for every assembly you reference directly or indirectly:
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
...
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection.Abstractions" publicKeyToken="adb9793829ddae60" />
<!-- 3.1.2.0 is the version of the assembly you ship -->
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="3.1.2.0" />
</dependentAssembly>
...
</assemblyBinding>
</runtime>
</configuration>
Unfortunately, there are many satellite assemblies, and it's a bit tedious to create all redirects with correct information, so I've created a tool that creates a .config file with the redirects configured automatically for all .NET assemblies in a given directory: https://github.com/smourier/BindingRedirectGenerator.
If you want it to work for the VB6 IDE too, you'll have to use the same procedure in a VB6.exe.config file aside VB6.exe.
A reminder to myself
Use a demo UI to access the original DLL to confirm the call works. ( if you can't get it to work skip to making the App.Config for the unit test project using BindingRedirectGenerator )
Add a unit test in the com visible project to confirm the
call works.
Copy all the dlls created by both projects to the
release folder
For each com visible dll run as Administrator
c:\windows\microsoft.net\framework\v4.0.30319\regasm /verbose /codebase /tlb:MyLibrary.tlb c:\myproject\releasedlls\MyLibrary.dll
Install BindingRedirectGenerator to c:\brg say
At the command prompt change directory to c:\brg
BindingRedirectGenerator c:\myproject\releasedlls App.config
Rename App.config to MyVB6Project.exe.config and copy it to the same folder as MyVB6Project.exe
Remember to set up the files for the vb6.exe folder if you want to run it in the vb6 ide
Put the whole process in a script for future use ( I used a .bat)
Keep an eye on what nuget has put in app.config
Pay attention to the yellow warnings at build time!

Several short questions about COM .net assemblies, regasm, dll, tlb and guids

All question are related to a .net project dll in .net framework 2.0 that exposes itself as COM.
1) If we don't specify any GUIDs in the source code (typelib, classes, interfaces) who is generating the GUIDs? The compiler or regasm?
2) The GUIDs values exists in the dll, in the tlb or in both files?
3) Any developer with the same source code would generate the very same GUIDs independently on the machine where she builds or run regasm?
4) If I run regasm passing existing dll and tlb files, what happens If the dll and the tlb doesn't match? Regasm regenerate the tlb file with uptodate elements and GUIDs? Or it registers the TypeLib with the current tlb file?
5) What is the point of running regasm with dll and tlb parameters set?
Tlb file is part of what you deploy or it is best practice to only deploy the dll and let regasm generate the tlb on the fly?
6) And last question, is tlb really required? What is the point of having a tlb file? Is not all the information already in the registry? What extra info it provides?
7) When unregistering with regasm, what we need to provide? The dll? The Tlb? Both? What happens if dll (or tlb) doesn't match with existing reg entries? If already registered with tlb option but I run regasm unregister with dll only it would delete the TypeLyb entry too?
8) Regarding bitness, regasm will always generate entries under SysWow64 too? The regasm under Framework64 do the same as the one under Framework?
A type library is the exact equivalent of .NET metadata. It is most of all useful to the client programmer, it makes the compiler and the IDE smart about your library. Providing auto-completion and syntax checking so the odds of a mismatch between his code and yours are minimal. The registration step is necessary so your files can be found back. The type library is normally embedded as a resource in the DLL itself, like .NET metadata, but the .NET build model does not make that easy to do. The client compiler uses the type library info to generate the appropriate COM calls. Guids are a big deal because that is what the client compiler needs to use, identifier names play no role. There is a way to use "late binding" using names, the exact equivalent of Reflection in .NET, but that does not involve a type library.
who is generating the GUIDs?
The CLR does. Every .NET interface or class has one, regardless if it is [ComVisible(true)]. Exposed also through the Type.GUID property. If you didn't use the [Guid] attribute on the type then it runs an algorithm to generate the Guid that uses the type declaration as input. Or in other words, if you make any changes to the type then you can be sure that the Guid will have a different value. Which is the basic reason you should never use the [Guid] attribute, unless you have to create an exact drop-in replacement and cannot recompile client code. The TLBID comes from the AssemblyInfo.cs file that was auto-generated when you created the project.
in the dll, in the tlb or in both files?
It only exists in the DLL when you used the [Guid] attribute, but normally it is generated at runtime as explained above. It is always present in the type library, that's how the client compiler knows to create an object of your class and use its interface(s).
would generate the very same GUIDs
Yes, only the type declaration plays a role.
If I run regasm passing existing dll and tlb files
Regasm can only create a type library, as requested with its /tlb option, it cannot take an existing one. It otherwise does the exact same thing as Tlbexp.exe does, use Reflection to enumerate the types in the assembly to find the [ComVisible(true)] ones and generate the matching type library declaration. The extra thing it does is write the registry key for the type library to HKLM/Software/Classes/Typelib. So the client IDE can find it back.
What is the point of running regasm with dll and tlb parameters set?
No real idea with "dll parameter" might mean. As noted above, use /tlb to generate the type library. Whether or not you deploy the type library depends on its usage, if you don't also provide the client code then you should always deploy it so the client programmer can use it. Other usage of the type library is the subject of this post. If you're not sure how the client programmer is going to use your code then always deploy.
Is not all the information already in the registry?
What's in the registry is limited, only enough info to find the type library file back. The description of your interfaces, their method signatures, guids and the CLSID that the factory function needs is in the type library.
When unregistering with regasm, what we need to provide?
Exact same thing as registering it, you only add /unregister. You must also provide /tlb if you used it previously so the TypeLib registry key can be deleted. It can be pretty important to automate this while you are busy developing and testing the library, since the guids are normally auto-generated you can produce a lot of garbage in the registry. As well as ugly head-scratching when you forget to run Regasm. Project > Properties > Build tab, "Register for COM interop" checkbox. But with the downside that you have to run VS elevated so it can write to the registry.
regasm will always generate entries under SysWow64 too?
SysWow64 plays no role, do always avoid deploying to c:\windows. But yes, bitness does matter, the registry is structured so a 64-bit app cannot accidentally create an object in a 32-bit library and die on an ugly exception. And the other way around. A 32-bit client app will read registry keys from HKLM/Software/WOW6432Node, you only get your registry keys there is you used the 32-bit version of Regasm. Notable perhaps is that it is usually fine to run both flavors of Regasm, given that C# code can run on any platform.

How do I properly register the Type Library of A VB.NET COM+ Component?

I am looking to upgrade legacy VB6 COM+ components to VB.NET components. I have seemingly upgraded one already, called EventPackage, which has one class, IEventListener. Another, TradeOrders, Implements EventPackage.IEventListener. When attempting to build TradeOrders, I get the following Errors/Warnings;
Cannot load type library for reference "EventPackage". Library not registered. (Exception from HRESULT: 0x8002801D (TYPE_E_LIBNOTREGISTERED))
The referenced component 'EventPackage' could not be found.
Type 'EventPackage.IEventListener' is not defined.
In the .vbproj, I notice this reference
<COMReference Include="EventPackage">
<Guid>{0D76C094-21A6-4E04-802B-6E539F7102D7}</Guid>
<Lcid>0</Lcid>
<VersionMajor>2</VersionMajor>
<VersionMinor>0</VersionMinor>
<WrapperTool>tlbimp</WrapperTool>
</COMReference>
When I search the registry for this Guid, I find nothing. When using GUIDs for similar COM+ objects, I find them in HKEY_CLASSES_ROOT\CLSID\{...}\TypeLib ("..." being the GUID of the other component). When I go to the registry key name corresponding to EventPackage.IEventListener, I find that there is no \TypeLib subkey. As you might suspect, searching the reg for "0D76C094-21A6-4E04-802B-6E539F7102D7" yields no results.
So I know this must be a registry problem, But I have tried seemingly every google result I have found. I have tried Regasm and regsvcs .exe's to no avail. Many pages just tell me that dragging the dll to the COM+ manager should automatically register the component.
So how do I register the Type library?
Details on how I made EventPackage COM+ component
Ran the VB6->VB.NET wizard
Then I added some lines to the assemblyinfo.vb file
added Imports System.EnterpriseServices
added Imports System.EnterpriseServices
Imports System.Data.SqlClient
<Assembly: CLSCompliant(True)>
<Assembly: AssemblyKeyFileAttribute("...")> for a strong name
<Assembly: Guid("...")> (Where "..." is the COM+ CLSID of the old component)
I added the following to the class file IEventListener.VB
Imports System.EnterpriseServices
<ComClass("...")> _ (Where ... is the proper COM+ CLSID, that is the only argument)
Inherits ServicedComponent
changed the ID made by the Conversion wizard to the proper value (from <System.Runtime.InteropServices.ProgId("IEventListener_NET.IEventListener)> to <System.Runtime.InteropServices.ProgId("EventPackage.IEventListener")> _
Then I dragged the DLL into the COM+ manager in the proper COM+ application (although, the "Path" is not specified and only says mscoree.dll)
I had that dam error (0x8002801D (TYPE_E_LIBNOTREGISTERED)) yesterday, it drove me crazy: VSTO Add-ins, COMAddIns and RequestComAddInAutomationService
It might be a red-herring but my answer has similar details about reg keys not existing and etc:
Right click on Visual Studio (2010) > Run As Administrator > Open Project > Compile!
The console command "regsvr32 mydll.dll" will register your COM component. You should be able then to find the guid under HKEY_CLASSES_ROOT\CLASSID then, under which the InprocServer32 folder will have the path to your dll. This is how COM looks up the dll.
I think that the problem here is that you haven't generated a type library for your .NET component. I know you said you used regasm - but did you use the right command line?
Start Menu => Programs => Microsoft .NET Framework SDK vX.Y => SDK Command Prompt.
In this command line line, type: regasm /tlb:Mydll.dll

Using tlbexp.exe on a COM dll

I am trying to set up communication between Centura and a COM .dll. (Downloaded from http://download.resip.fr for a database import)
Centura requires a .tlb file to be able to communicate to this component. Normally I would use regasm /tlb to generate the .tlb but seeing as it is a COM .dll this is not possible.
I found that I could use tlbexp for .NET dll's: http://msdn.microsoft.com/en-us/library/hfzzah2c(v=vs.80).aspx
I tried this out, knowing it would probably fail (as I have COM .dll). I received this error:
TlbExp : error TX0000 : Could not load file or assembly 'file:///C:\Windows\system32\ResipBcb.dll' or one of its dependencies. The module was expected to contain an assembly manifest.
Using Dependency Walker I noticed two .dll's missing. I found ieshims.dll online but I can't find the wer.dll. See this question.
I don't know if the tlbexp failure has anything to do with this file that is missing?
To sum up, my question is:
How do I get a .tlb from this .dll? I can't seem to find any way to extract the .tlb out of this COM .dll.
Best regards
Clint Cambier
What you are trying to do only works for .NET assemblies, not native COM servers. The type library for them is almost always embedded inside the DLL. In Visual Studio, use File + Open + File and select the DLL. Open the "TYPELIB" node, right-click the resource (usually 1), Export. Save it to, say, a project directory, use the .tlb filename extension.
TblExp and regasm are only valid on .NET assemblies, what you have is most likely a standard non .NET COM DLL. So neither of those two commands are valid on this DLL.
Standard COM objects are registered using regsvr32. Try running that against your DLL and see if it registers correctly. If it does you should see it listed in Centura's ActiveX explorer.

VB.Net plugin using Matlab COM Automation Server...Error: 'Could not load Interop.MLApp'

My Problem: I am using Matlab COM Automation Server to call and execute matlab .m files from a VB.Net plugin for a CAD program called Rhino 3D. The code works flawlessly when set up as a simple Windows Application in Visual Studio, but when I insert it (and make the requisite reference) into my .Net plugin and test it in the CAD program I get the following error:
"Could not load file or assembly 'Interop.MLApp, Version 1.0.0.0,
culture=neutral, PublicKeyToken=null' or one of its dependencies.
the system cannot find the file specified."
What I've Tried: I am baffled as to why this occurs, but I was able to contact the CAD program's technical support staff and they suggested that it has something to do with their DotNet SDK having trouble with references that are located far outside the CAD program directory. They didn't have any solutions so I tried playing around with copylocal and this made no difference. I tried using other COM libraries and the Open Office automation server works fine, although uses url's instead of requiring a reference. I also tested Excel, which does require a reference, and it returned the error: "retrieving the COM class factory for component with CLSID {...} failed due to the following error: 80040154." This may or may not be related to the issue with the Matlab COM reference, but I thought was worthwhile to share. Perhaps is there another way to reference Interop.MLApp?
I would appreciate any suggestions or thoughts on how I might make the Matlab Interop.MLApp reference work.
Best regards,
Ben
Try moving the assembly file(s) for MLApp into the bin directory. Based on everything I've read, this seems to be a glitch of some sort during the generation of the assembly binding where all the assemblies are merged together and their locations are assumed. I've included some links where I gleaned a bit of information about it.
http://blogs.msdn.com/isha/archive/2009/04/04/issues-with-wcf-service-when-the-asp-net-website-is-deployed-using-the-web-deployment-project-and-as-non-updatable-project.aspx
ttp://social.msdn.microsoft.com/Forums/en/netfxremoting/thread/30df57a8-2a57-4f9f-a120-30c24bc11681
ttp://social.msdn.microsoft.com/Forums/en/netfxremoting/thread/30df57a8-2a57-4f9f-a120-30c24bc11681
ttp://www.sitefinity.com/support/forums/sitefinity-3-x/bugs-issues/cannot-load-file-or-assembly-error.aspx
ttp://forums.asp.net/t/986130.aspx?PageIndex=8
ttp://stackoverflow.com/questions/408002/could-not-load-file-or-assembly-xxxx-or-one-of-its-dependencies-the-system-can