When you compile with /clr and you have a native class in your code (one that’s neither a ref class nor a value class), there are two entry points generated for its methods – a managed entry point and a native entry point. This allows both managed and native callers to call these methods, and the native entry point acts as a thunk to the managed entry point. Virtual functions are always called through the native entry point. Now, this results in a performance issue; when a managed caller calls a virtual function in a /clr compiled native class, there are two managed-unmanaged transitions that are required – the first managed-to-native transition to invoke the native entry point and the second native-to-managed transition to invoke the managed entry point – this is referred to as double thunking.
class N
{
public:
N()
{
Console::WriteLine(__FUNCSIG__);
}
N(const N&)
{
Console::WriteLine(__FUNCSIG__);
}
~N()
{
Console::WriteLine(__FUNCSIG__);
}
};
class X
{
public:
virtual void UseNVirt(N)
{
Console::WriteLine(__FUNCSIG__);
}
Now, if you call UseNVirt as follows, you can see the effect of double thunking :-
X* x = new X();
Console::WriteLine();
x->UseNVirt(n);
Console::WriteLine();
/*** Output
__thiscall N::N(const class N &)
__thiscall N::N(const class N &)
__thiscall N::~N(void)
void __thiscall X::UseNVirt(class N)
__thiscall N::~N(void)
***/
The solution to this is to use __clrcall appropriately :-
class X
{
public:
virtual void UseNVirt(N)
{
Console::WriteLine(__FUNCSIG__);
}
virtual void __clrcall UseNVirtClrCall(N)
{
Console::WriteLine(__FUNCSIG__);
}
Now, if you call UseNVirtClrCall as follows, there’s no double thunking involved :-
X* x = new X();
Console::WriteLine();
x->UseNVirtClrCall(n);
Console::WriteLine();
/*** Output
__thiscall N::N(const class N &)
void __clrcall X::UseNVirtClrCall(class N)
__thiscall N::~N(void)
***/
So, my suggestion to those who are doing mixed mode programming would be to profusely use __clrcall except when you know for certain that the method could be invoked by a native caller. Note that VC++ 2005 does some optimizations on its own and implicitly adds __clrcall to any method with a managed type in its signature (including the return type).
Nish,
Good suggestion indeed.
Speaking of performance of managed C++ under VS 05, you should take a look at this
issue that I found last month:
http://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=e976ea2a-ba1e-4537-9493-46af9429a81e
I’m somewhat amazed that the product went out the door when run-of-the-mill C++ code
runs orders of magnitude slower under the mixed mode debugger.
Jeff
Thanks for the link – and yeah, that bug must have been a real pain for you.
I’ve found the same performance issues as Jeff.
In fact – I’ve abandoned all c++/clr development due to the the debug performance issues
and the failure to ship stl.net. A language _is_ it’s libraries and the failure
to produce a version of the standard library that can work with managed classes
renders c++/clr useless.