I am working on MSIL profiler and encountered problems with ManagedToUnmanagedTransition
and UnmanagedToManagedTransition
callbacks of ICorProfilerCallback
interface.
What I want to retrieve is an information about method being called (name and module name it resides in).
So far it was working fine. Until so called dynamic pinvoke occured (described in detail at: http://blogs.msdn.com/b/jonathanswift/archive/2006/10/03/dynamically-calling-an-unmanaged-dll-from-.net-_2800_c_23002900_.aspx)
In this scenario IMetaDataImport::GetPinvokeMap
fails. Also IMetaDataAssemblyImport::GetAssemblyProps
returns "dynamic_pinvoke" as a name of the assembly.
profiler_1_0->GetTokenAndMetaDataFromFunction(function_id, IID_IMetaDataImport, (IUnknown**) &imd_import, &md_token);
imd_import->GetPinvokeMap(md_token, &mapping, module_name, buffer_size, &chars_read, &md_module_ref);
// here the fail occurs
profiler_1_0->GetTokenAndMetaDataFromFunction(function_id, IID_IMetaDataAssemblyImport, (IUnknown**) &imd_assembly_import, &md_token);
imd_assembly_import->GetAssemblyFromScope(&md_assembly);
imd_assembly_import->GetAssemblyProps(md_assembly, 0, 0, 0, assembly_name, buffer_size, &chars_read, 0, 0);
// assembly_name is set to "dynamic_pinvoke"
How to obtain a module name (.dll) and a name of function being pinvoked through dynamic pinvoke?
The profiler APIs are returning metadata specified in the managed code, normally via the DllImportAttribute. In the case of the 'dynamic pinvoke' which uses the Marshal.GetDelegateForFunctionPointer method, the module and function names were never specified as metadata and not available. An alternative approach to dynamic pinvoke declarations that includes the required metadata will probably avoid this issue. Try using System.Reflection.Emit APIs such as TypeBuilder.DefinePInvokeMethod as one solution.
Here is an example using System.Reflection.Emit that does work with the profiler APIs.
A few examples to demonstrate the issues with the current approach.
There is no MessageBox function exported from user32.dll. It only contains MessageBoxA and MessageBoxW. As we did not specify the ExactSpelling=false in the DllImport attribute and our CharSet is Unicode, .Net will also search user32.dll for our entry point appended with a W. This means MessageBoxW is in fact the native function we are calling. However, GetPinvokeMap returns 'MessageBox' as the function name (module_name variable in your code).
Now lets instead invoke the function via ordinal number rather than name. Using the dumpbin program in the Windows SDK:
2046 is the ordinal number for MessageBoxW. Adjusting our DllImport declaration to use the EntryPoint field we get:
This time GetPInvokeMap returns "#2046". We can see the profiler knows nothing about the 'name' of the native function being invoked.
Going even further, the native code being called may not even have a name. In the following example, an 'Add' function is created in executable memory at runtime. No function name or library has ever been associated with the native code being executed.