Unit testing Excel addin using VSTO - c#

I've been trying to test my addin without success, and even after looking at various sources like MSDN and StackOverflow
I can't seem to be able to do it.
Here's my current code (I trimmed to only have the relevant parts)
AssemblyInfo.cs
[assembly: ComVisible(true)]
[assembly: InternalsVisibleTo("MyAddInTest")]
ThisAddIn.cs
private AddinHelper _comAddinObject;
protected override object RequestComAddInAutomationService()
{
return _comAddinObject ?? (_comAddinObject = new AddinHelper());
}
AddinHelper.cs
[ComVisible(true)]
[Guid("B523844E-1A41-4118-A0F0-FDFA7BCD77C9")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IAddinHelper
{
string GetString();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("A523844E-1A41-4118-A0F0-FDFA7BCD77C9")]
[ComSourceInterfaces(typeof(IAddinHelper))]
public class AddinHelper : StandardOleMarshalObject, IAddinHelper
{
public string GetString()
{
return Globals.ThisAddIn.Application.ActiveWorkbook.Name;
}
}
ThisAddInTest.cs
Application = new Application();
Workbook = Application.Workbooks.Add();
COMAddIns comAddins = Application.COMAddIns;
COMAddIn comAddin = comAddins.Item("MyAddIn");
IAddinHelper comObject = (IAddinHelper) comAddin.Object;
The main project references the .NET Assembly version of Microsoft.Office.Interop.Excel with Embed Interop Types set to true.
My test project references my VSTO project, and the COM (ActiveX) version of the Interop.
The exception I get is:
Unable to cast COM object of type 'System.__ComObject' to interface type 'MyAddIn.IAddinHelper'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{B523844E-1A41-4118-A0F0-FDFA7BCD77C9}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
I currently use Office 2013 (so Office 15.0) and the references are all pointing to that version, .NET 4.5.3 and Visual Stuio Community 2017. The SO link that I provided seems to be the exact same issue, however I still have the exception when running my tests unlike there.

Related

Shallow understanding of COM interop leads to TYPE_E_CANTLOADLIBRARY exception - where's the mistake?

We have a .Net assembly, A, that makes use of a class Foo from a COM library, B.
We've created an interop for B (Interop.b.dll) and A makes use of Foo via the interop.
If I decompile Interop.b.dll I can see the following interface defined within it:
using System.Runtime.InteropServices;
namespace Interop.b
{
[Guid("SOME-GUID")]
[CoClass(typeof (FooClass))]
[ComImport]
public interface Foo : _Foo
{
}
}
In the references settings of .Net assembly A, I have the option to embed the interop types for Interop.b.dll. If I set this to true the interfaces defined within Interop.b.dll are embedded within A.dll. If I then decompile A.dll I can see the same interface as I found in the interop:
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Interop.b
{
[CompilerGenerated]
[Guid("SOME-GUID")]
[CoClass(typeof (object))]
[TypeIdentifier]
[ComImport]
public interface Foo : _Foo
{
}
}
Except that it's not the same. In this version we have [TypeIdentifier] as an additional attribute and the parameter of the CoClass attribute has changed from typeof(FooClass) to typeof(object).
I note that Interop.b.dll contains the type FooClass, which I believe is a wrapper class for the COM type Foo that is responsible for marshalling parameter types between .Net and COM, but this type has not been embedded in A.dll.
Within A.dll, Foo is used like this:
using Interop.b;
namespace My.Product
{
public class AClass: IAClass
{
private Foo LocalFoo { get; }
public AClass()
{
LocalFoo = new Foo();
}
}
}
On a clean install, this fails with the following exception:
System.InvalidCastException: Unable to cast COM object of type 'System.__ComObject' to interface type 'Interop.b.Foo'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{SOME-GUID}' failed due to the following error: Error loading type library/DLL. (Exception from HRESULT: 0x80029C4A (TYPE_E_CANTLOADLIBRARY)).
(Interop.b.dll has been installed to the GAC by the product installer)
On a development machine, reinstalling the product and running from installed binaries fails in the same way. Subsequently recompiling the product, which overwrites the installed binaries, and running from either code (in debug) or the fresh binaries then works.
Now, I believe the error is that the code in A.dll should be instantiating FooClass rather than Foo. i.e. something like this:
LocalFoo = new FooClass();
...because Foo is an interface (in the interop) and FooClass is the thing that handles the marshalling of the parameter types between .Net and COM.
So, questions:
1) Am I correct that the code should be instantiating FooClass rather than Foo?
2) Why does it work at all on a dev machine?
3) Why is the embedded interface using typeof(object) instead of typeof(FooClass)?
4) What is the benefit of embedding the interfaces from the interop in A.dll when we will still need Interop.b.dll on the target machine to allow us to make use of FooClass?

VSTO Unit Testing Office AddIn in C# .NET via RequestComAddInAutomationService

I have worked and read through various StackOverflow questions and other tutorials and documentation over the past weeks (N.B. some of them below) to try and find a way of unit testing a VSTO AddIn.
Unfortunately, it always results in an E_NOINTERFACE exception in my tests.
The code I am using is below - one extract of the ThisAddin partial class overriding the RequestComAddinAutomationService, another describing the test utility interface, the test itself, as well as an additional assembly extract proving that the AddIn assembly and its internals are visible to the test.
My question is: why is this not working? I am pretty sure that this follows the generally accepted practices of VSTO testing.
If the below is not possible anymore, how should one go about testing VSTO? Is .NET remoting/IPC the only solution?
ThisAddin.cs
public partial class ThisAddin
{
#region Testing Utilities
private AddinHelper _comAddinObject;
protected override object RequestComAddInAutomationService()
{
// This is being called when I debug/run my project, but not when I run the tests
return _comAddinObject ?? (_comAddinObject = new AddinHelper());
}
#endregion
}
#region Testing Utilities
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IAddinHelper
{
Presentation GetPresentation();
string GetString();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IAddinHelper))]
public class AddinHelper : StandardOleMarshalObject, IAddinHelper
{
public Presentation GetPresentation()
{
return Globals.ThisAddin... [...];
}
public string GetString()
{
return "Hello World!";
}
}
#endregion
AssemblyInfo.cs
[assembly: InternalsVisibleTo("MyProject.Tests")]
MyUnitTest.cs (has a reference to MyProject)
[TestClass]
public class BasicTest
{
[TestMethod]
public void TestInstantiates()
{
var application = new Application();
var doc = application.Presentations.Open(#"C:\Presentation.pptx",
MsoTriState.msoFalse, MsoTriState.msoFalse, MsoTriState.msoFalse);
var comAddins = application.COMAddIns;
var comAddin = comAddins.Item("MyProject"); // Returns okay
var comObject = (IAddinHelper) comAddin.Object; // Exception occurs
Assert.AreEqual(true, true); // Never reached
doc.Close();
}
}
Furthermore, the project's settings are set to "Register for COM interop" and Visual Studio runs elevated without errors - running it as non-admin results in the DLLs not being registered; therefore, I also know that the COM objects are registered.
Resulting Exception
An exception of type 'System.InvalidCastException' occurred in MyProject.Tests.dll but was not handled in user code
Additional information: Unable to cast COM object of type 'System.__ComObject' to interface type 'MyProject.IAddinHelper'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{59977378-CC79-3B27-9093-82CD7A05CF74}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
StackOverflow
How to call VSTO class from other c# project
Unit Testing VSTO projects
VSTO Add-ins, COMAddIns and RequestComAddInAutomationService (Register for COM Interop doesn't change anything)
Why cannot I cast my COM object to the interface it implements in C#? (the issue is likely not STAThread related)
https://sqa.stackexchange.com/questions/2545/how-do-i-unit-test-word-addin-written-in-c
Microsoft
https://blogs.msdn.microsoft.com/varsha/2010/08/17/writing-automated-test-cases-for-vsto-application/ <-- Even with this supposedly working sample project, I am getting the exact same error.
https://msdn.microsoft.com/en-us/library/bb608621.aspx
General workflow background: https://msdn.microsoft.com/en-us/library/bb386298.aspx
I honestly don't know how one is supposed to test VSTO Add-Ins without this working.
Solution
The Project with methods to be Tested has to use the PIA Microsoft.Office.Interop.PowerPoint via the .Net reference tab.
In the Unit Test Project you have to use the Microsoft Powerpoint 1X.0 Object Library via the COM reference tab - its an ActiveX.
The confusing thing is in Solution Explorer they are both called: Microsoft.Office.Interop.Powerpoint
When you specify the correct References you'll see this error on the line where the exception occurs:
Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'
To solve that simply add a .Net reference to the Microsoft.CSharp.dll in the Unit Test project.
Next we will run into the error you're seeing.
Firstly add a unique GUID to the Interface & class (this will overcome the error):
[ComVisible(true)]
[Guid("B523844E-1A41-4118-A0F0-FDFA7BCD77C9")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IAddinHelper
{
string GetPresentation();
string GetString();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("A523844E-1A41-4118-A0F0-FDFA7BCD77C9")]
[ComSourceInterfaces(typeof(IAddinHelper))]
public class AddinHelper : StandardOleMarshalObject, IAddinHelper
Secondly temporarily make the private AddinHelper _comAddinObject; public in Scope (you can do your [assembly: InternalsVisibleTo("MyProject.Tests")] later when its working).
Thirdly, check that Powerpoint has not disabled the COM add-in. This sometimes happens silently, without the Office App complaining.
Lastly, make sure Register for COM is ticked.
Viola:
Ref: I pulled my hair out working this out years ago helping this fellow SO'r: Moq & Interop Types: works in VS2012, fails in VS2010?

I need to create a C# based COM Server for consumption in VBScript

I am creating a COM server .dll in C# (.NET 3.0 - external requirement). Some clients work well with it but others do not. Specifically, I'm having problems getting VBScript to like it.
There are two primary components:
a. an interface
b. an implementation object
they both have their own GUID's. The implementation class EXPLICITLY implements the interface.
The 'Assembly Information' dialog check box 'Make assembly COM-Visible' is checked.
The 'Build' dialog check box 'Register for COM interop' is also checked.
I also signed the assembly, created a Strong Name Key.
In desperation, I added the System.EnterpriseServices.ServicedComponent assembly:
[Guid( "0135bc5c-b248-444c-94b9-b0b4577f5a1b" )]
public class TwoPays : ServicedComponent, ITwoPays
{
void ITwoPays.TestConnect()
{
...
[Guid( "0135bc5c-b248-444c-94b9-b0b4577f5a1a" )]
public interface ITwoPays
{
I also ran regsvcs.exe.
RegSvcs.exe LandCORC.dll
Microsoft (R) .NET Framework Services Installation Utility Version 2.0.50727.5483
Copyright (c) Microsoft Corporation. All rights reserved.
Which failed with:
WARNING: The assembly does not declare an ApplicationAccessControl Attribute. Application security will be enabled by default.
Installed Assembly:
Assembly: C:\Source\LandCORC\bin\Release\LandCORC.dll
Application: LandcORC
TypeLib: C:\Source\LandCORC\bin\Release\LandCORC.tlb
Since I also don't know anything about Application security, I'm not sure if the warning message from RegSvcs.exe is important.
So I've been using VbsEdit to test it. Before I added the ServicedComponent and ran RegSvcs.exe, it would die on the Set objXL = CreateObject("LandCORC.TwoPays") statement.
Now that completes but when I try to call TestConnect it says that objXL is an invalid pointer, for example:
Dim objXL
Set objXL = CreateObject("LandCORC.TwoPays")
objXL.TestConnect
Adding the reference for VbsEdit:
Prog ID: LandCORC.TowPays
TypeLib path: C:\Source\LandCORC\bin\Release\LandCORC.tlb
GUID: {0135BC5C-B248-444C-94B9-B0B4577F5A1B}
But if I change the VbsEdit reference to use the interface GUID instead, then the auto complete for objXL will show the correct list of methods but then when it executes that I get 'Object doesn't support this property or method: TestConnect' instead.
I don't know what I need to complete the puzzle.
NEW INFORMATION
Based on Kev's answer, I have corrected the interface and class definitions.
[Guid( "0135bc5c-b248-444c-94b9-b0b4577f5a1a" )]
[InterfaceType( ComInterfaceType.InterfaceIsDual )]
[ComVisible( true )]
public interface ITwoPays
{
[DispId( 1 )]
void TestConnect();
[DispId( 2 )]
void Terminate();
[Guid( "0135bc5c-b248-444c-94b9-b0b4577f5a1b" )]
[ClassInterface( ClassInterfaceType.None )]
[ComVisible( true )]
public class TwoPays : ITwoPays
{
[ComVisible( true )]
public void TestConnect()
{
LogInformationMessage( "TestConnect." );
}
[ComVisible( true )]
public void Terminate()
{
And running RegAsm /codebase /register /tlb comes back clean as does running the gacutil -i which states Assembly successfully added to the cache
This is from a project where I had a similar requirement, an InProc COM wrapped .NET component visible to VBScript and Classic ASP (as in IDispatch friendly in COM parlance).
I've renamed the interface, class and methods but you should get the idea.
Declare our interface:
[Guid("0B201484-E388-45eb-9AB8-A6AE1E197AAB")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[ComVisible(true)]
public interface IThingy
{
[DispId(1)]
int DoThing1(string name, int age);
[DispId(2)]
string DoThing2(int id);
[DispId(3)]
bool DoThing3(string address);
}
Implementation:
[Guid("68FAB6AC-9923-425a-85F2-59B50552A5C1")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Thingy: IThingy
{
[ComVisible(true)]
public int DoThing1(string name, int age)
{
// ....
}
[ComVisible(true)]
public string DoThing2(int id)
{
// ....
}
[ComVisible(true)]
public bool DoThing3(string address)
{
// ....
}
}
I also had to generate an assembly key file, this is because COM wrapped .NET assemblies need to be "strongly named" (I vaguely remember - it's been ten years since I wrote this code).
Next I regasm'd the assembly which does all the fun COM wiring in the registry:
regasm.exe COMExample.Fun.dll /register /codebase /tlb
And then chucked it in the GAC:
gacutil.exe -i COMExample.Fun.dll
To uninstall:
gacutil.exe -u COMExample.Fun.dll,Version=1.0.0.0
regasm.exe COMExample.Fun.dll /unregister /tlb
Get rid of the System.EnterpriseServices and ServicedComponent stuff. You only need that if you're going run up an out of process server in COM+ and need the COM+ transactional infrastructure etc.

Interop type could not be embedded

I have a dll from an external vendor; it contains 2 classes:
namespace RDTiffDataAccess
{
public class RDTiffDataClass : IRDTiffData, RDTiffData
{
public RDTiffDataClass();
}
}
and
namespace RDTiffDataAccess
{
public interface IRDTiffData
{
// some code
}
}
I try to call the constructor:
RDTiffDataAccess.IRDTiffData Rdt1 = new RDTiffDataAccess.RDTiffDataClass();
It gives me an error:
Interop type 'RDTiffDataAccess.RDTiffDataClass' cannot be embedded. Use the applicable interface instead.
I have searched and tried a possible solution by changing Embed Interop Types = false.
Now it compiles ok, but at run-time, an exception is thrown:
Retrieving the COM class factory for component with CLSID failed
due to the following error: 80040154 Class not registered
(Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)).
How do I go about resolving this?
I know this is an old question but could help someone else.
Try to change the project properties to x86 platform target on Build section.
Maybe the dlls were built targeting 86 bits.

Retrieving the COM class factory for component with CLSID {XXXX} failed due to the following error: 80040154 VB.net

I code in VB.net under Visual Studio 2008. I have this error and I don't understand why.
Retrieving the COM class factory for component with CLSID {XXXX} failed due to the following error: 80040154
I use a DLL and I want to compile it in 64bit. I have the following code:
Imports MetroProDAT_Library
Public Class Data
Public attr As ZygoDataAttributes
Public oData As ZygoDataFile
Public Sub LoadData(ByVal FileName As String)
Dim idx As Double
Dim jdx As Double
oData = New ZygoDataFile '///this sentence bug
attr = oData.Attribute
End Sub
End Class
it might be the dll file. do you have a specific dll files that needed to run the program ?
register it to the machine and test it again

Categories