I'm trying to call my C# dll from a C++ client, so far I have the dll all setup and in my registry (I can create and call it from the power shell for example).
The problem I'm having is that I can't call it from my C++ code.
My C# interface:
namespace MyInterop
{
[Guid("BE507380-1997-4BC0-AF01-EE5D3D537E6B"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IMyDotNetInterface
{
void ShowCOMDialog();
}
}
My C# class that implements the interface:
namespace MyInterop
{
[Guid("38939B1E-461C-4825-80BB-725DC7A88836"), ClassInterface(ClassInterfaceType.None)]
public class MyDotNetClass : IMyDotNetInterface
{
public MyDotNetClass()
{ }
public void ShowCOMDialog()
{
MessageBox.Show("I am a" +
" Managed DotNET C# COM Object Dialog");
}
}
}
Very simple as I'm just testing at the moment. I now import the tlb into my C++ file
#import "<Path to file>\MyInterop.tlb" raw_interfaces_only
Finally I try to call it:
HRESULT hr = CoInitialize(NULL);
MyInterop::IMyDotNetInterfacePtr MyDotNetClass(__uuidof(MyInterop::MyDotNetClass));
MyDotNetClass->ShowCOMDialog();
CoUninitialize();
But, VS is telling me that ShowCOMDialog is not a member of my interface. Have I missed something?
By declaring raw_interfaces_only, you have surpressed the generation of wrapper functions, as indicated in this link. And since your interface is based on IDispatch, you are forced to call your interface methods indirectly via IDispatch's Invoke.
Suggestions:
Change your interface type to ComInterfaceType.InterfaceIsDual or ComInterfaceType.InterfaceIsIUnknown
Remove raw_interfaces_only in order to work with the generated wrapper functions.
Related
I'm trying to interact with a piece of hardware using my own code. The vendor offers a dll on his website with some sample code in C#, the DLL seems to be compiled from Visual Basic.
I'm wondering how to use this DLL in C++. Is C++/CLI the only way to do this? Or is there some other way that allows me to keep my code Cross-Platform compatible?
simple example: suppose we have a managed class Hello in a .NET assembly (hello.dll).
//This is c# but it could be also vb. Compilator of c# and VB.net translate it to same thing .Net assembly dll. Which is internaly exactly the same as c# dll.
class Hello
{
public void HelloWorld()
{
Console.Writeline("Hello World!");
}
}
Now we need to define a native proxy class for Hello:
class Hello
{
public:
Hello() : wrapper_("hello.dll", "namespace name") {}
void HelloWorld()
{
wrapper_("Hello");
}
private:
nativeAdapter::NativeProxy wrapper_;
};
}
In our main function, we can use the proxy as if it were the managed class:
int main(int, char **)
{
Namespace::Hello hello;
hello.Hello();
return 0;
}
I have written a simple COM object in C# with only one method, which is called GetMac. I can't get it to work. I am trying to access it from a legacy Borland C++ Builder 4 (BCB4) application, which I know is old, and not used much anymore, but I am able to access other COM objects from it fine.
The Borland development machine is running Windows XP, so I make the C# COM object target the .NET 4.0 framework. I copied the DLL and PDB file over from the C# Visual Studio machine to the XP machine. I registered it via the following command:
"%WINDIR%\Microsoft.NET\Framework\v4.0.30319\regasm.exe" TRSDotNetCOM.dll /tlb /nologo /codebase
I am able to instantiate the COM object (class) fine via the following line of code:
Variant TDN = CreateOleObject("TRSDotNetCOM.TRSCOM_Class");
If I change the name string, it doesn't work, so I know I have this part correct.
However, when I try to call the method as follows:
MacV = TDN.OleFunction(funcNameV,counterV,macKeyV);
... I get a runtime exception (unfortunately, there's an issue with BCB4's exception handling for OLE calls, so the only info the debugger gives me is "Exception Occurred").
Since I am able to call other COM objects from the same BCB4 application in the same manner, I don't think the problem is with my C++ code. I think it is an issue with either the C#-created COM DLL, or the registration thereof.
To explore this, I used Microsoft OLE/COM Object Viewer to browse my system for the OLE object. I was able to find my object as "TRSDotNetCOM.TRSCOM_Class", as expected.
I'm brand new at using the OLE/COM Object Viewer, so I hope I am looking at the right things below:
When I expand the class, I see the following:
I right-clicked on _Object and chose "View", then "View Type Info". Then, the pane on the right shows:
[ uuid(65074F7F-63C0-304E-AF0A-D51741CB4A8D), hidden, dual, nonextensible,
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "System.Object")
] dispinterface _Object {
properties:
methods:
[id(00000000), propget,
custom({54FC8F55-38DE-4703-9C4E-250351302B1C}, "1")]
BSTR ToString();
[id(0x60020001)]
VARIANT_BOOL Equals([in] VARIANT obj);
[id(0x60020002)]
long GetHashCode();
[id(0x60020003)]
_Type* GetType(); };
When I expand the tree on the left, this is what I see:
I do not see my method "GetMac" listed anywhere in there. So, I'm thinking that somehow the method is not visible to COM, or that it's not getting registered via regasm.
Here is the source for the COM object:
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace TRSDotNetCOM
{
[Guid("80ef9acd-3a75-4fcd-b841-11199d827e8f")]
public interface TRSCOM_Interface
{
[DispId(1)]
string GetMac(string counter, string macKey);
}
// Events interface Database_COMObjectEvents
[Guid("67bd8422-9641-4675-acda-3dfc3c911a07"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface TRSCOM_Events
{
}
[Guid("854dee72-83a7-4902-ab50-5c7a73a7e17d"),
ClassInterface(ClassInterfaceType.None),
ComVisible(true),
ComSourceInterfaces(typeof(TRSCOM_Events))]
public class TRSCOM_Class : TRSCOM_Interface
{
public TRSCOM_Class()
{
}
[ComVisible(true)]
public string GetMac(string counter, string macKey)
{
// convert counter to bytes
var counterBytes = Encoding.UTF8.GetBytes(counter);
// import AES 128 MAC_KEY
byte[] macKeyBytes = Convert.FromBase64String(macKey);
var hmac = new HMACSHA256(macKeyBytes);
var macBytes = hmac.ComputeHash(counterBytes);
var retval = Convert.ToBase64String(macBytes);
return retval;
}
}
}
I did make sure and go into the project properties and check the "Register for COM interop" checkbox. I also generated a Secure Name file with the "sn" utility, and loaded the file in the Signing section of settings.
So...
1) Am I looking in the correct place in the OLE/COM Object Viewer for my method?
2) If so, why would my method not be visible or not get registered?
3) Any ideas of what else could be wrong?
UPDATE: Here is the updated code with Joe W's and Paulo's suggestions. (It still does not work however)
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace TRSDotNetCOM
{
[Guid("80ef9acd-3a75-4fcd-b841-11199d827e8f"),
ComVisible(true)]
public interface TRSCOM_Interface
{
[DispId(1)]
string GetMac(string counter, string macKey);
}
// Events interface Database_COMObjectEvents
[Guid("67bd8422-9641-4675-acda-3dfc3c911a07"),
ComImport,
ComVisible(true),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface TRSCOM_Events
{
}
[Guid("854dee72-83a7-4902-ab50-5c7a73a7e17d"),
ClassInterface(ClassInterfaceType.None),
ComDefaultInterface(typeof(TRSCOM_Interface)),
ComVisible(true),
ComSourceInterfaces(typeof(TRSCOM_Events))]
public class TRSCOM_Class : TRSCOM_Interface
{
public TRSCOM_Class()
{
}
public string GetMac(string counter, string macKey)
{
// convert counter to bytes
var counterBytes = Encoding.UTF8.GetBytes(counter);
// import AES 128 MAC_KEY
byte[] macKeyBytes = Convert.FromBase64String(macKey);
var hmac = new HMACSHA256(macKeyBytes);
var macBytes = hmac.ComputeHash(counterBytes);
var retval = Convert.ToBase64String(macBytes);
return retval;
}
}
}
You're missing just a few bits.
Declare your interfaces as ComVisible:
[ComVisible(true)]
public interface TRSCOM_Interface
If your assembly is already COM visible by default (you can check this in the project's properties or typically in AssemblyInfo.cs), you don't need to do this, but it does no harm and it'll keep the interface available for regasm.exe and tlbexp.exe in case you revert this configuration.
Declare the events interface as ComImport:
[ComImport]
public interface TRSCOM_Events
My guess here is that this interface is defined outside your C# project, probably by the BCB4 application or one of its modules.
If my guess is wrong and your C# project is the one defining this interface, then [ComVisible(true)].
If this interface has event methods, you then implement then as events in the class.
Finally, to avoid having another interface exported for your class, you may want to add the ClassInterface attribute:
[ClassInterface(ClassInterfaceType.None)]
public class TRSCOM_Class : TRSCOM_Interface
This way, you're telling that TRSCOM_Interface is your class default interface, as it is the first one you implement, and regasm.exe /tlb won't generate a class interface.
Depending on the order of implemented interfaces is not reassuring, so you can also use the ComDefaultInterface attribute:
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(TRSCOM_Interface))]
public class TRSCOM_Class : TRSCOM_Interface
Now, you can have any order in the implemented interfaces list without worrying about changing the default class interface.
That is the first time I have ever seen a method declared ComVisible. I would forgo that and instead declare the TRSCOM_Interface interface ComVisible.
Here is my other question that led into this one as reference also:
How to call managed C++ methods from Un-managed C++
I have successfully created a C# COM File. Now I need a simple explanation on how to implement it in unmanaged C++.
I am following this example but the c++ part is weak.
http://www.codeproject.com/Articles/7859/Building-COM-Objects-in-C
Here is my COM file
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace cSharpRiJHarn
{
[Guid("ED1483A3-000A-41f5-B1BC-5235F5897872")]
public interface DBCOM_Interface
{
[DispId(1)]
String encrypt(string s);
[DispId(2)]
String decrpyt(string s);
}
[Guid("A6BCEC1D-B60C-4c97-B9AD-1FE72642A9F8"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface DBCOM_Events
{
}
[Guid("7C13A8C6-4230-445f-8C77-0CA5EDECDCB5"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(DBCOM_Events))]
public class RijndaelLink : DBCOM_Interface
{
public String encrypt(String s)
{
return Rijndael.EncryptString(s);
}
public String decrpyt(String s)
{
return Rijndael.DecryptString(s);
}
}
}
I just want a VERY basic example on using this with unmanaged code.
Please include in your answers:
Do I need to include the project or just the source files of the COM
Do I need to add a reference
A very basic example of passing a string and printing it out with cout.
Thanks for your help!
The first thing you need to do is properly define the COM object in .NET, to be used by the unmanaged world (C++ or other). Here is a decent definition:
namespace cSharpRiJHarn
{
[Guid("ED1483A3-000A-41f5-B1BC-5235F5897872")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
public interface IRijndaelLink
{
string encrypt(string s);
string decrypt(string s);
}
[Guid("7C13A8C6-4230-445f-8C77-0CA5EDECDCB5")]
[ComVisible(true)]
public class RijndaelLink : IRijndaelLink
{
public string encrypt(string s)
{
return Rijndael.EncryptString(s);
}
public string decrypt(string s)
{
return Rijndael.DecryptString(s);
}
}
}
Next, you need to register this .NET assembly for COM using the RegAsm tool. I suggest also you build a Type Library (.TLB) with it, something like this (I suppose you build the whole stuff for X86, not X64):
c:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe YourAssembly.dll /tlb:YourAssembly.tlb /codebase
Please adapt to your actual path. Also check the codebase arg as you may not need this in production.
This will build a .TLB file with both the interface and the class inside. It works because we added the ComVisible attribute. You will also note I have not defined a Dispatch or Dual interface because in this sample, I don't need COM Automation (VB, VBA) nor any scripting language (VBScript, JScript) support, only IUnknown interfaces which are much easier to use in plain C/C++ than IDispatch interfaces.
Now, there is an easy way to import that in the unmanaged c++ world using a Microsoft specific C++ extension: #import Directive, similar to Add References in .NET. Here is a sample Console Application that uses the COM Object:
#include "stdafx.h"
#import "c:\MyPathToTheTlb\YourAssembly.tlb" // import the COM TLB
using namespace YourAssembly;
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL); // needed to enter COM space
IRijndaelLinkPtr ptr(__uuidof(RijndaelLink)); // create the COM Object with the desired interface
_bstr_t s = ptr->encrypt("hello"); // call the function
printf("%S", (LPWSTR)s); // for example
CoUninitialize();
return 0;
}
You will notice the #import directive also create cool wrappers (_bstr_t, as .NET String will be exported as Automation BSTR here, even for IUnknown interfaces) for string handling, so it's no really big deal.
This is not the only way all this can work, but that's IMHO one of the most simple.
I have an issue with C# and COM.
[Guid("f7d936ba-d816-48d2-9bfc-c18be6873b4d")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Process : IProcess
{
public Process()
{
}
public int UpdateBalance(string accountNumber, string adminEventDescription, decimal curAmount)
{
return 10;
}
}
[ComVisible(true)]
[Guid("5c640a0f-0dce-47d4-87df-07cee3b9a1f9")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IProcess
{
int UpdateBalance(string accountNumber, string adminEventDescription, decimal curAmount);
}
And the VB code
Private Sub Command1_Click()
Dim test As Object
Set test = New Forwardslash_PlayerTrackingSystem_Api.Process
End Sub
I get the following,
ActiveX component can't create object?
Any ideas on how to fix the issue?
Have you ticked the "Register for COM interop" box in the project properties?
Do you have the ProgID Forwardslash_PlayerTrackingSystem_Api.Process defined in the C# source as well? Your example code does not seem to include it. (Or are you working with an existing type library and creating the object in VB by GUID somehow?)
And is the C# component registered correctly in the registry on the machine where the VB code runs? See the answer by Paolo for a way to have VisualStudio do this for you when you build and/or register it yourself using the regasm.exe tool. This tool is equivalent to regsrv32.exe for "real" COM objects, but then registers an appropriately built .NET assembly in the registry for use from COM.
Your [InterfaceType] attribute is wrong. VB6 requires an IDispatch interface, it cannot handle an IUnknown interface. It likes ComInterfaceType.InterfaceIsDual best, that produces a full type library, enables IntelliSense in the VB6 editor and is roughly a 1000 times faster than the late-bound IDispatch.
Using regasm's /codebase switch is mandatory if the assembly is not registered in GAC.
i'm converting C++ to C++/CLI and would like to expose some managed classes as COM objects. In C# it was easy and setting [ComVisible] & inheriting from interface (also ComVisible) did the job.
However C++ project build as C++/CLI does not export DllRegisterServer.
Here is sample project (started from CLR Console Application project in VS 2008).
#include "stdafx.h"
using namespace System;
using namespace System::Runtime::InteropServices;
[ComVisible(true)]
[Guid("E3CF8A18-E4A0-4bc3-894E-E9C8648DC1F0")]
[InterfaceType(ComInterfaceType::InterfaceIsDual)]
public interface class ITestInterface
{
void TestMethod();
};
[ComVisible(true)]
[Guid("1514adf6-7cb0-4561-9fbb-b75c0467149b")]
public ref class CliComClass : ITestInterface
{
public:
virtual void TestMethod()
{
}
};
int main(array<System::String ^> ^args)
{
Console::WriteLine(L"Hello World");
return 0;
}
When I run regsvr32 on output .exe I got error saying DllRegisterServer was not found. I've tried google for some help but with no success.
You need to use TlbExp instead, TlbExp is the tool use to export managed classes to COM, it will read the assembly find the ComVisible type and register them.