The question seems like a dirty hack you should not do but let me explain first. The ultimate goal is to have method local statics like in C++.
void Func()
{
static methodLocalObject = new ExpensiveThing();
// use methodlocal Object
}
What has this to do with the instruction pointer? I want to cache data depending on my caller. To make this fast I walk back in the stack to get the address of my caller and use this as unique key for a dictionary to store the data. That would allow to create a reflection based tracer which does not use Reflection every time to get the name of the current method and type but only once and store the reflection infos in a hash table.
The answers so far were only mono based. I want to try a generic solution which works on .NET 3.5/4.0 32/64 bit. I do know that the calling convention for 64 bit is quite different so it could get challenging to get something reliable. But on the other hand I have full control inside my method how the stack does look like. The stack does look very different between .NET 3.5 and 4.0 and it differs of course also between release builds. I still have to check if NGen does create code with a different stack layout as well. One possibility would be to use a C++ helper method which takes 5 magic integer arguments (on x64 only the 5th will be on the stack) and check where I can find them on the stack. Another possiblity would be to simply use the whole stack until I find my magic marker on the stack as a key and use this part of the stack as unique enough key. But I am not sure if this approach can work at all or if there are better alternatives. I do know I can walk the stack in a safe way via the profiling or debugging apis but neither of them are fast.
For a tracing library the usual approach is to walk the stack using reflection to get the current method name and type.
class Tracer
{
[MethodImpl(MethodImplOptions.NoInlining)]
public Tracer()
{
StackFrame frame = new StackTrace().GetFrame(1); // get caller
Console.WriteLine("Entered method {0}.{1}", frame.GetMethod().DeclaringType.FullName, frame.GetMethod().Name);
}
}
But this is very slow. The other solution is to pass the data directly via strings which is much faster but it needs more typing. The alternate solution would be to use the instruction pointer of the calling function (if this can be determined in a very fast way) to get around the expensive reflection calls. Then this would be possible:
class Tracer
{
static Dictionary<Int64, string> _CachedMethods = new Dictionary<Int64, string>();
[MethodImpl(MethodImplOptions.NoInlining)]
public Tracer()
{
Int64 eip = GetEIpOfParentFrame();
string name;
lock (_CachedMethods)
{
if (!_CachedMethods.TryGetValue(eip, out name))
{
var callingMethod = new StackTrace().GetFrame(1).GetMethod();
name = callingMethod.DeclaringType + "." + callingMethod.Name;
_CachedMethods[eip] = name;
}
}
Console.WriteLine("Entered method {0}", name);
}
Int64 GetEIpOfParentFrame()
{
return 0; // todo this is the question how to get it
}
}
I know that the solution needs to be unmanaged. In C++ there is a compiler intrinsic called _ReturnAddress but according to the docs it does not work with managed code. Another way to ask the same question: Does anybody know the calling convention and stack layout for managed methods for .NET 3.5/4 x32/x64?
Yours, Alois Kraus
I have another, (though highly experimental) idea, that is based on using expression trees to do the invocations to your method via a invoker and a facade.
Instead of normally calling your method, you would create an expression tree to invoke th facade from a given location at your code. This expression tree is passed to an invoker, that caches the compiled expression tree along with the caller information. The caller information can be retrieved once via StackTrace.GetMethod and cached against the expression tree.
From personal experience, since you only need a key for your invocation, you should store a MethodHandle only, instead of the full MethodBase object (dramatically reduces memory consumption).
To perform the real invocation, you can now examine the expression tree and build a new one to call the real implementation with either a dictionary containing your method level statics or pass it the caller method key.
Wow, this is really cool and fast as hell. Please provide feedback on the gist: https://gist.github.com/1047616
The output of which is:
The real short answer is: the CLR VM is a stack machine, so no EIP there. The slightly longer answer is: if you rely on undocumented implementation-specific details, you could extrapolate a useable ID from the CPU EIP in unmanaged code.
Proof Of Concept
I just managed the following proof of concept, using mono 2.11 on Linux 32-bit. I hope the information might help. This implements unmanaged functions:
Native source: tracehelper.c [1]:
C# Source (client.cs) to call this native library function:
Compile and link using Makefile:
Running this with
LD_LIBRARY_PATH=. ./client.exe
results in:Note that this is on Mono 2.11. It works on 2.6.7 as well, with and without optimization.
[1] I learned GNU extended asm for this purpose; thanks SO!
Conclusions ?
Delivered a proof of concept; this implementation is specific to Mono. A similar 'trick' could be delivered on MS .Net (using a ::LoadLibrary of SOS.dll, perhaps?) but is left as an exercise for the reader :)
I would personally still go with my other answer, but I suppose I succumbed to the challenge and, like I've said before: YMMV, Here be dragons, TIMTOWTDI, KISS etc.
Good night
Your best bet will be StackFrame(Int32):
More ideas
If you must, you can use code generator that will fill in the ids before compilation.
With C# 5.0 there is a new, well-hidden feature that enables this.
Note Apparently, there is also the Microsoft BCL Portability Pack 1.1.3 Nuget package so you can use the Caller Info Attributes in .NET 4.0.
What this does, is make your optional parameters magically have caller-dependent default values. It has
It has some pretty nifty features:
The documentation sample looks like this:
I would use the profiler API's, but if you want more performance try Enter/Leave Hooks.
I think your trying to have your cake and eat it too, performance + portability don't always go together. Link in some MASM64 for performance :)