I've been doing quite a bit of googling trying to find the standard way to get an instance of a COM interface.
Microsoft provides an example of this in their article COM Interop Part 1: Client Tutorial:
// Create an instance of a COM coclass:
FilgraphManager graphManager = new FilgraphManager();
// See if it supports the IMediaControl COM interface.
// Note that this will throw a System.InvalidCastException if
// the cast fails. This is equivalent to QueryInterface for
// COM objects:
IMediaControl mc = (IMediaControl) graphManager;
// Now you call a method on a COM interface:
mc.Run();
However, it appears as though they are instantiating a COM object and casting it to a COM interface.
For the interface I am interested in, IDesktopWallpaper, there does not seem to be an implementing COM object to instantiate.
An example I found here defines some class that gets instantiated and then casts it to the interface the same way that the msdn example does:
[ComImport, Guid("C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD")]
internal class IDesktopWallpaper
{
}
[Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B"), //B92B56A9-8B55-4E14-9A89-0199BBB6F93B
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface DesktopWallpaperInterface
{
// declared members
}
I'm not understanding what the instantiated object is. It seems like an arbitrary object, it has a GuidAttribute which seems to indicate that it is an actual COM object.
Another example i found here System.Type and System.Runtime.InteropServices.Marshal to instantiate an object and then casts it to the interface:
IntPtr ptrRet;
SHGetMalloc(out ptrRet);
System.Type mallocType = System.Type.GetType("IMalloc");
Object obj = Marshal.GetTypedObjectForIUnknown(ptrRet,mallocType);
IMalloc pMalloc = (IMalloc)obj;
This method seems to be requesting a pointer to an existing instance of the interface. I can't find any methods like SHGetMalloc for IDesktopWallpaper in the Windows Shell documentation.
Question
So, long story short, what's the standard way to get an instance of a COM interface?
In the event that there is no one-size-fits-all solution, what are the standard ways that one can use to get an instance of a COM interface and in what circumstance is each of these ways most useful?
Edit
After downloading the Windows 10 SDK and referencing that against the Requirements section of the IDesktopWallpaper interface documentation, I have discovered that you can look up the MIDL from Shobjidl.h and use that in the GuidAttribute for your interface declaration and then look up the CLSID from Shobjidl.idl and use that in conjunction with Type.GetTypeFromCLSID(Guid) and Activator.CreateInstance(Type) to get an instance of an object that implements IDesktopWallpaper.
I also see now that the CLSID is what is used in the second method listed above for the GuidAttribute of the seemingly arbitrary object. It seems like this method allows you to mimic managed instantiation of the object by instantiating the class and then casting the instance to the COM interface.
However
I am still interested to know if this is the best way to do this and what pros and cons may be associated with this method vs others.
You can get a pointer to a COM object reference by a variety of methods:
P/Invoke CoCreateInstance
P/Invoke CLSIDFromProgID → CoCreateInstance
P/Invoke IRunningObjectTable.GetObject
Type.GetTypeFromCLSID → Activator.CreateInstance
Type.GetTypeFromProgID → Activator.CreateInstance
new SomeType() where SomeType is marked with ComImport
Activator.CreateInstance and new SomeType() eventually hit CoCreateInstance (if they do not get intercepted by various in-app-domain stuff). Calls to CoCreateInstance for an out-of-process server will eventually hit IRunningObjectTable with a class moniker (I think). The best option depends on what you are trying to do:
For an in-process server, just use ComImport
For an out-of-process server that is not implemented in .Net, ComImport will work, I would prefer to call CoCreateInstance to pass the right CLSCTX.
For a .net implemented out-of-process server implemented in .Net, you must call CoCreateInstance directly to avoid the "optimizations" added by ComImport that would result in the server being run in-process
If you are dealing with a moniker, use IRunningObjectTable
If you are starting out with a ProgID rather than a CLSID, use either CLSIDFromProgID or Type.GetTypeFromProgID
Regardless of how we get a reference to an object, we start out with IUnknown (object in .Net), and then have to call IUnknown->QueryInterface to get a pointer to a particular interface. Calling QueryInterface in .Net is effected by casting to an interface marked as ComVisible (and usually annotated with GuidAttribute).
In the example you named, you would end up with:
// based off of https://bitbucket.org/ciniml/desktopwallpaper
[ComImport]
[Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDesktopWallpaper
{
void SetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID, [MarshalAs(UnmanagedType.LPWStr)] string wallpaper);
[return: MarshalAs(UnmanagedType.LPWStr)]
string GetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID);
[return: MarshalAs(UnmanagedType.LPWStr)]
string GetMonitorDevicePathAt(uint monitorIndex);
[return: MarshalAs(UnmanagedType.U4)]
uint GetMonitorDevicePathCount();
[return: MarshalAs(UnmanagedType.Struct)]
Rect GetMonitorRECT([MarshalAs(UnmanagedType.LPWStr)] string monitorID);
void SetBackgroundColor([MarshalAs(UnmanagedType.U4)] uint color);
[return: MarshalAs(UnmanagedType.U4)]
uint GetBackgroundColor();
void SetPosition([MarshalAs(UnmanagedType.I4)] DesktopWallpaperPosition position);
[return: MarshalAs(UnmanagedType.I4)]
DesktopWallpaperPosition GetPosition();
void SetSlideshow(IntPtr items);
IntPtr GetSlideshow();
void SetSlideshowOptions(DesktopSlideshowDirection options, uint slideshowTick);
void GetSlideshowOptions(out DesktopSlideshowDirection options, out uint slideshowTick);
void AdvanceSlideshow([MarshalAs(UnmanagedType.LPWStr)] string monitorID, [MarshalAs(UnmanagedType.I4)] DesktopSlideshowDirection direction);
DesktopSlideshowDirection GetStatus();
bool Enable();
}
[ComImport]
[Guid("C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD")]
public class DesktopWallpaper
{
}
[Flags]
public enum DesktopSlideshowOptions
{
None = 0,
ShuffleImages = 0x01
}
[Flags]
public enum DesktopSlideshowState
{
None = 0,
Enabled = 0x01,
Slideshow = 0x02,
DisabledByRemoteSession = 0x04
}
public enum DesktopSlideshowDirection
{
Forward = 0,
Backward = 1
}
public enum DesktopWallpaperPosition
{
Center = 0,
Tile = 1,
Stretch = 2,
Fit = 3,
Fill = 4,
Span = 5,
}
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
An example use of which would be:
public partial class Form1 : Form
{
private IDesktopWallpaper Wallpaper;
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.Wallpaper = (IDesktopWallpaper)new DesktopWallpaper();
uint monitorCount = Wallpaper.GetMonitorDevicePathCount();
for (uint i = 0; i < monitorCount; i++)
{
lbMonitors.Items.Add(Wallpaper.GetMonitorDevicePathAt(i));
}
}
private void lbMonitors_SelectedValueChanged(object sender, EventArgs e)
{
var path = (string)lbMonitors.SelectedItem;
tbWallpaper.Text = Wallpaper.GetWallpaper(path);
}
}
Which produces the form:
Related
I'm working with DirectShow framework and trying to capture video from my webcam. Using the "ComImport" and "Guid" attributes, I import COM interfaces. When accessing the put_Owner() method of the IVideoWindow interface, I get an exception. In other interfaces, methods are called and return the S_OK result. Why can't it find a method with the given name?
Interface declaration:
[ComVisible(true)]
[ComImport]
[Guid(Constants.IVideoWindowGuid)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IVideoWindow {
[PreserveSig]
public HResult put_Owner(IntPtr owner);
}
I call the method as follows:
hResult = _videoWindow.put_Owner(Window);
if(hResult != HResult.S_OK)
throw new Exception(hResult.ToString());
I tried to capture a video in a console application, but there was no result, so I decided to create a WPF application from which I get a HWND handle. According to my logic, the video should be displayed in the WPF window, but the put_Owner method cannot be found.
Thanks
Edited.
_videoWindow is a member of the IVideoWindow interface (the declaration is described above), which in my case is a private field.
private IMediaControl _mediaControl;
private IMediaEventEx _mediaEvent;
private IVideoWindow _videoWindow;
Initialization occurs as follows:
_graphBuilder = (IGraphBuilder)(new FilterGraph());
_captureGraphBuilder = (ICaptureGraphBuilder2)(new CaptureGraphBuilder2());
_mediaControl = (IMediaControl) _graphBuilder;
_mediaEvent = (IMediaEventEx) _graphBuilder;
_videoWindow = (IVideoWindow) _graphBuilder;
For comparison, the methods of the objects _mediaControl and _mediaEvent are called successfully and return HRESULT S_OK result.
The IVideoWindow is a dual interface, meaning it supports an IUnknown-derived interface and an IDispatch interface.
When you declare this on an IDispatch interface:
[PreserveSig]
public HResult put_Owner(IntPtr owner);
It means the interface is supposed to declare an "put_Owner" named method with a parameter. This is not the case as the IVideoWindow definition in IDL format (you can see that with the OleViewer tool from Windows SDK) is:
dispinterface IVideoWindow
{
...
[id(0x60020016), propput]
void Owner([in] LONG_PTR rhs);
[id(0x60020016), propget]
LONG_PTR Owner();
...
}
Which means if you want to declare it as an IDispatch interface in C#, you must do this:
[ComImport, Guid("56a868b4-0ad4-11ce-b03a-0020af0ba770"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IVideoWindow
{
// the name "Owner" here *must* match idl's name
IntPtr Owner { get; set; }
}
Note the name is important, but this could work too:
[ComImport, Guid("56a868b4-0ad4-11ce-b03a-0020af0ba770"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IVideoWindow
{
// the name here doesn'the match idl's name so the DispId *must* match idl's pidl
[DispId(0x60020016)]
IntPtr Blah { get; set; }
}
Or you can declare it using the IUnknown-derived version, but in this case you must stick exactly to the binary contract, which would be something like this:
// note this InterfaceIsIUnknown here
[ComImport, Guid("56a868b4-0ad4-11ce-b03a-0020af0ba770"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IVideoWindow
{
// you *must* declare methods in same order as they are defined in .idl or .h to match IVideoWindow vtable
// or use the VtblGap trick
void _VtblGap0_26(); // skip 4 (IDispatch) + 22 IViewWindow methods
// note: names are irrelevant here, it's a binary contract
[PreserveSig]
int put_Owner(IntPtr owner);
[PreserveSig]
int get_Owner(out IntPtr owner);
}
Note here I used the VtblGap trick to avoid declaring all methods I don't need.
Another possibility, probably the most simple way, is just to create a COM reference to the "ActiveMovie control type library", located in %windir\System32\quartz.dll. In this case, you can use it like this:
var gm = new QuartzTypeLib.FilgraphManager();
var win = (QuartzTypeLib.IVideoWindow)gm;
win.Owner = ...
I have a C++ out-of-process COM server that hosts a lot of C# code to support the API exposed by the C++ COM objects.
For a variety of reasons, I am considering eliminating the C++ portion of my solution. However, because of constraints outside of my control I have to retain the out-of-process COM server. Microsoft does have a canonical example of this here.
Looking at this example there is something I don't understand. Before the message loop starts, a timer is created to call GC.Collect every 5 seconds. The only mention of this that I can find indicates it's to ensure the COM objects are released in a reasonable timeframe. I'm a little confused about this...does my C++ host currently call GC.Collect automatically? I'm certainly not doing it. And yet I am creating managed objects (with COMVisible(true) as COM objects in the C++ code. Does that mean I should be calling GC.Collect every 5 seconds now? If not, why do I need to call it in this new C# out of process server. Is that to make up for the automatic process that cleans up unreferenced COM objects in a normal C++ application? (Which I assume is happening sometime during the message loop.)
Calling GC.Collect every 5 seconds seems like it could be a bad idea. Am I wrong to worry? Is there some other method by which I could achieve the same results?
I am using .NET 4.5 and Visual Studio 2012.
IMO, the easiest way to create a COM out-of-proc server in C# is to use a DLL surrogate process.
You're still limited to dual interfaces (ComInterfaceType.InterfaceIsDual), and you'd need to register the generated type library (and do it as part of deployment, too):
C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe ManagedServer.dll /codebase /tlb
This will allow you to utilize the COM type library marshaller, because you don't have a dedicated COM proxy/stuf DLL for your C# COM objects.
Make sure to use the correct RegAsm.exe binary, depending on the target bit-ness of your ManagedServer.dll assembly. The above assumes x86 code.
Here is a complete working template example. It takes care of the surrogate registration:
using Microsoft.Win32;
using System;
using System.Runtime.InteropServices;
namespace ManagedServer
{
[ComVisible(true), Guid("1891CF89-1282-4CA8-B7C5-F2608AF1E2F1")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IManagedComObject
{
string ComMethod(string data);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IManagedComObject))]
[Guid("989162CD-A6A6-4A7D-A7FB-C94086A4E90A")]
[ProgId("Noseratio.ManagedComObject")]
public class ManagedComObject : IManagedComObject
{
// public constructor
public ManagedComObject()
{
}
// IManagedComObject
public string ComMethod(string data)
{
return data;
}
// registration
[ComRegisterFunction()]
public static void Register(Type type)
{
var guid = type.GUID.ToString("B");
using (var appIdKey = Registry.ClassesRoot.CreateSubKey(#"AppID\" + guid))
{
appIdKey.SetValue("DllSurrogate", String.Empty);
}
using (var appIdKey = Registry.ClassesRoot.CreateSubKey(#"CLSID\" + guid))
{
appIdKey.SetValue("AppId", guid);
}
}
[ComUnregisterFunction()]
public static void Unregister(Type type)
{
var guid = type.GUID.ToString("B");
using (var appIdKey = Registry.ClassesRoot.OpenSubKey(#"AppID\" + guid, writable: true))
{
if (appIdKey != null)
appIdKey.DeleteValue("DllSurrogate", throwOnMissingValue: false);
}
Registry.ClassesRoot.DeleteSubKeyTree(#"CLSID\" + guid, throwOnMissingSubKey: false);
}
}
}
By default, the objects will be created in MTA apartment, so the interface methods may possibly be called on any thread, you'd need to implement thread safety.
If you need an STA thread with message pump inside the surrogate process for your objects, you could do that explicitly by implementing a factory singleton and using CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStream to export objects outside the STA thread (this might be related).
Another point, your COM client code should use CLSCTX_LOCAL_SERVER when creating this an instance of ManagedComObject. This is not the case with Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.ManagedComObject")), which apparently uses CLSCTX_ALL. This can be easily taken care of:
using System;
using System.Runtime.InteropServices;
namespace Client
{
class Program
{
static void Main(string[] args)
{
// dynamic obj = Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.ManagedComObject"));
dynamic obj = ComExt.CreateInstance(
Type.GetTypeFromProgID("Noseratio.ManagedComObject").GUID,
localServer: true);
Console.WriteLine(obj.ComMethod("hello"));
}
}
// COM interop
public static class ComExt
{
const uint CLSCTX_LOCAL_SERVER = 0x4;
const uint CLSCTX_INPROC_SERVER = 0x1;
static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");
[DllImport("ole32.dll", ExactSpelling = true, PreserveSig = false)]
static extern void CoCreateInstance(
[MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
uint dwClsContext,
[MarshalAs(UnmanagedType.LPStruct)] Guid riid,
[MarshalAs(UnmanagedType.Interface)] out object rReturnedComObject);
public static object CreateInstance(Guid clsid, bool localServer)
{
object unk;
CoCreateInstance(clsid, null, localServer ? CLSCTX_LOCAL_SERVER : CLSCTX_INPROC_SERVER, IID_IUnknown, out unk);
return unk;
}
}
}
I have a native DLL which is implementing some API. The C++ header looks like this:
class CAPIInterface
{
public:
virtual int __stdcall Release()=0;
virtual LPCSTR __stdcall ErrorDescription(const int code)=0;
virtual int __stdcall Login(const int login,LPCSTR password)=0;
}
In C++ a pointer to the interface is acquired this way:
typedef int (*APICreate_t)(int version,CAPIInterface **api);
pfnAPICreate =reinterpret_cast<APICreate_t>(::GetProcAddress(hlib,"APICreate"));
CAPIInterface *api=NULL;
if(pfnAPICreate) (*pfnAPICreate)(version,&api);
The methods of the interface are called like this:
api->Login(123,"password");
Now I need to load this native DLL and use the API in my C# program. I managed to load the DLL and acquire the pointer to the native interface this way:
public static class GlobalMembers
{
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public delegate int APICreate_t(time_t version, out IntPtr api);
}
ptr_pfnAPICreate = NativeMethods.GetProcAddress(hlib,"APICreate");
pfnAPICreate = (GlobalMembers.APICreate_t)Marshal.GetDelegateForFunctionPointer(ptr_pfnAPICreate, typeof(GlobalMembers.APICreate_t));
pfnAPICreate(version, out mptr);
But now I'm not sure how to map this pointer (mptr) to the C# implementation of the interface. Also I'm not sure how to declare the interface CAPIInterface in C# as well. I tried declaring the interface this way:
[StructLayout(LayoutKind.Sequential)]
public class CAPIInterface
{
public delegate int Release();
public delegate string ErrorDescription(int code);
public delegate int Login(int login, string password);
}
But then it doesn't compile... it returns this error:
Error 3 Non-invocable member 'CAPIInterface.Login' cannot be used like a method. I understand that the delegates must be instantiated somewhere as well... but how to do it? Is it correct approach at all to declare the CAPIInterface as above?
I was able to convert my C++ API to C# with the help of SWIG. It works great. Thank you.
I have created derived control from WebBrowser control that has own IDocHostUIHandler implementation following idea from CreateWebBrowserSiteBase method:
The WebBrowser.WebBrowserSite class provides default implementations
of the OLE IDocHostUIHandler interface. You can provide your own
implementation of this interface or implement any other WebBrowser
ActiveX control interface in order to customize the behavior of the
control.
The problem that is not working. My code looks next way:
[PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
public MyBrowser(){}
protected override WebBrowserSiteBase CreateWebBrowserSiteBase()
{
var manager = new NewWebBrowserSite(this);
return manager;
}
protected class NewWebBrowserSite : WebBrowserSite,
UnsafeNativeMethods.IDocHostUIHandler
{
private MyBrowser host;
public NewWebBrowserSite(MyBrowser h)
: base(h)
{
this.host = h;
}
int UnsafeNativeMethods.IDocHostUIHandler.ShowContextMenu(int dwID, NativeMethods.POINT pt, object pcmdtReserved, object pdispReserved)
{
MyBrowser wb = (MyBrowser)this.host;
// other code
}
// rest of IDocHostUIHandler methods
}
My questions are:
Do I have to implement other interfaces to make it working;
Is that by design, I have read some post that it is related to a bug in .net framework implementation of WebBrowser
I know that is possible to go ICustomDoc.SetUIHandler way, but it is not what I am looking for.
I was having idea at some point to give up with c# and do that with unmanaged code. Is that the way?
I've just dealt with exactly the same problem: how to provide a custom implementation of IDocHostUIHandler to WinForms WebBrowser control. The problem is that the base class WebBrowserSite has already implemented its own version of IDocHostUIHandler (which is an internal interface, so it's not possible to explicitly re-implement it in the derived class NewWebBrowserSite). However, in theory it should not be a problem to implement another C# interface with the same GIID and methods layout (because that's all the COM client - the underlying WebBrowser ActiveX Control - cares about in this particular case).
Unfortunately, it was not possible until .NET 4.0. Luckily, now it is, by means of the new ICustomQueryInterface feature:
protected class NewWebBrowserSite : WebBrowserSite,
UnsafeNativeMethods.IDocHostUIHandler
ICustomQueryInterface
{
private MyBrowser host;
public NewWebBrowserSite(MyBrowser h): base(h)
{
this.host = h;
}
int UnsafeNativeMethods.IDocHostUIHandler.ShowContextMenu(int dwID, NativeMethods.POINT pt, object pcmdtReserved, object pdispReserved)
{
MyBrowser wb = (MyBrowser)this.host;
// other code
}
// rest of IDocHostUIHandler methods
// ICustomQueryInterface
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
{
if (iid == typeof(UnsafeNativeMethods.IDocHostUIHandler).GUID)
{
ppv = Marshal.GetComInterfaceForObject(this, typeof(UnsafeNativeMethods.IDocHostUIHandler), CustomQueryInterfaceMode.Ignore);
}
else
{
ppv = IntPtr.Zero;
return CustomQueryInterfaceResult.NotHandled;
}
return CustomQueryInterfaceResult.Handled;
}
}
You can't simply override the interfaces implemented by a class. If the methods for IDocHostUIHandler are not marked as virtual, you can't replace them.
The fact that the interface is defined in UnsafeNativeMethods is also a clue that you probably shouldn't be messing with it unless you have a very good idea of what you're doing.
I have created a C++ Dll project which contains a class "myCppClass" and tried to Dll export it using the following code as described by:
http://msdn.microsoft.com/en-us/library/a90k134d(v=vs.80).aspx
class __declspec(dllexport) CExampleExport : //public CObject
{ ... class definition ... };
I have omitted the "public CObject" as that requires afx.h and implies it is an MFC Dll. I am not sure if this is a good thing or not but it differed from the DLL project default settings.
From the above linked documentation I am led to believe that all "public functions and member variables" are available for import. How do I accomplish this in C#? Can simply instantiate the class?
Edit: I just realized that the Title of the post may be misleading. The emphasis should be on DllImport-ing from C# and ensuring that I followed the documentation properly in C++
C# cannot directly import C++ classes (which are effectively name-mangled C interfaces).
Your options are exposing the class via COM, creating a managed wrapper using C++/CLI or exposing a C-style interface. I would recommend the managed wrapper, since this is easiest and will give the best type safety.
A C-style interface would look something like this (warning: untested code):
extern "C" __declspec(dllexport)
void* CExampleExport_New(int param1, double param2)
{
return new CExampleExport(param1, param2);
}
extern "C" __declspec(dllexport)
int CExampleExport_ReadValue(void* this, int param)
{
return ((CExampleExport*)this)->ReadValue(param)
}
A C++/CLI-style wrapper would look like this (warning: untested code):
ref class ExampleExport
{
private:
CExampleExport* impl;
public:
ExampleExport(int param1, double param2)
{
impl = new CExampleExport(param1, param2);
}
int ReadValue(int param)
{
return impl->ReadValue(param);
}
~ExampleExport()
{
delete impl;
}
};
As far as I know, C# can only interop with COM interfaces. Lucky enough it doesn't need to be a full blown COM object with registry, it can be any plain old C++ class implementing IUnknown.
So do something like this in C++:
#include <Windows.h>
// Generate with from VisualStudio Tools/Create Guid menu
static const GUID IID_MyInterface =
{ 0xefbf7d84, 0x3efe, 0x41e0, { 0x95, 0x2e, 0x68, 0xa4, 0x4a, 0x3e, 0x72, 0xca } };
struct MyInterface: public IUnknown
{
// add your own functions here
// they should be virtual and __stdcall
STDMETHOD_(double, GetValue)() = 0;
STDMETHOD(ThrowError)() = 0;
};
class MyClass: public MyInterface
{
volatile long refcount_;
public:
MyClass(): refcount_(1) { }
STDMETHODIMP QueryInterface(REFIID guid, void **pObj) {
if(pObj == NULL) {
return E_POINTER;
} else if(guid == IID_IUnknown) {
*pObj = this;
AddRef();
return S_OK;
} else if(guid == IID_MyInterface) {
*pObj = this;
AddRef();
return S_OK;
} else {
// always set [out] parameter
*pObj = NULL;
return E_NOINTERFACE;
}
}
STDMETHODIMP_(ULONG) AddRef() {
return InterlockedIncrement(&refcount_);
}
STDMETHODIMP_(ULONG) Release() {
ULONG result = InterlockedDecrement(&refcount_);
if(result == 0) delete this;
return result;
}
STDMETHODIMP_(DOUBLE) GetValue() {
return 42.0;
}
STDMETHODIMP ThrowError() {
return E_FAIL;
}
};
extern "C" __declspec(dllexport) LPUNKNOWN WINAPI CreateInstance()
{
return new MyClass();
}
And on the C# side you do something like this:
[ComImport]
[Guid("EFBF7D84-3EFE-41E0-952E-68A44A3E72CA")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface MyInterface
{
[PreserveSig] double GetValue();
void ThrowError();
}
class Program
{
[DllImport("mylib.dll")]
static extern MyInterface CreateInstance();
static void Main(string[] args)
{
MyInterface iface = CreateInstance();
Console.WriteLine(iface.GetValue());
try { iface.ThrowError(); }
catch(Exception ex) { Console.WriteLine(ex); }
Console.ReadKey(true);
}
}
You can do pretty much anything you want this way, as long as the communication between C++ and C# goes through the virtual interface.
You cannot create a C++ class instance through pinvoke from C#. This is a troublesome implementation detail, only the C++ compiler knows how much memory needs to be allocated and when and how to properly call the constructor and destructor. The object size is by far the hardest nut to crack, there is no way whatsoever to make that reliable.
If you cannot flatten the C++ class out into static methods then you need to write a managed wrapper. That's done with the C++/CLI language, you'd write a "ref class" that has the unmanaged class object stored as a pointer, created in the constructor and deleted in the destructor and finalizer.
Actually, you can refer to the mangled names directly, using the EntryPoint property of the DllImport attribute. See this answer for more details.
C# and C++ are NOT ABI compatible like C++ and Delphi, so you cannot export virtual class members (methods) and declare them purely virtual on the calling side an invoke them, because C# cannot handle vtbl's of C++ objects.
I would suggest you to wrap your C++ classes by COM, so you have another positive side effect, that other COM compatible languages can use your classes too.