[?] Querying for C# methods from unmanaged C++ via COM

  • Thread starter Quinn Tyler Jackson
  • Start date
Q

Quinn Tyler Jackson

Hello:

This question relates both to C# and to unmanaged C++, but is not really "C#
specific" at the C# end.

Let us suppose that I have a C# class such that:

using System;
using SomeCOM_Lib;

namespace Foo
{
class Bar : SomeCOM_Lib.ISomeInterface
{
public void SomeMethod() {}
}

class Quux
{
public void SomeCSharpMethod()
{

SomeCOM_Lib.ISomeOtherInterface obj = new
SomeCOM_Lib.ISomeOtherInterface();
obj.TheInterface = obj;
}
}
}

Now, let us suppose in our C++, the put_TheInterface on ISomeOtherInterface
wishes to query TheInterface for the methods of Bar, via the base interface
ISomeInterface (which includes an IDispatch interface).

In other words, from unmanaged C++, given the IDispatch interface of the C#
"obj" -- is there a way to determine the names of the methods on obj, such
that "SomeMethod" can be identified and its ID queried, so it can be invoked
off IDispatch::Invoke?

Essentially, I wish to determine at run-time the methods of a C# class and
call them via IDispatch, without knowing in advance (at compile time) what
those methods are named.

This is to implement a generic "callback" functionality.

Thanks in advance for any pointers.

(At the C++ side, the code should not require the assumption that the .NET
framework is installed. The C# class could, in fact, have been implemented
in unmanaged C++, but in this case just happens to be in C#.)
 
Q

Quinn Tyler Jackson

class Quux
{
public void SomeCSharpMethod()
{

SomeCOM_Lib.ISomeOtherInterface obj = new
SomeCOM_Lib.ISomeOtherInterface();
obj.TheInterface = obj;
}
}
}

That should have read:

obj.TheInterface = new Bar();
 
W

William DePalo [MVP VC++]

Quinn Tyler Jackson said:
This question relates both to C# and to unmanaged C++, but is not really "C#
specific" at the C# end.
:)

In other words, from unmanaged C++, given the IDispatch interface of the C#
"obj" -- is there a way to determine the names of the methods on obj, such
that "SomeMethod" can be identified and its ID queried, so it can be invoked
off IDispatch::Invoke?

Essentially, I wish to determine at run-time the methods of a C# class and
call them via IDispatch, without knowing in advance (at compile time) what
those methods are named.

Half of what you want to do is easy. Given a managed type (a.k.a class), you
can use reflection to determine the methods that it contains.

http://msdn.microsoft.com/library/d.../html/frlrfsystemtypeclassgetmethodstopic.asp

As to the rest, I pass. :)

Regards,
Will
 
Q

Quinn Tyler Jackson

"William DePalo [MVP VC++]" said:
Half of what you want to do is easy. Given a managed type (a.k.a class), you
can use reflection to determine the methods that it contains.

I cannot believe that I managed to figure out the answer to my own question
by trial and error, but I did.

The key requirements were that:

1. I be able to dynamically register a C# class that my COM object knows
nothing about other than the fact that it has an IDispatch interface. This
requirement precludes using a tool that exports an assembly to the registry
after the fact.

2. I be able to query (from within my COM object) whether or not a C# class
it has been handed as an interface pointer has a given method, by using a
string that is only known at run-time. This precludes using headers at
compile time.

3. That the COM object not in any way "depend" on the .NET framework being
installed. Although in this case I am using C# for testing purposes, the
mechanism was supposed to be generic enough that any COM object that exposes
IDispatch should be able to do what I wanted.

The solution is not exactly "pretty" but it works.

The reason I need this was so that a COM object implemented in unmanaged ATL
that has been imported into a .NET project via Interop could call-back into
the .NET side.

Here is a short snippet of the C#-side of the solution I came up with:

using System;
using METASCOMLib;
using MetaSLua;
using System.Runtime.InteropServices;
using System.Reflection;

namespace CSharpExample
{
[Guid("03911914-DCF0-4caf-A654-5897ED3DC060")]
public interface ITest
{
void b_event();
}

[ClassInterface(ClassInterfaceType.AutoDual)]
public class CSharpEventHost : ITest, IMetaXParserEventHost
{
public CSharpEventHost()
{
}

public Guid ClassGUID
{
get
{
return new Guid("03911914-DCF0-4caf-A654-5897ED3DC060");
}
}

[DispId(15)]
public void b_event()
{
Console.Out.Write("b_event fired\n");
}
}

class ApplicationClass
{
[STAThread]
static void Main(string[] args)
{
RegistrationServices rs = new RegistrationServices();
bool b = rs.RegisterAssembly(Assembly.GetExecutingAssembly(),
0);

MetaXGrammar gmr = new MetaXGrammar();
gmr.LuaCallbacks = new LuaEvents(Console.Out, Console.Error);
gmr.SetLuaDebug(true, false, false);
gmr.EventHost = new CSharpEventHost();

gmr.Grammar =
"grammar X host Lua {" +
" S ::= a b c;" +
" a ::= '[0-9]+';" +
" b ::= '[a-z]+';" +
" c ::= '[0-9]+';" +

" @function_impls = :{ " +
" function c_event (N)\n" +
" if (N:MATCHED()) then\n" +
" print(N:LEXEME())\n" +
" end\n"+
" end" +
" }:;" +
"};";

if(gmr.IsGood)
{
IMetaXScanInfo r = gmr.Scan("12345 abc 67890 123abc456");

if(r.MatchLength > 0)
{
Console.Write("Lexeme = \"" + r.Lexeme + "\"\n");
Console.Write(gmr.Root.GetChildByPath("S/b").Lexeme + "\n");
}
}
else
{
Console.Write("Error: Grammar could not be compiled!\n");
}

rs.UnregisterAssembly(Assembly.GetExecutingAssembly());
}
}
}

<< END

As you can see, "b" in the run-time compiled grammar is known only at
run-time, and therefore that "b_event" exists in CSharpEventHost must be
determined at run-time. The grammar could, after all, have been loaded from
a text file.

The tricky part is knowing how to query gmr.EventHost from within the ATL
code that implements METAXCOMLib. I figured it out by trial and error to be
(please pardon the hacky nature of the following C++ -- it was for
proof-of-concept. I'll clean it up later.)

STDMETHODIMP CMetaXGrammar::put_EventHost(IMetaXParserEventHost* newVal)
{
m_pEventHost = newVal;

if(m_pEventHost != NULL)
{
DISPID id = 0;
LCID lid = LOCALE_SYSTEM_DEFAULT;

UINT n = 0;

m_pEventHost->GetTypeInfoCount(&n);

if(n != 0)
{
GUID iid = {{0}};
m_pEventHost->get_ClassGUID(&iid);

IUnknown* p = NULL;
m_pEventHost->QueryInterface(iid, (void**)&p);

if(p != NULL)
{
IDispatch* pID = NULL;
p->QueryInterface(IID_IDispatch, (void**)&pID);

if(pID != NULL)
{
ITypeInfo* pTI = NULL;
HRESULT hr = pID->GetTypeInfo(n, lid, &pTI);

MEMBERID mid = 0;
CComBSTR sN("b_event"); // this could be any string
hr = pTI->GetIDsOfNames(&sN, 1, &mid);

if(hr == S_OK)
{
DISPPARAMS dp = {0};
hr = pID->Invoke(mid, IID_NULL, lid, DISPATCH_METHOD, &dp, NULL, NULL,
NULL);
}

pTI->Release();
pID->Release();
}

p->Release();
}
}
}

return S_OK;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top