I have a .NET 4.0 class that uses Assembly.LoadFrom to load a .NET 3.5 mixed mode assembly. When this class is called from a .NET 4.0 app (with useLegacyV2RuntimeActivationPolicy="true" specified in the config file) it all works fine.
However, this class is also COM visible, and when I then call it from Excel 2007 (again, Excel.exe.config specifies useLegacyV2RuntimeActivationPolicy="true") I get a FileLoadException:
Could not load file or assembly 'Foo...' or one of its dependencies. Failed to load the runtime. (Exception from HRESULT: 0x80131700)
The inner exception is a System.Runtime.InteropServices.COMException: Failed to load the runtime. (Exception from HRESULT: 0x80131700)
I do not get this problem when calling from Excel 2003 or Excel 2010 or vbscript. Can someone explain what is going on and how I can fix it?
I've discovered from Microsoft that this is a known issue with Excel 2007. The problem has been resolved in Excel 2010, and the issue will not be fixed in Excel 2007.
The workaround is to force Excel to use a COM-exposed .NET 2.0 type before using the .NET 4.0 class. I did this by adding a reference to the .NET 2.0 version of mscorlib.tlb, and then adding the following VBA code before calling the .NET 4.0 class:
Dim o as mscorlib.Object
Set o = New mscorlib.Object
Hope this proves useful to anyone who stumbles across this obscure issue!
Related
I have a VB6 DLL that's been working fine for many years and many versions. It is used from an Excel add-in.
To make it compatible with 64-bit Excel, I want to convert it to a VB6 ActiveX EXE. I did so, but now when I try to instantiate it from the client (same version of Excel; 2016), I get an error. This is the error the client returns:
Automation error
The specified module could not be found.
My understanding is that the ActiveX EXE should have been automatically registered when it was compiled, but I also tried registering it by double-clicking it to run it, and also by manually registering it with regserver. But I still get the same error.
In the client, here's the line I run to instantiate the VB6 ActiveX EXE; this is exactly the same as how it successfully instantiated it when it was a VB6 DLL:
Dim abc As Object
Set abc = CreateObject("ABC.cls_abc")
The class module's Instancing is set to MultiUse, and Persistable is set to NotPersistable.
I searched regedit for ABC.exe, and it was there.
Any suggestions?
UPDATE:
I tried changing the ActiveX EXE's project name and class name, and it worked.
I changed the project name from ABC to ABC64, and I changed the class name from cls_abc to cls_abc64.
Then from Excel I instantiated it like this:
Set abc = CreateObject("ABC64.cls_abc64")
I tried changing it back to ABC and cls_abc, and changing it back to an ActiveX DLL. I had deleted the DLL, so I compiled it again. Then I tried instantiating that from Excel with Set abc = CreateObject("ABC.cls_abc"), and that worked fine.
So the project works as an ActiveX EXE if I change the project name and class name, and it works as an ActiveX DLL with the old project name and class name, but doesn't work as an ActiveX EXE with the old project name and class name (I haven't tested changing only one or the other). Does this suggest I have a problem in the registry?
My aim is to create a COM Visible Type that can be imported into VBA (Excel) and consumed there.
The Excel Object Browser can see my class but not any public methods of the class and fails on the attempt at calling a public method.
My F# Code:
namespace DotNetLibrary
type public Class1() =
member public this.DotNetMethod (x:string) = "Hello" + x
In the AssemblyInfo.fs I also amend to [<assembly: ComVisible(true)>]
I run regasm with switches of /codebase /tlb and a .tlb file is generated.
VBA finds my library in the references browser but does not pick up the DotNetMethod defined on Class1 above.
I have tried to follow the C# guidance for this topic in getting to where I am at the moment but I'm not getting to the finish line.
Run-Time Errors
The reason that VBA could not run the method succesfully is because I used the 32-bit version of the regasm utility instead of the 64-bit version to register the type library for COM. Given that I am using 64-bit Excel it needed 64-bit COM type libraries.
Compile-Time Issues
The reason that VBA could not identify the methods in the IDE could be related to the above, but is also likely to be due to the interface issues raised by
#HansPassant in his comments above.
I have installed DocumentFormat.OpenXml (ver 2.5.5631.0) from nugetpackage manager in VS2010. I can see the reference to the DLL. I have created a package folder. But when I am trying to execute method:
public override void Render(LocalReport report)
{
this.SetUpParameters(report);
byte[] renderedReport = report.Render("EXCELOPENXML");
this.target.UpdateRenderedReportContent(renderedReport);
}
I am getting this error:
Specified argument was out of the range of valid values. Parameter name: format
When I am using
report.Render("EXCEL");
I am not getting any error. I have to use EXCELOPENXML to generate xlsx excel.
What version of Microsoft.ReportViewer.WinForms are you referencing? If it's less than 11.0, that's probably your problem.
Incidentally, Visual Studio (2013) declined to show me version 11.0 as an option from the Add Reference dialog. I don't understand why. I had to browse to the the assembly in the GAC:
C:\Windows\assembly\GAC_MSIL\Microsoft.ReportViewer.WinForms\11.0.0.0__89845dcd8080cc91\Microsoft.ReportViewer.WinForms.DLL
Once you do that, run the following method. You should see EXCELOPENXML as an option now.
new LocalReport().ListRenderingExtensions();
How do you force Excel (2007) VBA to release references to a COM server object?
I have written an inprocess (Single instance DLL) COM Server in Visual Foxpro 9 SP2 which is instantiated from Excel 2007 VBA code on my development machine. Excel seems to be holding a reference to the COM object/dll even though I set it = Nothing. This prevents me from rebuilding the DLL due to a "File access is denied TestCOM.dll" message until I quit Excel which is a pain everytime I want to make a change and test it.
I have boiled the code down to a very simple test setup:
The VFP9 project (TestCOM) has just one .prg file with the following contents
DEFINE CLASS TestClass As Session OLEPUBLIC
ENDDEFINE
The VBA Code is as follows:
Sub Test()
Set objTest = CreateObject("TestCOM.TestClass")
Set objTest = Nothing
End Sub
I have tried removing the reference to the COM server library in the VBA project but this does not make any difference.
I have tried with and without DIMing the object variables and it makes no difference.
I have tried creating a new VFP DLL project but the problem persists.
If I build the VFP application/dll as an INPROCESS/DLL and run the VBA code I get this problem but if I build it as an OUTOFPROCESS/EXE and run the VBA code I do NOT get this problem.
I found a very similar problem in COM Object Cleanup except that my COM Server is written in Visual Foxpro 9 SP2 whereas that relates to C# and the OP has not explained in detail how they resolved the problem so I don't know how to get around it; if that is even possible.
The procedure used to instantiate your COM class from the code in your DLL is that Excel calls the COM library layer to find your implementation using either the ProgID or the ClassID. When you have an inproc server this means it finds a path to your DLL and uses LoadLibrary to load it into your client process and then creates the class factory and calls methods in the DLL. So the end result is that Excel calls LoadLibrary on your DLL and this locks the file until Excel calls FreeLibrary on the handle.
Using COM interfaces you don't have control of this. You call CoCreateInstance() (or from VBA you create the object with New or CreateObject which calls this Win32 API underneath). The implementation of this handles the LoadLibrary and everything else until you get handed an interface pointer to work with. Some applications will periodically call CoFreeUnusedLibraries() to attempt to release loaded COM dlls that are not currently in use. The default class factory implementation maintains a counter of objects created that can be used to determine if a DLL is in use or not - but this is not always reliable as COM class writers may not obey the rules. Exiting Excel obviously releases the lock on the file.
When you create your COM class as an out-of-process server - it lives in a separate executable or DLL whose lifetime is managed differently. Excel no longer holds a lock on the DLL and releasing the COM instance may well allow the hosting process to exit.
You can convert a DLL to be used as a localserver (out-of-process) by arranging to have it hosted by DllHost. If you use the OleView utility and find your classes ProgId then you can enable hosting in a surrogate process (dllhost). It's been a while since I did that but there should be information on the web about using surrogate hosting. Obviously hosting a COM object out-of-process makes everything slower and introduces the potential for various marshalling issues. Provided you keep oleautomation compatible interfaces it should be fine.
Adding a shorter answer ....
Releasing the DLL is knotty problem and one familiar to developers of in process COM components for use in Excel.
There are two conditions that need to be satisfied
1) Do not use an early binding library references (Tools->Reference) , use late binding instead. Early binding tools reference will hold a lock.
2) Call CoFreeUnusedLibraries to unload COM servers which no longer have clients.
From your sample code you are already late binding but please check your references. Whilst point 2) is referred to in payyhoyts answer no code is given.
Here is a copy and pasteable declaration
Private Declare Sub CoFreeUnusedLibraries Lib "ole32.dll" ()
I saved my VB-Express code as .dll and registered it with regasm and made a .tlb file.
But when I try to run a function from it in an Excel-modul I get: Run-time error ‘453’: Can’t find DLL entry point RegisterServiceProcess in kernel32
What step did I miss?
See http://richnewman.wordpress.com/2007/04/15/a-beginner’s-guide-to-calling-a-net-library-from-excel/
or better still try out ExcelDNA ( http://groups.google.com/group/ExcelDna )
I think you're creating a .Net dll and trying to call it from a COM-oriented environment (VBA), which isn't going to work without help. If I'm guessing right, then you need to investigate the COM Interop elements of .Net: Google throws up lots of promising-looking links, one of which is this article.
It looks a bit unpleasant, but I expect the nastiness can be tucked away somewhere...
Try this Microsoft Knowledge Base article: Can't Run Macro That Calls 16-bit DLL in 32-bit MS Excel.
Do you have the proper rights to access the DLL?
Thanks for the input to everybody, you helped me a big step further.
After following the guides you provided I got: Run-time error: '-2147024894' (80070002)': File or assembly name AssemblyName, or one of its dependencies, was not found.
But I could fix that with this Workaround.