I have a couple of suggestions. First of all, create an interface for anything that you touch in COM, even if its just a bog-standard DTO that has no methods and only properties. COM loves interfaces. It loves them so much, that everything you touch in COM is an interface.
The other suggestion is that you place a GuidAttribute on anything that you touch in COM. This will ensure that your registry doesn't get crapped up when you register the managed assembly with COM. COM loves GUIDs more than it loves interfaces, and it gets confused easily if an interface is registered to more than one GUID, which can happen if you don't hard-fix the interface GUIDs in code. Concrete classes also need a GUIDAttribute.
I know it sucks, but that's why MS is trying so hard to get people away from using COM.
That being said, you probably want C# like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ClassLibrary1
{
[ComVisible(true)]
[Guid("1CEB24F2-BF13-417F-B5BC-DB8E82A56EAE")]
public interface ITestEntity1 //This is how TestEntity1 is visible to the COM code...so it needs a Guid.
{
bool Thing { get; set; }
}
[ComVisible(true)]
[Guid("C8B5A7C2-F67C-4271-A762-3642754F2233")]
public class TestEntity1 : ITestEntity1 //Created by the COM runtime...needs a Guid.
{
public bool Thing { get; set; }
}
[ComVisible(true)]
[Guid("8904A7EC-D865-4533-91EC-1F68524651D0")]
public interface ITestEntity2
{
string Description { get; set; }
}
[ComVisible(true)]
[Guid("668EE2E8-5A60-468B-8689-D9327090AA44")]
public class TestEntity2 : ITestEntity2
{
public string Description { get; set; }
}
[ComVisible(true)]
[Guid("2791082F-F505-49C4-8952-80C174E4FE96")]
public interface ITestGateway
{
//MarshalAsAttribute is somewhat important, it tells the tlbexp.exe tool to mark
// the comInputValue parameter as IUnknown* in the COM interface.
//This is good because VARIANTS kinda suck...You'll see what I mean in the C++
// side. It also keeps some jack-wagon from passing a VARIANT_BOOL in
// on your object parameter.
void DoSomething(string a, [MarshalAs(UnmanagedType.Interface)]object comInputValue);
}
[ComVisible(true)]
[Guid("C3D079F3-7869-4B3E-A742-263775C6EA63")]
public class TestGateway : ITestGateway
{
public void DoSomething(string a, object comInputValue)
{
if (a == "yes")
{
var entity = (TestEntity1)comInputValue;
}
else
{
var entity = (TestEntity2) comInputValue;
}
//OR
if(comInputValue is TestEntity1)
{
//Do whatever here, and you don't need to test
// a string input value.
}
else if(comInputValue is TestEntity2)
{
//Other stuff is done here.
}
else
{
//Error condition??
}
}
}
}
That can be called by the following C++:
// ComClient.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#import <mscorlib.tlb> raw_interfaces_only
//This creates the CLSID_ and IID_ constants, and
// some strongly-typed interfaces.
#import "..\Debug\ClassLibrary1.tlb" no_namespace named_guids
int _tmain(int argc, _TCHAR* argv[])
{
ITestGateway* test = NULL;
char buffer[50];
gets(buffer); //Just a pause to attach the debugger in Managed + Native mode...hit enter in the console.
CoInitialize(NULL);
HRESULT hr = CoCreateInstance(CLSID_TestGateway,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITestGateway,
reinterpret_cast<void**>(&test));
if(FAILED(hr)) {
printf("Couldn't create the instance!... 0x%x\n", hr);
gets(buffer);
} else {
_bstr_t someString("yes");
//Instead of fooling with CComVariant,
// just directly create a TestEntity1...which COM will return
// as an ITestEntity1.
ITestEntity1* testEntity1 = NULL;
HRESULT hr = CoCreateInstance(CLSID_TestEntity1,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITestEntity1,
reinterpret_cast<void**>(&testEntity1));
if(FAILED(hr)) {
printf("Entity was not created!... 0x%x\n", hr);
gets(buffer);
return 0;
}
//Set some kind of property just for show.
testEntity1->PutThing(VARIANT_FALSE);
//If you attached your debugger with Managed code & Native code,
// you should be able to hit a C# break point during this call.
//Also, notice that there is no cast for testEntity1. All interfaces
// in COM derive from IUnknown, so you can just pass it.
//IDispatch also derives from IUnknown, so if that's what you already have,
// you can just pass it as well, with no cast.
test->DoSomething(someString, testEntity1);
printf("Something was done.");
testEntity1->Release(); //Release anything you make through CoCreateInstance()
}
test->Release();
return 0;
}