Search code examples
comcom-interop

Trying to use C# DLL with ComVisible attribute set from unmanaged C++


TL;DR: I'm trying to use a C# library in C++. Why am I getting an undeclared identifier error when trying to use an identifier from my .tlh file? There must be tons of examples out there, but I haven't been able to find any that include both the C# and C++ code, and that work. Links to such examples would be greatly appreciated.

I have the following classes defined in C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;

using CrystalDecisions.CrystalReports.Engine;

namespace CapsCrystalReportLib
{
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    [Guid("B4E5F784-12E6-4311-9BB9-D5B3252F20A3")]
    public interface ICapsCrystalReport
    {
        [DispId(1)]
        void DisplayReport(string fileName);
        [DispId(2)]
        void PrintReport(string fileName);
    }

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [Guid("89402DE5-BA26-4AC0-AB40-00ADD2876FF4")]
    [ProgId("CAPSCrystalReport.Report")]
    [ComDefaultInterface(typeof(ICapsCrystalReport))]
    public class CapsCrystalReport : ICapsCrystalReport
    {

        public void DisplayReport(string fileName)
        {
            MessageBox.Show("Displaying report " + fileName);
        }

        public void PrintReport(string fileName)
        {
            MessageBox.Show("Printing report " + fileName);
        }
    }
}

I have the following C++ program attempting to use this class:

#include "stdafx.h"
#import "W:\\CAPS Builds\\trunk\\CapsCrystalReportLib\\bin\\Debug\\CapsCrystalReportLib.tlb" no_namespace

int _tmain(int argc, _TCHAR* argv[])
{
   // Initialize COM.
   HRESULT hr = CoInitialize(NULL);

   // Create the interface pointer.
   CapsCrystalReport CRPtr(__uuidof(CapsCrystalReport));

   long lResult = 0;

   // Call the Add method.
   CRPtr->DisplayReport("SomeReport.rpt");

   // Uninitialize COM.
   CoUninitialize();

   return 0;

}

I am getting an undeclared identifier error. The compiler doesn't know what a CapsCrystalReport is. What am I doing wrong?

P.S. I took another look at the sample I copied this from. One of the comments asks the same question, and it was never answered.


Solution

  • You were very close, but CRPtr is a COM interface reference (=pointer) so it must be declared like this:

    ICapsCrystalReportPtr CRPtr(__uuidof(CapsCrystalReport));
    

    The IxxxPtr class was generated for you by #import in a .tlh file. What you can do when you have issues with #import, is just open the generated .tlh file and look at it.

    Note you don't have to declare a default interface in C#, you can just declare the class like this:

    [ComVisible(true)]
    [Guid("89402DE5-BA26-4AC0-AB40-00ADD2876FF4")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ProgId("CAPSCrystalReport.Report")]
    public class CapsCrystalReport
    {
        ... same ...
    }
    

    And in C++, you would have to adapt your imports like this:

    #import "C:\WINDOWS\Microsoft.NET\Framework64\v4.0.30319\mscorlib.tlb" auto_rename
    #import "W:\\CAPS Builds\\trunk\\CapsCrystalReportLib\\bin\\Debug\\CapsCrystalReportLib.tlb" no_namespace
    

    and you would use it like that (the interface was implicitely created by .NET and wrapped by the #import):

    _CapsCrystalReportPtr CRPtr(__uuidof(CapsCrystalReport));
    

    PS: I would recommend you to keep the namespace, avoid no_namespace because it can cause problems with collisions especially in C++.