Search code examples
c#c++dllcom

Using C# code in C++ (COM) Dll import not working quite right


As the title implies, I am having difficulty calling C# code from C++. A bit of context: there is an API call in C# (that does not exist in the C++ version of the API) and I need to integrate it into a much larger C++ project.

I have been reading over this SO post, and made the most headway with the COM method.

My C# dll compiles*. After copying the resulting dll and tlb files into the appropriate C++ project directory, I open an admin cmd prompt in the same C++ project directory and run:

regasm MyFirstDll.dll /codebase /tlb

(*I compile it as a Class Library, Assembly name: MyFirstDll, Default namespace: MyMethods, Assembly Information... -> Make assembly Com-Visible is checked, Build->Register for COM interop is also checked.)

Using the Object Browser, I am able to see the class I defined, as well as a method with the appropriate args and signature. Screenshot of it showing up in Object Browser

The issue I am experiencing is with the method call (and not the class). Even though the method is visible in the Object Browser, in my code it is not recognized as a method of the object.

class "ATL::_NoAddRefReleaseOnCComPtr" has no member "Add"
Here is my code:

MyFirstDll C# Project:

using System;
using System.Runtime.InteropServices;
//

namespace MyMethods
{
    // Interface declaration.
    [Guid("8a0f4457-b08f-4124-bc49-18fe11cb108e")]
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface Easy_Math
    {
        [DispId(1)]
        long Add(long i, long j);
    };
}
namespace MyMethods
{
    [Guid("0cb5e240-0df8-4a13-87dc-50823a395ec1")]
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("MyMethods.AddClass")]
    public class AddClass: Easy_Math
    {
        public AddClass() { }
        [ComVisible(true)]

        public long Add(long i, long j)
        {
            return (i + j);
        }
    }
}

NateMath.h:

#include <atlcomcli.h> 
#import "MyFirstDll.tlb"

class NateMath
{
public:
    NateMath(void);
    ~NateMath(void);
    long Add(long a, long b);

private:
    CComPtr<MyFirstDll::AddClass> adder;
};

NateMath.cpp:

#include "stdafx.h"
#include <atlcomcli.h>
#import "MyFirstDll.tlb"
#include "NateMath.h"

NateMath::NateMath(void)
{
    CoInitialize(NULL);
    adder.CoCreateInstance(__uuidof(MyFirstDll::AddClass));
}

NateMath::~NateMath(void)
{
    CoUninitialize();
}
long NateMath::Add(long a, long b) {
    return adder->Add(a,b);
}

The issue being that in "return adder->Add(a,b)" (NateMath.cpp) Add(a,b) shows up red with class "ATL::_NoAddRefReleaseOnCComPtr" has no member "Add"


Solution

  • This is because you are trying to use your class name in CComPtr instead of the interface. With COM, all methods are defined on an interface, not on the class that implements an interfaces.

    You can CoCreateInstance(__uuidof(YourClass)) because the intent is to create an instance of YourClass (which is identified by the GUID expressed by __uuidof(YourClass)). However, YourClass in the C++ is a dummy struct that's only present so that you can read the uuid -- the definition of YourClass in the C++ generated from the #import is empty and will always be empty.

    To fix this, use CComPtr<YourInterface>. This tells the C++ that you want to communicate with the referenced object via that interface. Here's a rule to remember: The type argument to CComPtr and CComQIPtr must always be a COM interface. That COM interface can either be an explicitly-defined interface, or it can be the "class interface" which was automatically produced by .NET.

    Speaking of class interfaces: If you had used ClassInterfaceType.AutoDual instead of None, you could have used a CComPtr<_YourClass> (note the leading underscore -- _YourClass is the class interface, whereas YourClass would be the class. I would recommend doing it the way you already have, however.