How to call a COM object from C# - c#

First, I realize that there are many posts here that discuss this topic. I must have read 20+ of them, easily. But none of them give the answer that I seek.
I have written a tiny C# test COM DLL with a single method in it that prints "I am alive!" in a message box. Using VStudio as admin, I can build and register the COM object. I have successfully called the object from VBA and run the method. And I can see the name of the COM Interface in the VStudio Add Reference / COM dialog box. This all makes me think the object is properly constructed, registered, and usable.
Now I'm trying to call it from a console C# app. Like many others, I'm trying to figure out the equivalent of the VBA "CreateObject("DLLName.ClassName")" code to get hold of the object in C#.
One way is to just add a reference to the DLL to my console app project. I point to the assembly through the Projects section of the Add Reference dialog, not through the COM section of the dialog. Then I can simply say var o = new MyComImplementationClass(); and treat it like any other class. That works, but it means my console app is cheating and not using the COM object through the usual COM GAC interface.
Another way (that doesn't work, but I wish it did), is to add the reference through the COM tab on the Add Reference dialog. I can see it but VS protests that "the XXX.tlb file was exported from a .NET assembly. Add a reference to the assembly instead." Which brings me back to the solution above, which I think means that my app is cheating. (I didn't have to add references to my VBA test app, for example.)
Another way is to use Type.GetTypeFromProgId as shown by this code fragment below. But I can't get that to work either. I must be passing in the incorrect ProgID string - I get the sense it has something to do with registry info and is not the same "DLLName.ClassName" string that I feed CreateObject() in VBA.
public static dynamic ComObjectGet () {
const string progID = "ComExampleDLLName.ComImplementationClassName";
Type foo = Type.GetTypeFromProgID (progID);
dynamic COMobject = Activator.CreateInstance (foo);
return COMobject;
}
Worse yet, on this MSDN example page it says "This method is provided for COM support. Program IDs are not used in Microsoft .NET Framework because they have been superseded by the concept of namespace." So probably I should not be using the GetTypeFromProgID at all.
If it helps any, I can use VSTO in C# to call the MSOffice primary interop assemblies. But they load from the COM tab of the add reference dialogs (which is where I want my COM library to load from).
For clarity, my COM DLL name is ComExampleLibrary.dll. The default namespace is ComExampleNamespace. The interface name is IComInterface, and the implementation classname is ComImplementation. The internal method name is Run.
Could someone give me instructions or a code snippet that does the "right, approved" thing for calling COM objects (not just the ones I write) from C#? Thank you.

Thanks to the people who helped me out, here is the answer. Both GetTypeFromProgID and GetTypeFromCLSID work as shown below. My problem was that I was using "AssemblyName.ClassName" instead of "Namespace.ClassName" in the call to GetTypeFromProgID.
public static dynamic ComObjectGet () {
const string progID = "ComExampleNamespace.ComImplementation";
Type foo = Type.GetTypeFromProgID (progID);
//var bar = Guid.Parse ("99929AA7-0334-4B2D-AC74-5E282A12D06C");
//Type foo = Type.GetTypeFromCLSID (bar);
dynamic COMobject = Activator.CreateInstance (foo);
return COMobject;
}
So my original code was correct, but I was passing in the wrong argument. This snippet is the equivalent of the VBA CreateObject("Namespace.ClassName") call.
I still don't know why I cannot add a reference to the COM item in the COM tab of the Add Reference dialog like I would for any other COM object. I suppose that's a different question.

Related

Using DLLs in VBScript

I've compiled C# code into a DLL, but have little experience with them. My C# code contains a class HelloWorld with a static method Print(). I'd like to use this DLL in VBScript to call the method Print(). I know this is base, but I'm using this as a test for a larger scale project that will be compiled to DLL in the end. What's the declare look like for that and how would the method call look?
Important: Both methods will work only if the DLL exposes a COM interface.
If your dll is registered with the system, use CreateObject with it's ProgID.
Set myObject = CreateObject("MyReallyCoolObject.HelloWorld")
myObject.Print
If your object is not registered on the system, use GetObject with a path to the file containing your object. Make sure your object exposes the proper interface. (The second parameter is optional. Here you can provide a class name if your object exposes more than one.)
Set myObject = GetObject("C:\some\path\helloworld.dll", "appname.HelloWorld")
myObject.Print
I think you might be looking for Registration-Free COM. This SO answer regarding the Microsoft.Windows.ActCtx should help specifically for VBScript.
Keep in mind that COM doesn't support static methods, so you'll have to make your Print method into an instance method.
How to call a .NET DLL from a VBScript

Adding COM object to managed code

I have a header-file, the COM interface. I have created a small win32 program which works, but my main program is written in C#.
So I would like to import this COM object in my main program, but how do I do this, when the only thing I got is the header-file?
All places I've looked I need a tlb-file..?
I'm new to COM objects so just ask if you need some extra info, or have another workaround :)
[UPDATE]
First thanks for all the responses!
I've tried some different things, but haven't solved my issue yet. In my research, I've found an article describing COM Interop http://msdn.microsoft.com/en-us/library/aa645736(v=vs.71).aspx#vcwlkcominteroppart1cclienttutorialanchor2
This haven't helped me out. But I've found that I should be able to get moving if I can complete the following:
Declaring a COM coclass:
[ComImport, Guid("7C075F7F-FD71-40a2-AC63-0D0C4DB39ECA")]
class CCamera
{
// Cannot have any members here
// NOTE that the C# compiler will add a default constructor
// for you (no parameters).
}
Creating a COM class wrapper:
[Guid("AD87369B-3BBA-4f1c-81C5-B92FCEA9A1F4"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface ICamera
{
//static HRESULT GetCameraInterface();
bool StartPreview();
bool StopPreview();
}
Using Casts Instead of QueryInterface:
try
{
CCamera cam = new CCamera();
ICamera test = (ICamera)cam;
//test.StartPreview();
}
catch (Exception e)
{
Console.WriteLine(e.StackTrace);
}
I get an invalid cast exception, so is this because I miss implementing some methods in the interface?
And how do I implement the following method from the c++ interface:
static HRESULT GetCameraInterface(void __RPC_FAR *__RPC_FAR *ppvObject);
[SOLUTION]
OK I got a solution, but I never solved to wrap the interface.
Instead I created a C++ dll project and exposed the methods I needed. Then in my C# project, could I use these methods with DllImport. If anybody need more explanation on how I archived this, send me a message.
If it is registered as a COM-Object, then you can import it via the Add Reference Dialog. Right click on the project --> Add Reference. Then select the Tab "COM" and you can select your COM Interface. Required Interop classes for .NET will be generated automatically.
you need to register the COM dll with the following line
regasm COMDll.DLL /tlb
then you can add it as a reference to your project.
I have completed some research and found that you can include a header file in an idl file in much the same way you do with C, below are the two links that show you how to include the header file in the idl and compile the idl to a tbl file which you can use in .Net:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa367049(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/aa367064(v=vs.85).aspx
I haven’t tried it and it looks like you may encounter some difficulty if the header file contains more than just the COM definitions, so good luck and please let me know how it went.

Marshaling unmanaged dll in C# through ComInterop without registering DLL

I have an unmanaged DLL which I'm currently calling from C# using a COM Class Wrapper.
[ComImport(), Guid("75E81043-CAD5-11D3-800D-00105A5E2FA0")]
public class MyObject { }
[ComImport(), Guid("75E81042-CAD5-11D3-800D-00105A5E2FA0"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface MyInterface
{
string EncryptString([In, MarshalAs(UnmanagedType.BStr)] string bstrOrginal);
}
Then to call:
MyInterface obj = (MyInterface)new MyObject();
string crypt = obj.EncryptString("something");
This works, the return value is as I expect. However, it requires that the dll is registered with regsvr32.
I'm looking for a way to do this without the requirement of needing to regsvr32. Preferably, by just having a copy of the dll available. It is worth noting, I have the source for the unmanaged dll, and the ability to modify it if necessary.
A shove in the right direction would be greatly appreciated.
I have wanted to do the same thing myself. As Jim mentions it is possible to get the address of DllRegisterServer and call it, but that still modifies your registry with the various entries. If you don't want to do this (for example, you might not have the necessary privileges to write to the Registry), then there is another way.
Any DLL that houses one or more in-process COM objects must expose the DllGetClassObject function. This function is used to acquire an instance of the COM class factory that is used to create a COM object. What you need to do is:
Load the library (DLL) that houses the desired COM object
Locate the DllGetClassObject function
Call DllGetClassObject, passing it the CLSID of the desired COM object, this will return an IClassFactory instance.
Call the CreateInstance method on the class factory to get an instance of the COM object.
Cast the returned object to the interface you wish to use.
Note that there be dragons with this approach -- it is fairly low level. If you get anything wrong you will experience access violation exceptions or worse. (For instance, your interface declaration has to exactly match the COM interface).
I have included some sample code at gist which you might like to use if you want to go this way.
Using this code would look something like this:
// Load the library. You dispose this after you are finished with
// all of your COM objects.
var library = new LibraryModule();
library.Load("mylibrary.dll"); or whatever your dll is called
var clsid = new Guid("75E81043-CAD5-11D3-800D-00105A5E2FA0");
var myObject = (MyInterface)ComHelper.CreateInstance(library, clsid);
Just note that if you dispose of the LibraryModule object, then this will unload the DLL. Depending on your needs, you might just assign the value to a static field so that it exists for the lifetime of the program.
You need to set up your unmanaged DLL for registration-free COM. There's a pretty complete walkthrough here, with plenty of examples. Among other things, it involves using manifests to point to the file, side-by-side assemblies, and some interop.
An important note in the walkthrough is that registration is required for the initial setup of the client, but not later.
If your DLL has to be a COM object, then it must be registered. This registration does not need to be performed by regsvr32. All regsvr32 does is load the DLL, get the address of DllRegisterServer and call it it. DllRegisterServer adds the necessary entries to the registry for an application to use the object.
If your DLL does not need to be a COM object, you can modify it to simply export the function(s) you need and p-invoke it(them).

C# COM Class - DISP_E_UNKNOWNNAME

I have declared a COM visible class in C#. The code is as follows:
[ComVisible(true)]
public class AComVisibleClass : TheParentClass
{
public bool SomeFunc(string id)
{
return true;
}
}
This class is instantiated by a factory class, also COM accessible.
But if I try to access in a VB script file, a DISP_E_UNKNOWNNAME exception is thrown.
This is a new class on a pre-existent library we have here at work. All other classes are accessible through COM. The whole library is compiled into a single assembly file. I have registered the new assembly using regasm, but I still get this exception.
I've tried to debug the COM call using VS2008. The factory class seems to be able to instantiate the AComVisibleClass. The aforementioned exception is thrown only when the factory tries to execute SomeFunc.
I know this may sound a little(?) bit vague, but I cannot expose the real code here. If someone need more information, please ask me.
I can think of three possible reasons for this problem:
Reason 1: Wrong name used in CreateObject:
I suppose that your VBScript code calls
something like
this:
Set obj = CreateObject("MyLibrary.AComVisibleClass")
If this is correct, then please open the registry editor and check whether the HKEY_CLASSES_ROOT key contains a subkey called MyLibrary.AComVisibleClass.
If it does not, then your library name possibly is different than you expected. Search the registry for AComVisibleClass to find the correct library name.
Reason 2: 64-bit issues:
If the problem happens on a 64-bit operating system, the reason could be that your VBScript runs as a 32-bit process and the C# COM DLL is 64-bit or vice versa.
Reason 3: Wrong function name:
You might be using the wrong function name in the script, e.g. obj.SomeFnc(1) instead of obj.SomeFunc(1), or the function name you have chosen is a reserved keyword in VBScript or it contains unusual characters.
Sounds like you need to support IDispatch.
Check out Does C# .NET support IDispatch late binding?
edit
This answer is likely wrong, and I may yet wind up deleting it. For now, it seems to add value, so I'll let it stay.

What's wrong with my Shared Add-in Constructor?

Good morning, fellows developers:
I'm currently trying to fix several performance issues of an Excel Shared Add-in inherited from a previous developer, basically I'm trying to find how the add-in stuff works internally in Excel, meaning I had searched the net for information an my understanding is:
In the registry the LoadBehaviour should be set to 3
The Excel Workbook during the open event should previously load all add-ins referenced in the VBA Project
Once the document is open my add-in should be available to be used by the VBA code.
Now I add Log4Net to the add-in and curiously enough I had seen the following behaviour
During the Open Event in the Excel Workbook there is a global variable
Public myAddin As Object
Set myAddin = New TradingAddin.TradingAddin
Thus the Contructor of the C# Class is called.
After a couple of seconds the constructor is called one more time and all the IDTExtensibility2 methods OnConnection, OnDisconnection, etc.. are called as expected.
I thought that once Excel loads the Add-in it should be available to the VBE Code and i could write something like
Set myAddin = Application.COMAddins.Item("Trading").Object
But it returns Nothing and calling the Constructor of the Class twice destroy any state saved inside the C# Object that should be available in memory during the life of the Excel Workbook.
Update:
The Platform is Visual Studio 2005 Team Edition and the Target Application is Excel 2003 and the Add-in is a Shared Add-in. I'm not using VSTO.
The actual code I was trying to invoke in VBA is
Set addIn = Application.COMAddIns.Item("K2Trading.K2Trading").Connect
Set managedObject3 = addIn.Object <--- This value that I thought was an Instance of the Add-in is equal to Nothing (NULL)
Set addIn = Application.COMAddIns("K2Trading.K2Trading").Connect
Also changing the LoadBehaviour in the registry to 3 from 2 loads the Add-in the first time correctly firing all Extensibility Event OnConnection, OnDisconecction and the Object Class Constructor, now I need to find a way for the invocation part from VBA of the Add-in, meaning how to connect the Add-in instance to a reference in VBA and from there call all the methods exposed through the Interface of the COM Object????
Also I double checked using ProcMon that the Add-in was found and loaded by Excel as per this link (very useful) http://blogs.msdn.com/dvespa/archive/2008/10/15/troubleshooting-outlook-com-addins-using-procmon.aspx .
Any ideas our perhaps pointing into the right direction?
How could I find out how many instances of the COM Object are loaded? or put into another words could be possible to have a single instance of a COM Object?
TIA,
Pedro
To Mike:
I tried your solution but I'v been facing Unspecified error (Exception from HRESULT: 0x80004005 (E_FAIL)) when executing this code
public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode,
object addInInst, ref System.Array custom)
{
object missing = System.Reflection.Missing.Value;
try
{
if (debug)
{
log.Debug("Connection Mode :" + connectMode);
}
this.excelApp = (Excel.Application)application;
this.addInInstance = addInInst;
Office.COMAddIn addIn = this.excelApp.COMAddIns.Item(ref addInInst);
//addIn.Object = this;
// We connect our Instance of the Add-in to the Arrya of COMAddins of Excel
VBA.Interaction.CallByName(addIn, "Object", VBA.CallType.Let, this);
^
|
The Exception occurs here.
You will noticed this looks a little different from you posted a while ago
public void OnConnection(
object application,
Extensibility.ext_ConnectMode connectMode,
object addInInst,
ref System.Array custom)
{
// Direct call fails b/c ".Object" is a late-bound call:
//
// addInInst.Object = this;
// Reflection fails I believe b/c .Object is a 'let' assigned
// property for reference type, which is very unusual even for
// COM (although legal) and is a foreign concept to .NET. Use
// of the right BindingFlags here *might* work, but I'm not sure:
//
// PropertyInfo propInfo;
// propInfo = addInInst.GetType().GetProperty("Object");
// propInfo.SetValue(addInInst, this, null);
// This works!:
VBA.Interaction.CallByName(addInInst, "Object", VBA.CallType.Let, this);
}
Because addInInst is not pass as a Office.COMAddin but a instance of my class, so
trying to assign to the Object Property is incorrect since it doesn't exists in that class
Also I'm curious about how Excel loads the Add-ins, what I mean by that is based on my observation when I just loaded Excel the OnConnection method didn't execute right away, but until I hit the =AvgCost() function ??
Any ideas?
How could I find out how many
instances of the COM Object are
loaded? or put into another words
could be possible to have a single
instance of a COM Object?
Do you mean COM add-in here? You cannot have the same COM add-in loaded into Excel more than once.
Now I need to find a way for the
invocation part from VBA of the
Add-in, meaning how to connect the
Add-in instance to a reference in VBA
and fro there call all the methods
exposed throught the Interface of the
COM Object????
VBA is all late-bound, it is not pre-compiled into a DLL like a VB6 DLL or a .NET assembly. Therefore, all of your VBA calls will have to be late bound as well. Easiest is to call Excel.Appliction.Run("NameOfYourVbaMacro") to call any macro stored within a standard module within your workbook. (You can access your workbook by name using Excel.Application.Workbooks["NameOfYourAddin.xla"]. You don't need to treat it as an add-in specifically, other than when you force it to load.) You can also use reflection code to access the workbook and worksheet members, if you have code behind either the ThisWorkbook class module or any of the Worksheet class modules.
I thought that once Excel loads the
Add-in it should be available to the
VBE Code and i could write something
like
Set myAddin =
Application.COMAddins.Item("Trading").Object
So, if I understand correctly, you want not only to have your C# managed COM add-in call VBA code (as discussed above), but now you also want to have VBA code to be able to call our C# managed COM add-in? I think this is a ton of complexity that probably needs some re-thinking... but it can be done.
Exposing your managed COM add-in to a COM caller via the ComAddin.Object property is tricky when done from C#, but doable. See the following discussion:
Calling Managed Add-In method from Automation client.
I hope this helps get you going...
Mike
Edit: Response to Pedro's Reply
Pedro,
You will noticed this looks alittle
different from you posted a while
ago...
Yes, your code is different and is why it does not work!
At a minimum, your last line looks incorrect:
VBA.Interaction.CallByName(addIn, "Object", VBA.CallType.Let, this);
Instead, your code should be passing in your class to the 'addInInst':
VBA.Interaction.CallByName(addInInst, "Object", VBA.CallType.Let, this);
And I'm not sure what you are trying to do by this line:
Office.COMAddIn addIn = this.excelApp.COMAddIns.Item(ref addInInst);
That line looks like it should throw an exception. I am very surprised that it does not. But code similar to that -- where you pass in the progId for the COM add-in you wish to access -- is typically used by an external caller that wishes to access your COM Add-in via the .Object property. This is not code that should be used from within the add-in itself.
Because addInInst is not pass as a
Office.COMAddin but a instance of my
class, so trying to assign to the
Object Property is incorrect since it
doesn't exists in that class
I don't understand what you are trying to do here. I don't know if you can pass in any other object other than the instance of the class that implements IDTExtensibility2. At a minimum, whatever class you do pass back must be be a COM visible class by using the correct class and interface attributes. But I think that it is far easier to stick to the standard practice of passing in the class that implements IDTExtensibility2 from within the OnConnection method. That is, pass in your 'this' object reference.
If you want to try fancy things that go beyond the standard approach, that's ok, but I would get a simple example working first. Try to replicate the code that I show in the example Calling Managed Add-In method from Automation client. Once you have that working, you can try to move onto more sophisticated operations. But once you have the simple version working, I think you'll find that this is all that you need.
I hope this helps Pedro,
Mike

Categories