Handle missing reference to COM interface (MATLAB) nicely - c#

I have a VS2013 C# project which references the MATLAB COM interface. 90% of what the app does is in no way related to MATLAB and I would like to be able to compile and run in environments where MATLAB is not available. The desired behavior is:
Build, regardless of presence of the reference (throwing a warning).
If MATLAB is not available, print a message if the user tries to execute MATLAB specific functionality, but it should not be fatal.
My first instinct was to use a pre-processor directive with different build configurations, but I would like to avoid this because it gets messy when setting up continuous integration and tests etc.
So I tried some reflection:
try
{
var matlab = Activator.CreateComInstanceFrom("appname.exe", "MLApp.MLApp").Unwrap();
}
catch
{
//message
}
Which throws a MissingMethodException (i.e. it cannot find a constructor). When MATLAB is present this works fine:
var matlab = new MLApp.MLApp();
New ideas most welcome, thanks in advance!
Edit:
See below for a detailed answer, but for those looking for a quick bit of test code, here is what worked for me.
var Matlab_t = Type.GetTypeFromProgID("Matlab.Autoserver");
if (Matlab_t != null)
matlab = Activator.CreateInstance(Matlab_t);
else
MessageBox.Show("Matlab installation not found.\n");

In my COM related Unit Tests / Detectors I simply do it in multiple steps:
Find COM type
Create COM object from type
Call methods.
Release COM objects, all of them.
In your case I would suggest writing mapper class hiding all of this COM ugliness under layer of abstraction. (I just needed those helpers to test COM library I wrote, so it was opposite)
Type.GetTypeFromProgID throws no exceptions when type is not found, instead it simply returns null.
public static object GetComObject(string typeName)
{
Type ComType;
ComType = Type.GetTypeFromProgID(typeName);
if (ComType == null)
{
//COM type not found
}
// Create an instance of your COM Registered Object.
return Activator.CreateInstance(ComType);
}
When object is (or is not) created I follow up with dynamic or hand-written reflections (does same stuff as dynamic but with higher degree of control, and also works in older .NET versions).
public static object CallFunction(string method, string typeName, params object[] args)
{
Type ComType;
object ComObject;
ComType = Type.GetTypeFromProgID(typeName);
if (ComType == null)
{
//COM type not found
}
// Create an instance of your COM Registered Object.
ComObject = Activator.CreateInstance(ComType);
// Call the Method and cast return to whatever it should be.
return ComType.InvokeMember(method, BindingFlags.InvokeMethod, null, ComObject, args);
}
After work is finished you should release them, all of them. Including objects returned by (created by) methods in root COM object. (Releasing parent, but not its children causes, aside of memleaks, Unit Test engine to freeze, nasty)
public static void DisposeCOM(dynamic obj)
{
if (obj != null)
{
Marshal.ReleaseComObject(obj);
}
}

Related

Pragmatically detect if if I forget to include an assembly

SEE EDITS AT THE END.
I have an app that requires to user to install a 3rd party application for mine to run. I'd like to make my application provide a warning to the user if they have forgotten to install this 3rd party app instead of just quitting as a result of trying to access a missing assembly.
In C#, I'd like to check if an assembly is included.
I thought I could use:
object.ReferenceEquals(Oject, null)
But but object.ReferenceEquals does not like to take Type as an input.
I tried:
var type = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type2 in assembly.GetTypes()
where type2.Name == "Oject"
select type2);
if (type == null) {...}
But Visual Studio shows me that type can never be null.
So: Is there a simple way I can detect if I forgot to include an assembly before I access it (and the app just quits with no warning or message telling the user WHY it quit)?
I thought I could use
try
{
types = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
types = ex.Types.Where(p => p != null).ToArray();
}
from: Missing types using reflection on DLL but I'm doing this check from Program.cs that is a static class.
EDIT:
It turns out that I have the referenced assemblies but that these assemblies may not have the correct logic installed. So the references came along with all the binaries but since the 3rd party app was not installed, those binaries went bonkers when they could not reach their intended targets and it is those binaries that seem to be failings.
So... I still have this issue but it may explain I cannot "Catch" ??
An interesting fact about type loading in .NET. A type is loaded upon entering a method that uses it. An assembly is loaded when the first type is loaded. So, example code below (assuming "SomeType" is a type in the assembly you are looking for)
So by this logic you can have:
static void Main() {
if (CanLoad()) {
DoStuff();
} else {
Console.WriteLine("Some error message");
}
}
static void DoStuff() {
// Ok to reference SomeType here
}
private static bool CanLoad() {
try {
TryLoad();
return true;
} catch {
return false;
}
}
private static void TryLoad() {
Type t = typeof(SomeType);
}
With this code, what happens is this. Main calls CanLoad normally. CanLoad doesn't need any fancy type, so it happily starts up, and then attempts to call TryLoad. TryLoad, however, has a reference to "SomeType", so when the compiler attempts to enter that method, before executing any part of it, it attempts to load the assembly containing "SomeType". If this fails, the method throws a FileNotFoundException trying lo load it. We catch that in CanLoad and return false.
It's very important that neither "Main" nor "CanLoad" have any references to that assembly at all (even after the check), or they will crash before the check (remember, the loads are done when entering a method, not on reaching the line), but "DoStuff" can reference whatever it wants.

Detecting when a DLL has raised an exception

OK, so I have the C# DLL method:
public void DeletePublisher(string strName)
{
try
{
if (_PublisherData.PublisherDictionary.ContainsKey(strName))
_PublisherData.PublisherDictionary.Remove(strName);
}
catch (Exception ex)
{
SimpleLog.Log(ex);
}
}
It works fine. If there is a exception it is detected and added to the log.
At the moment, the above is called via MFC in my C++ project using a wrapper:
bool CMSATools::DeletePublisher(CString strPublisher)
{
bool bDeleted = false;
if (m_pInterface != nullptr)
{
CComBSTR bstrPublisher = strPublisher.AllocSysString();
throw_if_fail(m_pInterface->DeletePublisher(bstrPublisher));
bDeleted = true;
}
return bDeleted;
}
They both work fine. The issue is that fact that the CPP method currently has no knowledge of the C# method having failed. Now, in this particular instance I know I could change the signature of the DLL method to return false for a exception failure occurring and examine that return value in the CPP file.
But, in other instances I am already using the return value and thus, it would seem for consistency to me, that I pass in a bool bExceptionRaised parameter instead to my methods in the DLL.
That way, I can test that value when the method seemed to complete and if it is false act accordingly.
At the moment my application doesn't realise that an exception occurred and that is confusion.
Can I assume that either of these methodologies are the simplest approach to what I am trying to detect?
Update
Based on the answer provided I have tried to follow this tutorial and I am getting confused. I have tried to follow it and I can't create a CLR DLL and build it that is a bridge to my C# DLL file.
Whilst I appreciate the answer I feel like it is breaking up everything I have worked on since the C# DLL already handles and logs it's exceptions. Whilst I would like to learn how to build this bridge for the future, I still think perhaps at the point in time just changing my signatures is sufficient. Either way, my attempt a basic build of a bridge is failing.
Use a C++/CLI wrapper for the access of the managed component.
With C++/CLI you can directly use the C# component can catch the managed exception and you can throw a native exception or even return true or false... whatever you want.
void DoManagedStuff(SOMEDATA somedata)
{
try
{
auto sm = ConvertToSomeDataToManaged(somedata);
CallManagedCode(sm);
}
catch (Exception^ e)
{
throw CMyNativeException();
}
}

Why cannot I cast my COM object to the interface it implements in C#?

I have this interface in the dll (this code is shown in Visual Studio from metadata):
#region Assembly XCapture.dll, v2.0.50727
// d:\svn\dashboard\trunk\Source\MockDiagnosticsServer\lib\XCapture.dll
#endregion
using System;
using System.Runtime.InteropServices;
namespace XCapture
{
[TypeLibType(4160)]
[Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")]
public interface IDiagnostics
{
[DispId(1)]
void GetStatusInfo(int index, ref object data);
}
}
So I created a COM server with such class:
[ComVisible(true)]
[Guid(SimpleDiagnosticsMock.CLSID)]
[ComDefaultInterface(typeof(IDiagnostics))]
[ClassInterface(ClassInterfaceType.None)]
public class SimpleDiagnosticsMock : ReferenceCountedObject, IDiagnostics
{
public const string CLSID = "281C897B-A81F-4C61-8472-79B61B99A6BC";
// These routines perform the additional COM registration needed by
// the service. ---- stripped from example
void IDiagnostics.GetStatusInfo(int index, ref object data)
{
Log.Info("GetStatusInfo called with index={0}, data={1}", index, data);
data = index.ToString();
}
}
Server seems to work fine, and I am able to use the object from a VBScript. But then I try to use it from another C# client:
[STAThread]
static void Main(string[] args)
{
Guid mockClsId = new Guid("281C897B-A81F-4C61-8472-79B61B99A6BC");
Type mockType = Type.GetTypeFromCLSID(mockClsId, true);
IDiagnostics mock = (IDiagnostics)Activator.CreateInstance(mockType);
//var diag = mock as IDiagnostics;
object s = null;
mock.GetStatusInfo(3, ref s);
Console.WriteLine(s);
Console.ReadKey();
}
And it fails with
Unable to cast COM object of type 'System.__ComObject' to interface
type 'XCapture.IDiagnostics'. This operation failed because the
QueryInterface call on the COM component for the interface with IID
'{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}' failed due to the following
error: No such interface supported (Exception from HRESULT: 0x80004002
(E_NOINTERFACE)).
What am I doing wrong?
I have also tried to use InvokeMember, and that kinda worked except that I wasn't able to get the ref-returned data parameter.
EDIT: added STAThread attribute to my Main procedure. This does not solve the issue, but you really should use STAThread with COM unless you're absolutely sure you don't need it. See Hans Passant's answer below.
This exception can be a DLL Hell problem. But the simplest explanation is for what's missing from your snippet. Your Main() method is missing the [STAThread] attribute.
That's an important attribute that matters when you use COM objects in your code. Most of them are not thread-safe and they require a thread that's a hospitable home for code that cannot support threading. The attribute forces the state of the thread, the one you can set explicitly with Thread.SetApartmentState(). Which you can't do for the main thread of an app since Windows starts it, so the attribute is used to configure it.
If you omit it then you the main thread joins the MTA, the multi-threaded apartment. COM is then forced to create a new thread to give the component a safe home. Which requires all calls to be marshaled from your main thread to that helper thread. The E_NOINTERFACE error is raised when COM cannot find a way to do that, it requires a helper that knows how to serialize the method arguments. That's something that needs to be taken care of by the COM developer, he didn't do that. Sloppy but not unusual.
A requirement of an STA thread is that it also pumps a message loop. The kind you get in a Winforms or WPF app from Application.Run(). You don't have one in your code. You might get away with it since you don't actually make any calls from a worker thread. But COM components tend to rely on the message loop to be available for their own use. You'll notice this by it misbehaving, not raising an event or deadlocking.
So start fixing this by applying the attribute first:
[STAThread]
static void Main(string[] args)
{
// etc..
}
Which will solve this exception. If you have the described event raising or deadlock problems then you'll need to change your application type. Winforms is usually easy to get going.
I cannot otherwise take a stab at the mocking failure. There are significant deployment details involved with COM, registry keys have to be written to allow COM to discover components. You have to get the guids right and the interfaces have to be an exact match. Regasm.exe is required to register a .NET component that's [ComVisible]. If you try to mock an existing COM component, and got it right, then you'll destroy the registration for the real component. Not so sure that's worth pursuing ;) And you'll have a significant problem adding a reference to the [ComVisible] assembly, the IDE refuses to allow a .NET program to use a .NET assembly through COM. Only late binding can fool the machine. Judging from the COM exception, you haven't gotten close to mocking yet. Best to use the COM component as-is, also a real test.
So, the problem was that my DLL with IDiagnostics interface was generated from a TLB, and that TLB never got registered.
Since the DLL was imported from the TLB, RegAsm.exe refuses to register the library. So I used the regtlibv12.exe tool to register the TLB itself:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\regtlibv12.exe "$(ProjectDir)\lib\Diagnostics.tlb"
Then everything magically started to work.
Since regtlibv12 is not a supported tool, I still don't know how to do this properly.

Hosting CLR Runtime in C++

I am working on an extension for a project that will allow hosting the CLR inside the core application. With this I plan to allow this extension to manage managed extensions that it loads/unloads inside itself. That being said, I need to use separate AppDomains to ensure unloading is possible.
Currently, I am able to get the domains setup and get the plugin file loaded but at that point I'm stuck. I'm not sure how to call functions inside the domains loaded assembly at will and so on.
Here is my loading setup so far, minus error checking and such:
ICorRuntimeHost* lpRuntimeHost = NULL;
CorBindToRuntimeEx( L"v4.0.30319", L"wks", 0, CLSID_CorRuntimeHost, IID_PPV_ARGS( &lpRuntimeHost ) );
lpRuntimeHost->Start();
// File data read from disk.
// Dll file just CreateFile/ReadFile and insert into pluginFileData.
CComSafeArray<BYTE> pluginFileData;
IUnknown* lpUnknown = NULL;
lpRuntimeHost->CreateDomain( wstrPlugin.c_str(), NULL, &lpUnknown );
CComPtr<_AppDomain> appDomain = NULL;
lpUnknown->QueryInterface( &appDomain.p );
CComPtr<_Assembly> appAssembly = NULL;
hResult = appDomain->Load_3( pluginFileData, &appAssembly );
I have a class library that all plugins must reference and use in order to be considered a plugin. Which so far is nothing more than a base class to inherit:
namespace FrameworkAPI
{
public class IFrameworkPlugin
{
public override bool Initialize(IntPtr interfaceObj)
{
return false;
}
}
}
And then an extension would reference that class library and use that as its base:
namespace ClassLibrary1
{
public class Main : IFrameworkPlugin
{
public override bool Initialize(IntPtr interfaceObj)
{
// Return true to stay loaded.
return true;
}
}
}
What I am stuck at is how to do a few things:
How can I obtain the main class but as the base to invoke methods in the base that allow the main class to still handle?
How can I ensure that the main class inherits the base so I can ensure its a valid plugin file?
How I can freely invoke methods from the C++ side to fire events in the C# plugin.
For the firing events, the C++ plugin will call more things in the C# plugins once they are loaded, such as rendering events, command handling, etc.
Most of the examples I find online are specific to requiring the entire C# side to be static which I don't want. Also most do not use separate AppDomains and rather all execute in the default. I don't want this since it limits being able to unload a specific plugin.
If any other info is missing and needed feel free to let me know.
I resolved this issue by using a COM exposed interface for the C# side of things.
I have placed the FrameworkAPI inside a separate DLL and exposed it's main interface to COM then reference it in the plugins that will use it.
With it compiled with COM enabled, I can import the .tlb generated file to use the interface in C++ easily.

Marshal.ReleaseComObject throws exception

Trying to use a COM visible .NET class via other .NET application and get exception:
Message: The object's type must be
__ComObject or derived from __ComObject.
Parameter name: o
Stack Trace: at
System.Runtime.InteropServices.Marshal.ReleaseComObject(Object
o)
The class looks as follows:
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IViewer : IComInteropDefinedInterface
{
}
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[Guid("[some guid]")]
public class MyViewer : UserControl, IViewer
{
//IViewer implementation
}
I register the component with:
regasm [Assembly Path] /tlb /codebase
The client application, which is also in .NET instantiates successfully the given class, but when he callsMarshal.ReleaseComObject() it gets the exception described above.
Any idea for solving this problem?
EDIT:
Unfortunately I can't provide the client application code for instantiating my object. However I know the client is using the same method to instantiate real COM objects.
I got this problem recently, when reimplementing a native COM to managed code.
The solution was to ask if the object is a native COM with Marshal.IsComObject, only native COMs must be release with Marshal.ReleaseComObject.
This is code:
if (Marshal.IsComObject(comObject))
{
Marshal.ReleaseComObject(comObject);
}
comObject = null;
Important: you have to be sure, no use that object after been Released.
For a more detailed explanation read this post: http://blogs.msdn.com/b/visualstudio/archive/2010/03/01/marshal-releasecomobject-considered-dangerous.aspx
But how are you creating the class instance? Simply using the expression new MyViewer() doesn't create a COM object. Instead it creates a plain old .Net object which cannot be used with the ReleaseComObject method.
Based on your sample code, in particular the line about MyViewer having an implementation, it doesn't sound like you're dealing with a COM object. Instead it looks like you have a managed object which implements a COM interface.
In order to use the ReleaseComObject you'd need to actually have a COM / RCW object.
My guess would be that you are actually not using COM but simply use a referenced .NET class. If your project contains code like
MyViewer viewer = new MyViewer();
and you have added the library containing MyViewer not as a COM reference, you are actually not using COM.
I would rather try:
if (comObject != null)
{
if (System.Runtime.InteropServices.Marshal.IsComObject(comObject))
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(comObject);
}
comObject= null;
}

Categories

Resources