a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
136 lines
5.1 KiB
Plaintext
136 lines
5.1 KiB
Plaintext
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.
|