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.
Related
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.
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.
I am working on WP8 project that includes class library project as C# source code and Windows Runtime Component as C++ source code. Does anyone know whether or not it is possible to create such C# class library which would reference Windows Runtime Component? The ultimate result should be .NET assembly and .WIMND/.DLL runtime component that can be used for application. Currently I cannot build class library because it doesn't see Windows Runtime Component, even though I added it to the project.
More specific. I have, say, MyNs.MyClass.MyMethod() which is defined in C++ runtime component and used from C# class library. Currently I cannot compile C# due to missing method although I have windows runtime component project attached to the same solution.
Although I am butting in because this is not my area, I tried Googling for "c# call windows runtime component". There seem to be many hits/examples, e.g. the first one is https://msdn.microsoft.com/en-us/library/hh755833.aspx.
Does that not help you?
I solved this by adding reference to Windows runtime component manually into the C# class library .csproj file as follows
...
<ItemGroup>
<Reference Include="WindowsRuntimeComponent.winmd" />
</ItemGroup>
...
I managed to make a C++ WRL project and use a class in that project from a C# project by adding a reference in the normal way. The Wrl project (not C++/CX, which also works) was made using some WRL template that I found somewhere on the web. The wrl project required me to make a .idl to define the interface, and produced its .dll and .winmd. Here is some code for those who are battling with this type of thing:
The Wrl class:
#include "pch.h"
#include "WrlTestClass2_h.h"
#include <wrl.h>
using namespace Microsoft::WRL;
using namespace Windows::Foundation;
namespace ABI
{
namespace WrlTestClass2
{
class WinRTClass: public RuntimeClass<IWinRTClass>
{
InspectableClass(RuntimeClass_WrlTestClass2_WinRTClass, BaseTrust)
public:
WinRTClass()
{
}
// http://msdn.microsoft.com/en-us/library/jj155856.aspx
// Walkthrough: Creating a Basic Windows Runtime Component Using WRL
HRESULT __stdcall Add(_In_ int a, _In_ int b, _Out_ int* value)
{
if (value == nullptr)
{
return E_POINTER;
}
*value = a + b;
return S_OK;
}
};
ActivatableClass(WinRTClass);
}
}
The C# code that uses this class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
namespace CSharpClientToWrl
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
WrlTestClass2.WinRTClass _winRtTestClass = new WrlTestClass2.WinRTClass();
int _answer = _winRtTestClass.Add(4, 6);
Assert.AreEqual(_answer, 10);
}
}
}
The .idl file of the wrl project:
import "inspectable.idl"; import "Windows.Foundation.idl";
#define COMPONENT_VERSION 1.0
namespace WrlTestClass2 {
interface IWinRTClass;
runtimeclass WinRTClass;
[uuid(0be9429f-2c7a-40e8-bb0a-85bcb1749367), version(COMPONENT_VERSION)]
interface IWinRTClass : IInspectable
{ // http://msdn.microsoft.com/en-us/library/jj155856.aspx // Walkthrough: Creating a Basic Windows Runtime Component Using WRL HRESULT Add([in] int a, [in] int b, [out, retval] int* value);
}
[version(COMPONENT_VERSION), activatable(COMPONENT_VERSION)]
runtimeclass WinRTClass
{
[default] interface IWinRTClass;
} }
I've been working with trying to link up some c++ code and wrap it inside a COM object to access via C#. I created an atl project and added a simple method such as Add(double a, double b). The following is the code from my atl.h file:
// atl.h : Declaration of the Catl
#pragma once
#include "resource.h" // main symbols
#include "com_i.h"
#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
#endif
// Catl
class ATL_NO_VTABLE Catl :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<Catl, &CLSID_atl>,
public Iatl
{
public:
Catl()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_ATL)
DECLARE_NOT_AGGREGATABLE(Catl)
BEGIN_COM_MAP(Catl)
COM_INTERFACE_ENTRY(Iatl)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
public:
STDMETHOD(Add)(DOUBLE a, DOUBLE b);
};
OBJECT_ENTRY_AUTO(__uuidof(atl), Catl)
The following is from the atl.cpp file
// atl.cpp : Implementation of Catl
#include "stdafx.h"
#include "atl.h"
STDMETHODIMP Catl::Add(DOUBLE a, DOUBLE b)
{
// TODO: Add your implementation code here
return a + b;
}
Inside my c# file I'm calling the dll... after i referenced it... it sees the dll but not the methods assigned. which is my problem. Heres the code from program.cs
sing System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace sharpdll
{
class Program
{
[DllImport("com.dll")]
public static extern double Add(double a, double b);
static void Main(string[] args)
{
Add(2, 3);
}
}
}
Debugging breaks at Add(2, 3);
Says "Unable to find an entry point named 'Add' in DLL 'com.dll'."
Any ideas?
DllImport is for PInvoke (to native Win32 dlls).
You want COM Interop.
Register your ATL com object, then Add a reference to it, as you would to any .Net or COM component.
An alternative to ATL, you can expose your C++ functionality through C++/CLI.
Hope this helps,
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.