Spotfire COM Automation - c#

I am trying to automate save to library using COM object of spotfire. I looked at the examples of Spotfire SDK for COM automation, it calls OpenDocument method using InvokeMethod. When I try to call Save method using the same technique it throws me Missing Member Exception.
public bool OpenDocument(string filePath)
{
var test = this.InvokeMethod("OpenDocument", filePath);
return (bool)test;
}
public bool SaveDocument()
{
return (bool)this.InvokeMethod("Save", null);
}
In the above code snippet, OpenDocument is working perfectly fine but Its throwing error for Save. The com object is of System.__comobject type so I am not even able to inspect and get the metadata (methods / properties) to see the actual method name, which I can use as a parameter to InvokeMethod

Related

Runtime casting of COM objects in C#

I am working on a application which needs to communicate via COM interface with multiple CAD applications (not in the same time). I want to have nice and reusable code, but I came across problems with type casting of COM objects when I made generic application handle getter method.
What I tried so far:
This is the attempt I would like the most if it worked.
public static TCadAppType CadApp<TCadAppType>()
{
dynamic cadApp = default(TCadAppType);
//Here under Dynamic View/Message there is already an error
// Message = "Element not found. (Exception from HRESULT: 0x8002802B (TYPE_E_ELEMENTNOTFOUND))"
// cadVersion.Value evaluates to "SldWorks.Application"
cadApp = (TCadAppType)Marshal.GetActiveObject(cadVersion.Value);
//Following 2 lines of code are for testing purposes only, i am testing with Solidworks API
AssemblyDoc Assembly;
//The exception is thrown when I try to access some method from the Solidworks API
Assembly = (AssemblyDoc)cadApp.OpenDoc6("some parametras...");
}
Attempt using Convert class
// Another attempt using Convert class
public static TCadAppType CadApp<TCadAppType>()
{
dynamic cadApp = default(TCadAppType);
// cadVersion.Value evaluates to "SldWorks.Application"
cadApp = Marshal.GetActiveObject(cadVersion.Value);
cadApp = Convert.ChangeType(cadApp, typeof(SldWorks.SldWorks));
// Exception is thrown with the following message:
// Message = "Object must implement IConvertible."
}
I really thought that I am on the right track, since there is an article on Microsoft Docs website explaining how dynamic can help you with com interopt: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/using-type-dynamic#com-interop
Any ideas how I can do this runtime casting a keep my code as reusable as possible?
My software setup:
Win 10
Project is targeted for .NET 4.7.2
First Tests are with Solidworks 2019
Turns out that the my coding attempt 1 was valid c# code indeed.
I tried it using with Autodesk Inventor, and it works.
So the only thing left for me is to conclude that this is some bug from Solidworks and their COM interfacing.
Thank you Optional Option for your interest in the topic.

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?

System.NullReferenceException When Trying To Load Functions From A DLL File - C#

I'm creating an add-on system for a shell I'm developing using C#. I've followed this and this. Here is my function to load an add-on:
public void loadAppFromDLL(string assemblyFile)
{
Assembly a = Assembly.Load(assemblyFile);
Type app = a.GetType("App");
MethodInfo loadMethod = app.GetMethod("load");
object appInstance = Activator.CreateInstance(app);
loadMethod.Invoke(appInstance, null);
}
Here is the add-on:
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace App
{
public class App
{
public void load()
{
MessageBox.Show("Application loaded successfully!");
}
}
}
When I build the add-on, I place it in the same directory as the shell executable and call:
LoadExternalApp lea = new LoadExternalApp();
lea.loadAppFromDLL("SampleApp");
(LoadExternalApp contains the DLL loading function)
When I was debugging my shell, I noticed that:
The app didn't start
There was a System.NullReferenceException
What am I not doing right?
This:
Type app = a.GetType("App");
is looking for a type with a namespace-qualified name of App.
Your type is called App in a namespace of App, so Assembly.GetType is returning null, and then you're dereferencing it. Instead, you should use:
Type app = a.GetType("App.App");
However, you shouldn't give a class the same name as its namespace in the first place. Fix that, so that you end up with something more like:
Type app = a.GetType("App.PlugIn");
You should still check whether GetType (or GetMethod) returns null, in order to fail rather more gracefully and with more information.
Additionally, you should start following .NET naming conventions - give methods names in PascalCase. Oh, and you might want to consider a common interface for your add-ins rather than relying on reflection to call methods.

How To Connect C# with ACAD 2010

Work on vs2008 C#, Need help to connect with autocad 2010, I follow the following steps to connect but it gives me an error.
I have to add a reference, so I go to references>>add reference>>[COM TAB]>>Autocad 2010 type library>>[OK]
I use two libraries:
using Autodesk.AutoCAD.Interop;
using Autodesk.AutoCAD.Interop.Common;
And then I use the following code to connect with AutoCAD 2010:
namespace Sample_CSharp_Acad_connect
{
class Program
{
private static IAcadApplication oAcadApp = null;
private static string sAcadID = "AutoCAD.Application.18";
static void Main()
{
try //get a running AutoCAD instance if avaialbale
{
oAcadApp = (IAcadApplication)System.Runtime.InteropServices.Marshal.GetActiveObject(sAcadID);
}
catch(Exception) //none found so start a new instance
{
System.Type AcadProg = System.Type.GetTypeFromProgID(sAcadID);
oAcadApp = (IAcadApplication)System.Activator.CreateInstance(AcadProg);
}
if (oAcadApp != null)
{
oAcadApp.Visible = true; //could leave this false to hide Acad from the user
//do whatever with Acad
//oAcadApp.Quit();
}
}
}
Error message : error in getting object class COM for component CLSID
COM? Don't do it.
AutoCAD has a .NET API. See http://usa.autodesk.com/adsk/servlet/index?id=1911627&siteID=123112 for resources from Autodesk. Also see the wiki here for more info and links galore: https://stackoverflow.com/tags/autocad/info
How do you run this code? Is it an exe file? As far as I know, it is not so easy to control AutoCAD using an external progam. There are often lizening issues which prevent such operations.
It might be easier to access AutocAD functionality using a DLL. In that case the object model of AutoCAD can be accessed directly:
Dim theApp as Autodesk.AutoCAD.Interop.AcadApplications = Autodesk.AutoCAD.Interop.AcadApplication()
Debug.Print(theApp.Caption)
The SDK contains a lot of samples for DLLs which can be loaded into AutoCAD.
And: If it isn't really necessary, consider using .NET!

.NET2.0 C# Interop: How to call COM code from C#?

In my last development environment, I was able to easily interact with COM, calling methods on COM objects. Here is the original code, translated into C# style code (to mask the original language):
public static void SpawnIEWithSource(String szSourceHTML)
{
OleVariant ie; //IWebBrowser2
OleVariant ie = new InternetExplorer();
ie.Navigate2("about:blank");
OleVariant webDocument = ie.Document;
webDocument.Write(szSourceHTML);
webDocument.close;
ie.Visible = True;
}
Now begins the tedious, painful, process of trying to interop with COM from managed code.
PInvoke.net already contains the IWebBrower2 translation, the relavent porition of which is:
[ComImport,
DefaultMember("Name"),
Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
SuppressUnmanagedCodeSecurity]
public interface IWebBrowser2
{
[DispId(500)]
void Navigate2([In] ref object URL, [In] ref object Flags, [In] ref object TargetFrameName, [In] ref object PostData, [In] ref object Headers);
object Document { [return: MarshalAs(UnmanagedType.IDispatch)] [DispId(0xcb)] get; }
}
I've created the COM class:
[ComImport]
[Guid("0002DF01-0000-0000-C000-000000000046")]
public class InternetExplorer
{
}
So now it's time for my actual C# transaction:
public static void SpawnIEWithSource(String szHtml)
{
PInvoke.ShellDocView.IWebBrowser2 ie;
ie = (PInvoke.ShellDocView.IWebBrowser2)new PInvoke.ShellDocView.InternetExplorer();
//Navigate to about:blank to initialize the browser
object o = System.Reflection.Missing.Value;
String url = #"about:blank";
ie.Navigate2(ref url, ref o, ref o, ref o, ref o);
//stuff contents into the document
object webDocument = ie.Document;
//webDocument.Write(szHtml);
//webDocument.Close();
ie.Visible = true;
}
The careful readers notice that IWebBrowser2.Document is a late-bound IDispatch.
We're using Visual Studio 2005, with .NET 2.0 on our, and our customer's, machines.
So what's the .NET 2.0 method to invoke methods on an object that, on some level, only supports late-bound IDispatch?
A quick search of Stack Overflow for using IDispatch from C# turns up this post saying what I want is not possible in .NET.
So is it possible to use COM from C# .NET 2.0?
The question is that there is an accepted design pattern that I want to use in C#/.NET. It involves launching Internet Explorer out of process, and giving it HTML content, all the while not using temporary files.
A rejected design idea is hosting Internet Explorer on a WinForm.
An acceptable alternative is launching the system registered web browser, giving it HTML to display, without using a temporary file.
The stumbling block is continuing to use COM objects in the .NET world. The specific problem involves performing late-binding calls to IDispatch without needing C# 4.0. (i.e. while using .NET 2.0)
Update: Based on question updates, I have removed the portions of my answer that are no longer relevant to the question. However, in case other readers are looking for a quick and dirty way to generate HTML in a winforms app and do not require an in-process IE, I will leave the following:
Possible Scenario 1: The ultimate goal is to simply display HTML to your end user and are using Windows Forms
System.Windows.Forms.WebBrowser is the painstakingly easy .NET wrapper for the interface you are trying to manually implement. To get it, Drag and drop an instance of that object from your toolbar (listed as "Web Browser" under the "All Windows Forms" section) onto your form. Then, on some suitable event handler:
webBrowser1.Navigate("about:blank");
webBrowser1.Document.Write("<html><body>Hello World</body></html>");
On my test app, this correctly displayed the haunting message we all have learned to fear and loath.
Late bound IDispatch called is relativly easy in .NET, although piss-poor:
public static void SpawnIEWithSource(String szHtml)
{
// Get the class type and instantiate Internet Explorer.
Type ieType = Type.GetTypeFromProgID("InternetExplorer.Application");
object ie = Activator.CreateInstance(ieType);
//Navigate to the blank page in order to make sure the Document exists
//ie.Navigate2("about:blank");
Object[] parameters = new Object[1];
parameters[0] = #"about:blank";
ie.GetType().InvokeMember("Navigate2", BindingFlags.InvokeMethod | BindingFlags.IgnoreCase, null, ie, parameters);
//Get the Document object now that it exists
//Object document = ie.Document;
object document = ie.GetType().InvokeMember("Document", BindingFlags.GetProperty | BindingFlags.IgnoreCase, null, ie, null);
//document.Write(szSourceHTML);
parameters = new Object[1];
parameters[0] = szHtml;
document.GetType().InvokeMember("Write", BindingFlags.InvokeMethod | BindingFlags.IgnoreCase, null, document, parameters);
//document.Close()
document.GetType().InvokeMember("Close", BindingFlags.InvokeMethod | BindingFlags.IgnoreCase, null, document, null);
//ie.Visible = true;
parameters = new Object[1];
parameters[0] = true;
ie.GetType().InvokeMember("Visible", BindingFlags.SetProperty | BindingFlags.IgnoreCase, null, ie, parameters);
}
The referenced SO question that originally said "not possible until C# 4.0" was amended to show how it is possible in .NET 2.0.
Does C# .NET support IDispatch late binding?
The answers in the post that you link to are actually incorrect. It is generally very easy to deal with IDispatch based objects in .Net. Basically you go through three steps:
Most automation objects (probably well over 90%) that are exposed as IDispatch interfaces have other interfaces that can be used by non-scripting type COM clients (either the IDispatch interface is actually a full COM interface derived from IDispatch or the object supports one or more other IUnknown derived interfaces). In this case, you would simply import the appropriate COM interface definition and then cast the object to the appropriate interface. The cast calls QueryInterface under the covers and returns a wrapped reference to the interface you want.
This is the technique you would use in the scenario that you presented above. The Document object that is returned from the IE automation object supports the IHTMLDocument, IHTMLDocument2, IHTMLDocument3, IHTMLDocument4 and IHTMLDocument5 interfaces (depending on the version of IE you are using). You should cast to the appropriate interface and then call the appropriate method. For example:
IHTMLDocument2 htmlDoc = (IHTMLDocument2)webDocument;
htmlDoc.Write(htmlString);
htmlDoc.Close();
In the rare case where the automation object does not support an alternative interface. Then you should use VB.Net to wrap that interface. With Option Strict set to off (for the wrapper class only) you can use VB's built in support for late bound calls to simply call the appropriate IDispatch methods under the covers. In rare cases with unusual argument types you may need to fiddle a bit with the call but, in general, in VB you can just do it! Even with the dynamic additions to C# v4 VB will still probably have significantly better support for late-bound COM calls.
If for some reason you can't use VB to wrap the automation interface then you can still make any necessary calls from C# using reflection. I won't go into any details since this option should basically never be used but here is a small example involving Office automation.
See this article :
http://www.codeproject.com/KB/cs/IELateBindingAutowod.aspx
Internet Explorer Late Binding Automation
By yincekara
Internet Explorer automation sample code using late binding, without Microsoft.mshtml and shdocvw dependency.
for htmlDoc.write(htmlString);
modify
[Guid("332C4425-26CB-11D0-B483-00C04FD90119")]
[ComImport]
[TypeLibType((short)4160)]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
internal interface IHTMLDocument2
{
[DispId(1054)]
void write([MarshalAs(UnmanagedType.BStr)] string psArray);
//void write([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] object[] psarray);

Categories