6

I have a DLL written in C++, which exports a function CreateRisk. The function returns an interface pointer, as follows:

extern "C"
{
    __declspec(dllexport) IRisk* __stdcall CreateRisk()
    {
        return new  Risk();


    }
}

IRisk is derived from IUnknown and has a custom method Calculate:

class IRisk: public IUnknown                               
{
public:
    virtual int __stdcall Calculate(int i,double s) = 0;  
};

the class Risk implements IRisk interfaces (the implementation is omitted here).

What I want is to call the function CreateRisk in c# and obtain a reference to IRisk.

I defined a wrapper interface in c#

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]        
    public interface IRisk
    {
        [PreserveSig]                                            
        int Calculate(int i,double s);
    }

and I added a dll entry in C#

    [DllImport("Risk.dll")]
    extern static IntPtr CreateRisk();

I can call CreateRisk in C# and obtain a value type of IntPtr. Is there any way to marshal the IntPtr to the c# IRisk interface so that I can call the Calculate method in C#?

I have tried Marshal.GetIUnknownForObjectInContext,Marshal.GetObjectForIUnknown, but to no avail.

I know creating a COM component can do it. However, COM component needs to be registered with the system. I want to avoid those troubles and let C# use the interface exported by the C++ dll directly.

PS:

The following is my Risk class implementation:

class IRisk : public IUnknown                               
{
public:
    virtual int __stdcall Calculate(int i,double y ) = 0;  
};

class Risk:public IRisk
{
    int count;
public:
    Risk();
    virtual int __stdcall Calculate( int i ,double y);
    virtual ULONG __stdcall AddRef();
    ULONG __stdcall Release();
    HRESULT __stdcall QueryInterface(const IID& riid,void **ppvObject);
};

Risk::Risk(){
    count=0;
}

ULONG __stdcall Risk::AddRef()
{
    return ++count;
}
ULONG __stdcall Risk::Release()
{
    return --count;
}
HRESULT __stdcall Risk::QueryInterface(const IID& riid,void **ppvObject) {
    *ppvObject=this;
    AddRef();
    return S_OK;
}
int __stdcall Risk::Calculate(int i ,double y) {
    return (int)(i+y);
}
George
  • 61
  • 1
  • 3
  • Try to put Risk.dll in the same directory as your .NET assembly; http://stackoverflow.com/questions/8836093/how-can-i-specify-a-dllimport-path-at-runtime – fableal Dec 06 '12 at 10:49
  • @fableal If there was a problem with the location of the native DLL, then Guiyun would not get as far as receiving the return value from `CreateRisk`. – David Heffernan Dec 06 '12 at 12:30

2 Answers2

2

The following will work:

[DllImport("Risk.dll")]
extern static IRiskAssessment CreateRisk();

You will need to add a GUID to the interface:

[Guid("your GUID goes here")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]        
public interface IRiskAssessment
{
    [PreserveSig]                                            
    int Calculate(int i,double s);
}

Obviously you need to use the same GUID in the C# code as was used in the C++ code!

Update

Looking at your C++ COM object implementation, the obvious fault is in QueryInterface. You must not return S_OK for all GUIDs. Only return S_OK for the interfaces you implement. Return E_NOINTERFACE for the others.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • David, thanks for your quick reply. I added a GUID to the interface. When CreateRisk is called, it throws a AccessViolationException, saying "Attempted to read or write protected memory. This is often an indication that other memory has been corrupted ". I do not have a GUID defined in C++ since I do not use a TypeLib although this interface is completely COM compatible. – George Dec 06 '12 at 12:33
  • You will need to define a GUID in the C++ code. You don't need a type lib, you just need to implement `QueryInterface` to respond to the GUID. You do not need to register a COM object. I didn't when I tested this code. Is it possible that your implementation of IUnknown is broken? – David Heffernan Dec 06 '12 at 12:39
  • David, I add my Risk class implementation in C++ at the end of the original question. The reason I do not use a GUID is because the class only supports one interface and I always returns the interface regardless of the GUID. Something wrong with the implementation? – George Dec 06 '12 at 13:04
  • Well, it returns `S_OK` whenever you call `QueryInterface`. That's wrong. Only return `S_OK` for interfaces that you actually support. This link shows you how to do it: http://www.codeguru.com/cpp/com-tech/activex/tutorials/article.php/c5567/Step-by-Step-COM-Tutorial.htm#Step5 – David Heffernan Dec 06 '12 at 13:10
  • After revising the QueryInterface implementation, the exception is gone. David, I really appreciate it. What puzzles me is that why I should need to take care of the case of E_NOINTERFACE? Is that because .NET tries to query other common interface when doing the marshalling? – George Dec 06 '12 at 13:40
  • That's the obvious explanation, but I don't know for sure. It always pays to follow the rules with QI. – David Heffernan Dec 06 '12 at 13:55
  • I'm glad I could help. Do feel free to accept the answer! ;-) – David Heffernan Dec 06 '12 at 14:10
1

If I were you I would use SWIG to generate a C# interface for your C++ code, saves you a lot of troubles. Also, I think your assembly may have to be compiled in mixed-mode, or as a managed assembly.

Community
  • 1
  • 1
  • This doesn't really answer the question. There is in fact no need for mixed-mode. It's no trouble at all to pinvoke to functions that return COM interfaces. – David Heffernan Dec 06 '12 at 11:18