Using C++ COM interface in C# for both client and server - c#

I need to make a piece of C# code interact through COM with all kinds of implementations.
To make it easeier for users of that integration, I included the interacted interfaces in IDL (as part of a relevant existing DLL, but without coclass or implementation), then got that into my C# code by running Tlbimp to create the types definition.
I implemented my C#, creating COM objects based on Windows registry info and casting the object into the interface I need.
I then created a C# implementation of the interface in a seperate project and registered it.
The main program creates the testing COM object correctly but fails to cast it into the interface (gets a null object when using C# 'as', gets an InvalidCastException of explicit cast).
Can someone suggest why the interface is not identified as implemented by the testing object?
This is the interface defition in IDL (compiled in C++ in VS 2005):
[
object,
uuid(B60C546F-EE91-48a2-A352-CFC36E613CB7),
dual,
nonextensible,
helpstring("IScriptGenerator Interface"),
pointer_default(unique)
]
interface IScriptGenerator : IDispatch{
[helpstring("Init the Script generator")]
HRESULT Init();
[helpstring("General purpose error reporting")]
HRESULT GetLastError([out] BSTR *Error);
};
This is the stub created for C# by Tlbimp:
[TypeLibType(4288)]
[Guid("B60C546F-EE91-48A2-A352-CFC36E613CB7")]
public interface IScriptGenerator
{
[DispId(1610743813)]
void GetLastError(out string Error);
[DispId(1610743808)]
void Init();
}
This is part of the main C# code, creating a COM object by its ProgID and casting it to the IScriptGenerator interface:
public ScriptGenerator(string GUID)
{
Type comType = Type.GetTypeFromProgID(GUID);
object comObj = null;
if (comType != null)
{
try
{
comObj = Activator.CreateInstance(comType);
}
catch (Exception ex)
{
Debug.Fail("Cannot create the script generator COM object due to the following exception: " + ex, ex.Message + "\n" + ex.StackTrace);
throw ex;
}
}
else
throw new ArgumentException("The GUID does not match a registetred COM object", "GUID");
m_internalGenerator = comObj as IScriptGenerator;
if (m_internalGenerator == null)
{
Debug.Fail("The script generator doesn't support the required interface - IScriptGenerator");
throw new InvalidCastException("The script generator with the GUID " + GUID + " doesn't support the required interface - IScriptGenerator");
}
}
And this is the implementing C# code, to test it's working (and it's not):
[Guid("EB46E31F-0961-4179-8A56-3895DDF2884E"),
ProgId("ScriptGeneratorExample.ScriptGenerator"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(SOAAPIOLELib.IScriptGeneratorCallback))]
public class ScriptGenerator : IScriptGenerator
{
public void GetLastError(out string Error)
{
throw new NotImplementedException();
}
public void Init()
{
// nothing to do
}
}

Again - thanks for the suggestions.
I was able to finally resolve the issue on my own. I tried the above suggestions and didn't made any progress. Then I changed the namespace of the interop in the 'testing' code - it varied from the one in the main code because of different argument use when using Tlbimp. This solved the problem.
Here's my guess to why: .Net creates the COM object, but when it detects this is actually a .Net object, it bypass the COM layer and communicates directly. In which case, queryInterface (with the interface GUID) is not used and the interface do differ because of different C# namespaces.
This means that in order to supprot integration with .Net code, I will need to publish my original interop assembly aside the IDL.
Thanks,
Inbar

I think you need this on the interface
[InterfaceType(ComInterfaceType.InterfaceIsDual)]

Related

suspicious cast there is no type in the solution which is inherited from--> why/somehow it is working

i am currently working on an AddIn for Inventor(3d Modeling Software). When you are using the api there are two different kind of documents
AssemblyDocument
PartDocument
the api provides me a method which returns the selected document.
PartDocument part = ((PartDocument)application.ActiveDocument);
during runtime this cast works. The compiler tells me that this is a suspicious cast, because the 'PartDocument' does not implements the type the 'application.ActiveDocument' returns.
[TypeLibType(TypeLibTypeFlags.FDispatchable)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("xxxxxx")]
[DefaultMember("Type")]
[ComImport]
public interface PartDocument
{
....
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[TypeLibType(TypeLibTypeFlags.FDispatchable)]
[Guid("xxxxx")]
[DefaultMember("Type")]
[ComImport]
public interface Application
{...
[DispId(50331905)]
_Document ActiveDocument { [DispId(50331905), MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [return: MarshalAs(UnmanagedType.Interface)] get; }
[Guid("xxxxxx")]
[CoClass(typeof (_DocumentClass))]
[ComImport]
public interface _Document : Document, _VbaImplementationEvents_Event
{
}
So why is this working ? Can someone explain this this to me ?
And how can i test this ?
var documentMock =new Mock<PartDocument>();
var applicationMock = new Mock<Application>();
applicationMock.Setup(x => x.ActiveDocument).Returns(documentMock.Object);
The compiler tells me that he cannot cast from 'PartDocument' to '_Document' --> this is true, so why is it working during runtime ?
Thanks in advance
It works because it is a COM type. For COM type casting, the interop handler will automatically call IUnknown.QueryInterface on the COM object (every COM object implements IUnknown) to find out if it supports PartDocument interface, and get the location of the relevant VTable. This is entirely disconnected from the .NET type specification, and, theoretically, could return a different result each time it was called.

DLL for VBA.Collection

I need to use a VBA.Collection class and I need to register a COM component in order to do that. I found msvbvm60.dll file on my computer and successfully registered it in my system. I am creating an instance of a class VBA.Collection, but I get an exception in that line of code:
VBA.Collection collection = new VBA.Collection();
The exception has the following description:
System.Runtime.InteropServices.COMException was unhandled
HResult=-2147221164 Message=Retrieving the COM class factory for
component with CLSID {A4C4671C-499F-101B-BB78-00AA00383CBB} failed due
to the following error: 80040154 Class not registered (Exception from
HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)). Source=mscorlib
ErrorCode=-2147221164
I used regedit.exe tool to search for a component with CLSID of {A4C4671C-499F-101B-BB78-00AA00383CBB}, but haven't been able to find one. I suspect that there should be another .dll file that I should register, even though I can see with Object Browser in Visual Studio that reference to msvbvm60.dll gives me access to VBA namespace and Collection class within it.
It says on this web-page http://codeverge.com/asp.net.migrating-from-asp/using-vba.collection-object-in-net/577432 that the problem will go away if I instantiate VBA.CollectionClass . However, if I do so, I receive the following error and my code doesn't compile:
Interop type 'VBA.CollectionClass' cannot be embedded. Use the
applicable interface instead
Have I registered the wrong COM component? Or is there anything I need to change in my C# code?
I also read on How to create VBA.Collection() object in c# that I need to create my own class based on VBA._Collection interface and not instantiate VBA.Collection class. I created the following class:
class MyCollection : VBA._Collection
{
private Dictionary<object, object> _items = new Dictionary<object, object>();
public void Add(ref object Item, [System.Runtime.InteropServices.OptionalAttribute]ref object Key, [System.Runtime.InteropServices.OptionalAttribute]ref object Before, [System.Runtime.InteropServices.OptionalAttribute]ref object After)
{
// Ignoring the Before and After params for simplicity
_items.Add(Key, Item);
}
public int Count()
{
return _items.Count;
}
public System.Collections.IEnumerator GetEnumerator()
{
return _items.Values.GetEnumerator();
}
public dynamic Item(ref object Index)
{
return _items[Index];
}
public void Remove(ref object Index)
{
_items.Remove(Index);
}
}
I now instantiate MyCollection class with MyCollection collection = new MyCollection();
I do not get an exception there anymore. However, I need to call a method GetIncidentCodes() of another COM component that returns an object of type VBA.Collection. If I use collection = class1.GetIncidentCodes();, then my program won't compile because an explicit cast is required. If I use collection = (MyCollection) class1.GetIncidentCodes();, then I get the following exception:
Unable to cast COM object of type 'System.__ComObject' to class type
'PoliceDispatcherClient.MyCollection'. Instances of types that
represent COM components cannot be cast to types that do not represent
COM components; however they can be cast to interfaces as long as the
underlying COM component supports QueryInterface calls for the IID of
the interface.
Should I cast the return value of GetIncidentCodes() into interface VBA._Collection? I am new to .NET, so I am not sure how to do it.

Returning Managed C# List to Unmanaged C++ code

I have a C# dll ( for which register for COM interop option is set).
This C# dll has the below interface and class
interface IMyInterface
{
bool IsNameExists(string name);
List<string> GetNameList();
}
public class MyClass : IMyInterface
{
public bool IsNameExists(string name)
{
//DO Something
}
public List<string> GetNameList()
{
// DO something
}
}
I need to call the methods IsNameExists and GetNameList from unmanaged C++.
#import "..\..\ProdCon\bin\ProdCon.tlb" raw_interfaces_only
void main()
{
HRESULT hr =::CoInitialize(NULL);
IMyInterface pIMyInterface(__uuidof(MyClass));
VARIANT_BOOL bRet = FALSE;
BSTR bstrName = ::SysAllocString(_T("RAJESH"));
hr = pIMyInterface->IsNameExists(bstrName,&bRet);
}
So I created COM object as above and called the IsNameExists mehtod without any issue.
Since GetNameList method returns list, I am getting the below warning
'MyDll.IMyInterface.GetNameList(#0), MyDll'. Warning: Type library
exporter encountered a generic type instance in a signature. Generic
code may not be exported to COM.
Please help me how to return C# list to unmanaged C++ code. So that unmanaged C++ code can use this list.
'Generic code may not be exported to COM.' => hence its the type parameter string in public List GetNameList(). Thus essentially you need access to a non-generic c# method to get the data.
If you have control of the MyClass codebase you could (for example) add a:
public string[] GetNameArray()
{
return GetNameList.ToArray();
}
If not then you'll need to write a proxy/wrapper class to do something similar to the above and expose that via COM, either as a one off or a 'general' methodology using reflection say.
See for example http://weblog.west-wind.com/posts/2007/Jul/10/Generics-and-COM-Interop-dont-mix

C# com interopt failure on american machines

I've been working on a simple dll library that is com-accessible so that other softwares can use our library (from any managed or unmanaged language).
Creating a com-accessible dll is fairy easy:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace MyNamespace
{
//This interface defines purely the events. DotNetEventSender should implement this interface with the ComSourceInterfaces() attribute
//to become an Event Source.
[ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface COMEventsInterface
{
//[DispId(1)]
// we don't have any events, but if needed, include them here
}
[ComVisible(true)]
public interface ICOM
{
//Methods
int Sum(int[] intsToSum)
}
//Identifies this interfaces that are exposed as COM event sources for the attributed class.
[ComSourceInterfaces(typeof(COMEventsInterface))]
//Tells the compiler not to generate an interface automatically and that we are implementing our own interface (IDotNetEventSender)
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class COM : ICOM
{
// Methods
public int Sum(int[] intsToSum)
{
int sum = 0;
foreach ( int i in intsToSum )
{
sum += i;
}
return sum;
}
}
}
In debug mode in would now mark this project to register for com-interop via Project>Properties>Build>Register for com interop.
In release mode I have an installer which marks the primary output from my project as "vsdrpCOM".
And this works great, in most cases. But somehow on some machines (all American) this won't work. The com class gets registered but I constantly get the error: HRESULT 0x80131534, which is actually already descibed here on SO: Error when instantiating .NET/COM interop class via classic ASP
But realy, I don't see any solution here. I've checked for user rights, domain rights, ...
EDIT:
The constructor of my real class does this one thing:
(I've added the try catch because I found on SO that this is an error in the constructor...)
// Constructor
public COM()
{
try
{
// register itself with the application
MyApplication.COMObject = this;
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
It just registers itself to a static class' property COMObject:
private static COM _comObject;
public static COM COMObject
{
get
{
return _comObject;
}
set
{
_comObject = value;
}
}
Although, the COM class doesn't really need to register itself, i've done this for future use if I would like to trigger Events
Well, I happens to be that i have faulty declared a DateTime in one of my declarations of a static class... private DateTime myDateTime = Convert.ToDateTime("15/09/2013 12:00:00");
And ofcourse, on a EU system, this will work, but on an American (or even others) this gives an error because there is no 15th month...
This gets triggered even before the constructor of my com-accessible class and that's why the error couldn't be handled.
Dumb mistake, but proves that sometimes errors look very complex while they are very simple.

Calling a method on Object exposed by Word Add-in throws RemotingException

I am writing a (Shared) Word Add-In in C# and want to communicate with it by exposing an object through the Object property of the COMAddIn class.
Because I want my code to be executed on the UI thread I derive my add-in and exposed object from the StandardOleMarshalObject class. This should take care of the marshaling as described here and here.
But by doing this I get different behavior when i compile against .NET 2.0 or.NET 4.0. When compiling against .NET 4.0 my exposed object is of type __ComObject and lets itself be cast to my publicly comvisible defined interface. This in turn lets me call methods on the object and works perfectly.
When compiling against .NET 2.0 the exposed object is of type __TransparentProxy. This can also be cast to my interface but when i try to call a method it wil throw a System.Runtime.Remoting.RemotingException with the message:
This remoting proxy has no channel sink which means either the server has no registered server channels that are listening, or this application has no suitable client channel to talk to the server.
When I do not inherit from StandardOleMarshalObject it does seem to work but then my code would execute on an arbitrary RPC thread which is not what i'm looking for.
I have searched the internet but was not able to find a solution or reason why this is not working in .NET 2.0. I did find some similar problems, but they all seem to address Excel.
At this moment I'm not in the position of switching to .NET 4.0 so i'm really hoping this can be solved for .NET 2.0.
Does anyone have a solution for this problem, or at least an explanation?
Here is my test code :
[ComVisible(true)][Guid("...")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IService
{
void Hello();
}
[ComVisible(true)][Guid("...")]
[ClassInterface(ClassInterfaceType.None)]
public class MyService : StandardOleMarshalObject, IService
{
public void Hello()
{
MessageBox.Show("Hello");
}
}
public class MyAddIn : StandardOleMarshalObject, IDTExtensibility2
{
public void OnConnection(object application, ext_ConnectMode connectMode,
object addInInst, ref Array custom)
{
_service = new MyService();
((COMAddIn)addInInst).Object = _service;
}
//Rest of the IDTExtensibility2 implementation
}
public class Test
{
public static void Main(string[] args)
{
Application app = new Application();
app.Visible = true;
COMAddIn addIn = app.COMAddIns.Item("MyAddin");
IService service = addIn.Object as IService;
if (service != null)
service.Hello(); // <-- RemotingException happening here
}
}
So I found a workaround for my problem that is acceptable and works perfectly with .NET2.0. I don't find it as elegant as it could have been, but it works. I'm using a little hidden "proxy" window that lets me marshal calls made from an out-of-proc client to the UI thread of Word. I'm not planning to have many methods exposed through COM so the extra lines of code won't be a problem. I've added the important pieces of code below.
/// <summary>
/// HiddenForm can be used to marshal calls to the UI thread but is not visible
/// </summary>
public class HiddenForm : Form
{
public HiddenForm()
{
//Making a dummy call to the Handle property will force the native
//window handle to be created which is the minimum requirement for
//InvokeRequired to work.
IntPtr hWnd = Handle;
}
}
/// <summary>
/// AddInService will be exposed through the Object property of the AddIn but does NOT derive
/// from StandardOleMarshalObject but instead uses a <see cref="HiddenForm"/> to marshal calls
/// from an arbitrary RPC thread to the UI thread.
/// </summary>
public class AddInService : IAddInService
{
private readonly Form _invokeForm;
public AddInService()
{
//create an instance of the HiddenForm which allows to marshal COM
//calls to the UI thread.
_invokeForm = new HiddenForm();
}
public void HelloOutOfProc()
{
if(_invokeForm.InvokeRequired)
{
_invokeForm.Invoke(
new Action<object>(o => HelloOutOfProc()), new object()); //not really elegant yet but Action<> was the only "out of the box" solution that I could find
}
else
{
MessageBox.Show("HelloOutOfProc on thread id " + Thread.CurrentThread.ManagedThreadId);
}
}
}
/// <summary>
/// AddIn Class which DOES derive from StandardOleMarshalObject so it's executed on the UI thread
/// </summary>
public class Connect : StandardOleMarshalObject, IDTExtensibility2
{
private IAddInService _service;
public void OnConnection(object application, ext_ConnectMode connectMode,
object addInInst, ref Array custom)
{
//create service object that will be exposed to out-of-proc processes
_service = new AddInService();
//expose AddInService through the COMAddIn.Object property
((COMAddIn)addInInst).Object = _service;
}
}
Tested on Window 7, Office 2007. Hope this helps others.
I do still like to know WHY it's working in .NET4.0 and not .NET2.0. So if anyone has a answer to this, it is still appreciated.

Categories

Resources