Author: Dietmar Maurer (dietmar@ximian.com) (C) 2001 Ximian, Inc. More about PInvoke and Internal calls ===================================== 1.) What is PInvoke PInvoke stands for Platform Invoke. It is possible to call functions contained in native shared libraries, for example you can declare: [DllImport("cygwin1.dll", EntryPoint="puts"] public static extern int puts (string name); If you then call "puts(...)" it invokes the native "puts" functions in "cygwin1.dll". It is also possible to specify several marshalling attributes for the arguments, for example you can specify that they puts() function expect ts the string in Ansi encoding by setting the CharSet attribute field: [DllImport("cygwin1.dll", EntryPoint="puts", CharSet=CharSet.Ansi)] public static extern int puts (string name); 2.) What are internal calls Some class library functions are implemented in C, because it is either not possible to implement them in C# or because of performance gains. Internal functions are contained in the mono executable itself. Here is an example form our array implementation: [MethodImplAttribute(MethodImplOptions.InternalCall)] public extern int GetRank (); If you call this GetRank() function it invokes ves_icall_System_Array_GetRank() inside the mono runtime. If you write your own runtime environment you can add internal calls with mono_add_internal_call(). 2.) Runtime considerations Invoking native (unmanaged) code has several implications: - We need to handle exceptions inside unmanaged code. The JIT simply saves some informations at each transition from managed to unmanaged code (in a linked list), called Last Managed Frame (LMF). If an exception occurs the runtime first looks if the exception was inside managed code. If not there must be a LMF entry which contains all necessary information to unwind the stack. Creation of those LMF structure clearly involves some overhead, so calling into unmanaged code is not as cheap as it looks like at first glance. Maybe we can introduce a special attribute to avoid the creation of LMF on internal call methods that cant raise exceptions. - PInvoke has the possibility to convert argument types. For example Strings are marshalled as Char*. So each String argument is translated into a char*. The encoding is specified in the CharSet of the DllImport attribute. 3.) When/how does the runtime call unmanaged PInvoke code - LDFTN, CALLI, Delegate::Invoke, Delegate::BeginInvoke: We must generate wrapper code when we load the function with LDFTN, so that all arguments are marshalled in the right format. We also need to save/restore the LMF. - MethodBase::Invoke (runtime invoke): We need to marshal all arguments in they right format and save/restore the LMF - CALL: We need to marshal all arguments in they right format and save/restore the LMF The easiest way to implement this is to always create a wrapper function for PInvoke calls, which takes care of argument marshalling and LMF save/restore. 4.) When/how does the runtime call unmanaged internal calls We don't need to convert any arguments, so we need only take care of the LMF structure. - LDFTN, CALLI, Delegate::Invoke, Delegate::BeginInvoke: We must generate wrapper code when we load the function with LDFTN which saves/restores the LMF. - MethodBase::Invoke (runtime invoke): We need to save/restore the LMF. - CALL: We need to save/restore the LMF. - CALLVIRT (through the vtable): We must generate wrapper code to save/restore the LMF. Please notice that we can call internal function with CALLVIRT, i.e. we can call those function through a VTable. But we cant know in advance if a vtable slot contains an internal call or managed code. So again it is best to generate a wrapper functions for internal calls in order to save/restore the LMF. Unfortunately we need to push all arguments 2 times, because we have to save the LMF, and the LMF is currently allocated on the stack. So the stack looks like: -------------------- | method arguments | -------------------- | LMF | -------------------- | copied arguments | -------------------- AFAIK this is the way ORP works. Another way is to allocate the LMF not on the stack, but then we have additional overhead to allocate/free LMF structures (and another call to arch_get_lmf_addr). Maybe it is possible to avoid this addiotional copy for internal calls by including the LMF in the C function signature. Lets say we hav a puts() function which is a internal call: ves_icall_puts (MonoString *string); If we simply modify that to include the LMF we can avoid to copy all arguments: ves_icall_puts (MonoLMF lmf, MonoString *string); But this depends somehow on the calling conventions, and I don't know if that works on all plattforms? 5.) What is stored in the LMF - all caller saved registers (since we can trust unmanaged code) - the instruction pointer of the last managed instruction - a MonoMethod pointer for the unmanaged function - the address of the thread local lfm_addr pointer (to avoid another call to arch_get_lmf_addr when restoring LMF) The LMF is allocated on the stack, so we also know the stack position for stack unwinding.