Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
136 lines (93 sloc) 5.14 KB
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.
Something went wrong with that request. Please try again.