il2cpp_IL2CPP内部:P /调用包装

il2cpp

This is the sixth post in the IL2CPP Internals series. In this post, we will explore how il2cpp.exe generates wrapper methods and types use for interop between managed and native code. Specifically, we will look at the difference between blittable and non-blittable types, understand string and array marshaling, and learn about the cost of marshaling.

这是IL2CPP Internals系列中的第六 。 在本文中,我们将探讨il2cpp.exe如何生成用于在托管代码和本机代码之间进行互操作的包装方法和类型。 具体来说,我们将研究可blittable类型和不可bbltable类型之间的区别,了解字符串和数组封送处理,并了解封送处理的成本。

I’ve written a good bit of managed to native interop code in my days, but getting p/invoke declarations right in C# is still difficult, to say the least. Understanding what the runtime is doing to marshal my objects is even more of a mystery. Since IL2CPP does most of its marshaling in generated C++ code, we can see (and even debug!) its behavior, providing much better insight for troubleshooting and performance analysis.

过去,我已经写了很多托管本机互操作代码,但是至少可以说很难在C#中正确地进行p / invoke声明。 了解运行时正在整理我的对象的内容甚至是一个谜。 由于IL2CPP在生成的C ++代码中进行了大多数封送处理,因此我们可以看到(甚至调试!)其行为,从而为故障排除和性能分析提供了更好的见解。

This post does not aim to provide general information about marshaling and native interop. That is a wide topic, too large for one post. The Unity documentation discusses how native plugins interact with Unity. Both Mono and Microsoft provide plenty of excellent information about p/invoke in general.

本文不旨在提供有关封送和本机互操作的一般信息。 这是一个广泛的话题,对于一个帖子来说太大了。 Unity文档讨论了本机插件如何与Unity交互。 通常, MonoMicrosoft都提供了大量有关p / invoke的出色信息。

As with all of the posts in this series, we will be exploring code that is subject to change and, in fact, is likely to change in a newer version of Unity. However, the concepts should remain the same. Please take everything discussed in this series as implementation details. We like to expose and discuss details like this when it is possible though!

与本系列的所有文章一样,我们将探索可能会更改的代码,实际上,在新版本的Unity中,代码可能会更改。 但是,概念应保持不变。 请把本系列中讨论的所有内容作为实现细节。 我们希望尽可能地公开和讨论这样的细节!

设置 (The setup)

For this post, I’m using Unity 5.0.2p4 on OSX. I’ll build for the iOS platform, using an “Architecture” value of “Universal”. I’ve built my native code for this example in Xcode 6.3.2 as a static library for both ARMv7 and ARM64.

对于本文,我在OSX上使用Unity 5.0.2p4。 我将使用“ Universal”的“ Architecture”值为iOS平台进行构建。 我已经在Xcode 6.3.2中为该示例构建了本机代码,作为ARMv7和ARM64的静态库。

The native code looks like this:

本机代码如下所示:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <cstring>
#include <cmath>
extern "C" {
int Increment(int i) {
return i + 1;
}
bool StringsMatch(const char* l, const char* r) {
return strcmp(l, r) == 0;
}
struct Vector {
float x;
float y;
float z;
};
float ComputeLength(Vector v) {
return sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
}
void SetX(Vector* v, float value) {
v->x = value;
}
struct Boss {
char* name;
int health;
};
bool IsBossDead(Boss b) {
return b.health == 0;
}
int SumArrayElements(int* elements, int size) {
int sum = 0;
for (int i = 0; i < size; ++i) {
sum += elements[i];
}
return sum;
}
int SumBossHealth(Boss* bosses, int size) {
int sum = 0;
for (int i = 0; i < size; ++i) {
sum += bosses[i].health;
}
return sum;
}
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <cstring>
#include <cmath>
extern "C" {
int Increment ( int i ) {
return i + 1 ;
}
bool StringsMatch ( const char * l , const char * r ) {
return strcmp ( l , r ) == 0 ;
}
struct Vector {
float x ;
float y ;
float z ;
} ;
float ComputeLength ( Vector v ) {
return sqrt ( v . x * v . x + v . y * v . y + v . z * v . z ) ;
}
void SetX ( Vector * v , float value ) {
v -> x = value ;
}
struct Boss {
char * name ;
int health ;
} ;
bool IsBossDead ( Boss b ) {
return b . health == 0 ;
}
int SumArrayElements ( int * elements , int size ) {
int sum = 0 ;
for ( int i = 0 ; i < size ; ++ i ) {
sum += elements [ i ] ;
}
return sum ;
}
int SumBossHealth ( Boss * bosses , int size ) {
int sum = 0 ;
for ( int i = 0 ; i < size ; ++ i ) {
sum += bosses [ i ] . health ;
}
return sum ;
}
}

The scripting code in Unity is again in the HelloWorld.cs file. It looks like this:

Unity中的脚本代码再次位于HelloWorld.cs文件中。 看起来像这样:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Start () {
Debug.Log (string.Format ("Using a blittable argument: {0}", Increment (42)));
Debug.Log (string.Format ("Marshaling strings: {0}", StringsMatch ("Hello", "Goodbye")));
var vector = new Vector (1.0f, 2.0f, 3.0f);
Debug.Log (string.Format ("Marshaling a blittable struct: {0}", ComputeLength (vector)));
SetX (ref vector, 42.0f);
Debug.Log (string.Format ("Marshaling a blittable struct by reference: {0}", vector.x));
Debug.Log (string.Format ("Marshaling a non-blittable struct: {0}", IsBossDead (new Boss("Final Boss", 100))));
int[] values = {1, 2, 3, 4};
Debug.Log(string.Format("Marshaling an array: {0}", SumArrayElements(values, values.Length)));
Boss[] bosses = {new Boss("First Boss", 25), new Boss("Second Boss", 45)};
Debug.Log(string.Format("Marshaling an array by reference: {0}", SumBossHealth(bosses, bosses.Length)));
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Start ( ) {
Debug . Log ( string . Format ( "Using a blittable argument: {0}" , Increment ( 42 ) ) ) ;
Debug . Log ( string . Format ( "Marshaling strings: {0}" , StringsMatch ( "Hello" , "Goodbye" ) ) ) ;
var vector = new Vector ( 1.0f , 2.0f , 3.0f ) ;
Debug . Log ( string . Format ( "Marshaling a blittable struct: {0}" , ComputeLength ( vector ) ) ) ;
SetX ( ref vector , 42.0f ) ;
Debug . Log ( string . Format ( "Marshaling a blittable struct by reference: {0}" , vector . x ) ) ;
Debug . Log ( string . Format ( "Marshaling a non-blittable struct: {0}" , IsBossDead ( new Boss ( "Final Boss" , 100 ) ) ) ) ;
int [ ] values = { 1 , 2 , 3 , 4 } ;
Debug . Log ( string . Format ( "Marshaling an array: {0}" , SumArrayElements ( values , values . Length ) ) ) ;
Boss [ ] bosses = { new Boss ( "First Boss" , 25 ) , new Boss ( "Second Boss" , 45 ) } ;
Debug . Log ( string . Format ( "Marshaling an array by reference: {0}" , SumBossHealth ( bosses , bosses . Length ) ) ) ;
}

Each of the method calls in this code are made into the native code shown above. We will look at the managed method declaration for each method as we see it later in the post.

此代码中的每个方法调用均制成上面显示的本机代码。 我们将在后面的文章中查看每个方法的托管方法声明。

为什么我们需要封送处理? (Why do we need marshaling?)

Since IL2CPP is already generating C++ code, why do we need marshaling from C# to C++ code at all? Although the generated C++ code is native code, the representation of types in C# differs from C++ in a number of cases, so the IL2CPP runtime must be able to convert back and forth from representations on both sides. The il2cpp.exe utility does this both for types and methods.

由于IL2CPP已经在生成C ++代码,为什么我们需要从C#到C ++代码的封送处理呢? 尽管生成的C ++代码是本机代码,但是C#中类型的表示形式在许多情况下与C ++不同,因此IL2CPP运行时必须能够从两侧的表示形式来回转换。 il2cpp.exe实用程序对类型和方法都执行此操作。

In managed code, all types can be categorized as either blittable or non-blittable. Blittable types have the same representation in managed and native code (e.g. byte, int, float). Non-blittable types have a different representation in managed and native code (e.g. bool, string, array types). As such, blittable types can be passed to native code directly, but non-blittable types require some conversion before they can be passed to native code. Often this conversion involves new memory allocation.

在托管代码中,所有类型都可以归类为blittablenon-blittable 。 可托管类型在托管代码和本机代码中具有相同的表示形式(例如, byteintfloat )。 不可引用类型在托管代码和本机代码中具有不同的表示形式(例如boolstring ,array类型)。 这样,可blittable类型可以直接传递给本机代码,但是不可bbltable类型必须先进行一些转换,然后才能传递给本机代码。 通常,此转换涉及新的内存分配。

In order to tell the managed code compiler that a given method is implemented in native code, the extern keyword is used in C#. This keyword, along with a DllImport attribute, allows the managed code runtime to find the native method definition and call it. The il2cpp.exe utility generates a wrapper C++ method for each extern method. This wrapper performs a few important tasks:

为了告诉托管代码编译器给定的方法是在本机代码中实现的,在C#中使用了extern关键字。 此关键字与DllImport属性一起,允许托管代码运行时查找本机方法定义并对其进行调用。 il2cpp.exe实用程序为每个extern方法生成一个包装器C ++方法。 该包装器执行一些重要的任务:

    We’ll take a look at the generated wrapper methods for some extern method declarations next.

    接下来,我们将看看为某些外部方法声明生成的包装器方法。

    封送可蓝调类型 (Marshaling a blittable type)

    The simplest kind of extern wrapper only deals with blittable types.

    最简单的外部包装器仅处理可漂白类型。

    1

    2
    [DllImport("__Internal")]
    private extern static int Increment(int value);

    1

    2
    [ DllImport ( "__Internal" ) ]
    private extern static int Increment ( int value ) ;

    In the Bulk_Assembly-CSharp_0.cpp file, search for the string “HelloWorld_Increment_m3”. The wrapper function for the Increment method looks like this:

    在Bulk_Assembly-CSharp_0.cpp文件中,搜索字符串“ HelloWorld_Increment_m3”。 Increment方法的包装函数如下所示:

    1

    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    extern "C" {int32_t DEFAULT_CALL Increment(int32_t);}
    extern "C" int32_t HelloWorld_Increment_m3 (Object_t * __this /* static, unused */, int32_t ___value, const MethodInfo* method)
    {
    typedef int32_t (DEFAULT_CALL *PInvokeFunc) (int32_t);
    static PInvokeFunc _il2cpp_pinvoke_func;
    if (!_il2cpp_pinvoke_func)
    {
    _il2cpp_pinvoke_func = (PInvokeFunc)Increment;
    if (_il2cpp_pinvoke_func == NULL)
    {
    il2cpp_codegen_raise_exception(il2cpp_codegen_get_not_supported_exception("Unable to find method for p/invoke: 'Increment'"));
    }
    }
    int32_t _return_value = _il2cpp_pinvoke_func(___value);
    return _return_value;
    }

    1

    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    extern "C" { int32_t DEFAULT_CALL Increment ( int32_t ) ; }
    extern "C" int32_t HelloWorld_Increment_m3 ( Object_t * __this /* static, unused */ , int32_t ___value , const MethodInfo * method )
    {
    typedef int32_t ( DEFAULT_CALL * PInvokeFunc ) ( int32_t ) ;
    static PInvokeFunc _il2cpp_pinvoke_func ;
    if ( ! _il2cpp_pinvoke_func )
    {
    _il2cpp_pinvoke_func = ( PInvokeFunc ) Increment ;
    if ( _il2cpp_pinvoke_func == NULL )
    {
    il2cpp_codegen_raise_exception ( il2cpp_codegen_get_not_supported_exception ( "Unable to find method for p/invoke: 'Increment'" ) ) ;
    }
    }
    int32_t _return_value = _il2cpp_pinvoke_func ( ___value ) ;
    return _return_value ;
    }

    First, note the typedef for the native function signature:

    首先,请注意本机函数签名的typedef:

    1

    typedef int32_t (DEFAULT_CALL *PInvokeFunc) (int32_t);

    1

    typedef int32_t ( DEFAULT_CALL * PInvokeFunc ) ( int32_t ) ;

    Something similar will show up in each of the wrapper functions. This native function accepts a single int32_t and returns an int32_t.

    每个包装函数中都会显示类似的内容。 此本机函数接受单个int32_t并返回int32_t

    Next, the wrapper finds the proper function pointer and stores it in a static variable:

    接下来,包装器找到合适的函数指针并将其存储在静态变量中:

    1

    _il2cpp_pinvoke_func = (PInvokeFunc)Increment;

    1

    _il2cpp_pinvoke_func = ( PInvokeFunc ) Increment ;

    Here the Increment function actually comes from an extern statement (in the C++ code):

    这里的Increment函数实际上来自extern语句(在C ++代码中):

    1

    extern "C" {int32_t DEFAULT_CALL Increment(int32_t);}

    1

    extern "C" { int32_t DEFAULT_CALL Increment ( int32_t ) ; }

    On iOS, native methods are statically linked into a single binary (indicated by the “__Internal” string in the DllImport attribute), so the IL2CPP runtime does nothing to look up the function pointer. Instead, this extern statement informs the linker to find the proper function at link time. On other platforms, the IL2CPP runtime may perform a lookup (if necessary) using a platform-specific API method to obtain this function pointer.

    在iOS上,本机方法静态链接到单个二进制文件中(由DllImport属性中的“ __Internal”字符串指示),因此IL2CPP运行时不执行任何操作来查找函数指针。 而是,此extern语句通知链接程序在链接时找到正确的功能。 在其他平台上,IL2CPP运行时可以使用特定于平台的API方法执行查找(如果需要)以获取此功能指针。

    Practically, this means that on iOS, an incorrect p/invoke signature in managed code will show up as a linker error in the generated code. The error will not occur at  runtime. So all p/invoke signatures need to be correct, even with they are not used at runtime.

    实际上,这意味着在iOS上,托管代码中不正确的p / invoke签名将在生成的代码中显示为链接器错误 。 在运行时不会发生该错误。 因此,即使在运行时未使用所有p / invoke签名,也必须正确。

    Finally, the native method is called via the function pointer, and the return value is returned. Notice that the argument is passed to the native function by value, so any changes to its value in the native code will not be available in the managed code, as we would expect.

    最后,通过函数指针调用本机方法,并返回返回值。 请注意,参数是通过值传递给本机函数的,因此,正如我们期望的那样,在本机代码中对其值的任何更改将在托管代码中不可用。

    封送非强制类型 (Marshaling a non-blittable type)

    Things get a little more exciting with a non-blittable type, like string. Recall from an earlier post that strings in IL2CPP are represented as an array of two-byte characters encoded via UTF-16, prefixed by a 4-byte length value. This representation does not match either the char* or wchar_t* representations of strings in C on iOS, so we have to do some conversion. If we look at the StringsMatch method (HelloWorld_StringsMatch_m4 in the generated code):

    不可string类型(例如string事情变得更加令人兴奋。 回想一下以前的文章 ,IL2CPP中的字符串表示为通过UTF-16编码的两字节字符数组,其前缀为4字节长度值。 此表示形式与iOS上C语言中字符串的char*wchar_t*表示形式都不匹配,因此我们必须进行一些转换。 如果我们看一下StringsMatch方法(生成的代码中的HelloWorld_StringsMatch_m4 ):

    1

    2
    3
    DllImport("__Internal")]
    [return: MarshalAs(UnmanagedType.U1)]
    private extern static bool StringsMatch([MarshalAs(UnmanagedType.LPStr)]string l, [MarshalAs(UnmanagedType.LPStr)]string r);

    1

    2
    3
    DllImport ( "__Internal" ) ]
    [ return : MarshalAs ( UnmanagedType . U1 ) ]
    private extern static bool StringsMatch ( [ MarshalAs ( UnmanagedType . LPStr ) ] string l , [ MarshalAs ( UnmanagedType . LPStr ) ] string r ) ;

    We can see that each string argument will be converted to a char* (due to the UnmangedType.LPStr directive).

    我们可以看到,每个字符串参数都将转换为char* (由于UnmangedType.LPStr指令)。

    1

    typedef uint8_t (DEFAULT_CALL *PInvokeFunc) (char*, char*);

    1

    typedef uint8_t ( DEFAULT_CALL * PInvokeFunc ) ( char * , char * ) ;

    The conversion looks like this (for the first argument):

    转换如下所示(对于第一个参数):

    1

    2
    char* ____l_marshaled = { 0 };
    ____l_marshaled = il2cpp_codegen_marshal_string(___l);

    1

    2
    char * ____l_marshaled = { 0 } ;
    ____l_marshaled = il2cpp_codegen_marshal_string ( ___l ) ;

    A new char buffer of the proper length is allocated, and the contents of the string are copied into the new buffer. Of course, after the native method is called we need to clean up those allocated buffers:

    分配了适当长度的新char缓冲区,并将字符串的内容复制到新缓冲区中。 当然,在调用本机方法之后,我们需要清理那些分配的缓冲区:

    1

    2
    il2cpp_codegen_marshal_free(____l_marshaled);
    ____l_marshaled = NULL;

    1

    2
    il2cpp_codegen_marshal_free ( ____l_marshaled ) ;
    ____l_marshaled = NULL ;

    So marshaling a non-blittable type like string can be costly.

    因此,封送不可字符串的类型(例如字符串)可能会非常昂贵。

    封送用户定义的类型 (Marshaling a user-defined type)

    Simple types like int and string are nice, but what about a more complex, user defined type? Suppose we want to marshal the Vector structure above, which contains three float values. It turns out that a user defined type is blittable if and only if all of its fields are blittable. So we can call ComputeLength (HelloWorld_ComputeLength_m5 in the generated code) without any need to convert the argument:

    intstring这样的简单类型都不错,但是更复杂的用户定义类型呢? 假设我们要封送上面的Vector结构,其中包含三个float值。 事实证明, 只有当其所有字段都是blittable时, 用户定义的类型才是blittable 。 因此我们可以调用ComputeLength (在生成的代码中为HelloWorld_ComputeLength_m5 ),而无需转换参数:

    1

    2
    3
    4
    5
    6
    typedef float (DEFAULT_CALL *PInvokeFunc) (Vector_t1 );
    // I’ve omitted the function pointer code.
    float _return_value = _il2cpp_pinvoke_func(___v);
    return _return_value;

    1

    2
    3
    4
    5
    6
    typedef float ( DEFAULT_CALL * PInvokeFunc ) ( Vector _ t1 ) ;
    // I’ve omitted the function pointer code.
    float _return_value = _il2cpp_pinvoke_func ( ___v ) ;
    return _return_value ;

    Notice that the argument is passed by value, just as it was for the initial example when the argument type was int. If we want to modify the instance of Vector and see those changes in managed code, we need to pass it by reference, as in the SetX method (HelloWorld_SetX_m6):

    请注意,参数是通过值传递的,就像参数类型为int的初始示例一样。 如果我们想修改Vector的实例并查看托管代码中的更改,则需要像SetX方法( HelloWorld_SetX_m6 )中那样通过引用传递它:

    1

    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    typedef float (DEFAULT_CALL *PInvokeFunc) (Vector_t1 *, float);
    Vector_t1 * ____v_marshaled = { 0 };
    Vector_t1  ____v_marshaled_dereferenced = { 0 };
    ____v_marshaled_dereferenced = *___v;
    ____v_marshaled = &____v_marshaled_dereferenced;
    float _return_value = _il2cpp_pinvoke_func(____v_marshaled, ___value);
    Vector_t1  ____v_result_dereferenced = { 0 };
    Vector_t1 * ____v_result = &____v_result_dereferenced;
    *____v_result = *____v_marshaled;
    *___v = *____v_result;
    return _return_value;

    1

    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    typedef float ( DEFAULT_CALL * PInvokeFunc ) ( Vector_t1 * , float ) ;
    Vector_t1 * ____v_marshaled = { 0 } ;
    Vector _ t1  ____v_marshaled_dereferenced = { 0 } ;
    ____v_marshaled_dereferenced = * ___v ;
    ____v_marshaled = &____v_marshaled_dereferenced ;
    float _return_value = _il2cpp_pinvoke_func ( ____v_marshaled , ___value ) ;
    Vector _ t1  ____v_result_dereferenced = { 0 } ;
    Vector_t1 * ____v_result = &____v_result_dereferenced ;
    * ____v_result = * ____v_marshaled ;
    * ___v = * ____v_result ;
    return _return_value ;

    Here the Vector argument is passed as a pointer to native code. The generated code goes through a bit of a rigmarole, but it is basically creating a local variable of the same type, copying the value of the argument to the local, then calling the native method with a pointer to that local variable. After the native function returns, the value in the local variable is copied back into the argument, and that value is available in the managed code then.

    在这里, Vector参数作为指向本机代码的指针传递。 生成的代码要花些时间,但是它基本上是在创建相同类型的局部变量,将参数的值复制到局部变量,然后使用指向该局部变量的指针调用本地方法。 返回本机函数后,将局部变量中的值复制回参数中,然后该值可在托管代码中使用。

    封送不可引用的用户定义类型 (Marshaling a non-blittable user defined type)

    A non-blittable user defined type, like the Boss type defined above can also be marshaled, but with a little more work. Each field of this type must be marshaled to its native representation. Also, the generated C++ code needs a representation of the managed type that matches the representation in the native code.

    不可定义的用户定义类型(如上面定义的Boss类型)也可以编组,但需要做更多的工作。 此类型的每个字段都必须编组为其原始表示形式。 同样,生成的C ++代码需要与本地代码中的表示形式相匹配的托管类型的表示形式。

    Let’s take a look at the IsBossDead extern declaration:

    让我们看一下IsBossDead extern声明:

    1

    2
    3
    [DllImport("__Internal")]
    [return: MarshalAs(UnmanagedType.U1)]
    private extern static bool IsBossDead(Boss b);

    1

    2
    3
    [ DllImport ( "__Internal" ) ]
    [ return : MarshalAs ( UnmanagedType . U1 ) ]
    private extern static bool IsBossDead ( Boss b ) ;

    The wrapper for this method is named HelloWorld_IsBossDead_m7:

    该方法的包装器名为HelloWorld_IsBossDead_m7

    1

    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    extern "C" bool HelloWorld_IsBossDead_m7 (Object_t * __this /* static, unused */, Boss_t2  ___b, const MethodInfo* method)
    {
    typedef uint8_t (DEFAULT_CALL *PInvokeFunc) (Boss_t2_marshaled);
    Boss_t2_marshaled ____b_marshaled = { 0 };
    Boss_t2_marshal(___b, ____b_marshaled);
    uint8_t _return_value = _il2cpp_pinvoke_func(____b_marshaled);
    Boss_t2_marshal_cleanup(____b_marshaled);
    return _return_value;
    }

    1

    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    extern "C" bool HelloWorld_IsBossDead_m7 ( Object_t * __this /* static, unused */ , Boss _ t2  ___b , const MethodInfo * method )
    {
    typedef uint8_t ( DEFAULT_CALL * PInvokeFunc ) ( Boss_t2_marshaled ) ;
    Boss_t2_marshaled ____b_marshaled = { 0 } ;
    Boss_t2_marshal ( ___b , ____b_marshaled ) ;
    uint8_t _return_value = _il2cpp_pinvoke_func ( ____b_marshaled ) ;
    Boss_t2_marshal_cleanup ( ____b_marshaled ) ;
    return _return_value ;
    }

    The argument is passed to the wrapper function as type Boss_t2, which is the generated type for the Boss struct. Notice that it is passed to the native function with a different type: Boss_t2_marshaled. If we jump to the definition of this type, we can see that it matches the definition of the Boss struct in our C++ static library code:

    参数以类型Boss_t2形式传递给包装函数,该类型是Boss结构的生成类型。 请注意,它以另一种类型传递给本机函数: Boss_t2_marshaled 。 如果跳转到此类型的定义,我们可以看到它与C ++静态库代码中Boss结构的定义匹配:

    1

    2
    3
    4
    5
    struct Boss_t2_marshaled
    {
    char* ___name_0;
    int32_t ___health_1;
    };

    1

    2
    3
    4
    5
    struct Boss_t2_marshaled
    {
    char * ___name_0 ;
    int32_t ___health_1 ;
    } ;

    We again used the UnmanagedType.LPStr directive in C# to indicate that the string field should be marshaled as a char*. If you find yourself debugging a problem with a non-blittable user-defined type, it is very helpful to look at this _marshaled struct in the generated code. If the field layout does not match the native side, then a marshaling directive in managed code might be incorrect.

    我们再次在C#中使用UnmanagedType.LPStr指令来指示应将字符串字段编组为char*如果您发现自己使用不可自定义的用户定义类型调试问题,那么在生成的代码中查看此_marshaled结构非常有帮助。 如果字段布局与本机面不匹配,则托管代码中的编组指令可能不正确。

    The Boss_t2_marshal function is a generated function which marshals each field, and the Boss_t2_marshal_cleanup frees any memory allocated during that marshaling process.

    Boss_t2_marshal函数是一个生成的函数,用于封送每个字段,并且Boss_t2_marshal_cleanup释放在该封送处理过程中分配的所有内存。

    封送数组 (Marshaling an array)

    Finally, we will explore how arrays of blittable and non-blittable types are marshaled. The SumArrayElements method is passed an array of integers:

    最后,我们将探讨如何将可Bbltable和不可Bbltable类型的数组编组。 SumArrayElements方法传递一个整数数组:

    1

    2
    [DllImport("__Internal")]
    private extern static int SumArrayElements(int[] elements, int size);

    1

    2
    [ DllImport ( "__Internal" ) ]
    private extern static int SumArrayElements ( int [ ] elements , int size ) ;

    This array is marshaled, but since the element type of the array (int) is blittable, the cost to marshal it is very small:

    该数组已编组,但是由于数组( int )的元素类型是可拆分的,因此编组它的成本非常小:

    1

    2
    int32_t* ____elements_marshaled = { 0 };
    ____elements_marshaled = il2cpp_codegen_marshal_array<int32_t>((Il2CppCodeGenArray*)___elements);

    1

    2
    int32_t * ____elements_marshaled = { 0 } ;
    ____elements_marshaled = il2cpp_codegen_marshal_array < int32_t > ( ( Il2CppCodeGenArray * ) ___elements ) ;

    The il2cpp_codegen_marshal_array function simply returns a pointer to the existing managed array memory, that’s it!

    il2cpp_codegen_marshal_array函数简单地返回一个指向现有托管阵列内存的指针!

    However, marshaling an array of non-blittable types is much more expensive. The SumBossHealth method passes an array of Boss instances:

    但是,封送非可拆分类型数组的成本要高得多。 SumBossHealth方法传递一组Boss实例:

    1

    2
    [DllImport("__Internal")]
    private extern static int SumBossHealth(Boss[] bosses, int size);

    1

    2
    [ DllImport ( "__Internal" ) ]
    private extern static int SumBossHealth ( Boss [ ] bosses , int size ) ;

    It’s wrapper has to allocate a new array, then marshal each element individually:

    包装程序必须分配一个新数组,然后分别封送每个元素:

    1

    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Boss_t2_marshaled* ____bosses_marshaled = { 0 };
    size_t ____bosses_Length = 0;
    if (___bosses != NULL)
    {
    ____bosses_Length = ((Il2CppCodeGenArray*)___bosses)->max_length;
    ____bosses_marshaled = il2cpp_codegen_marshal_allocate_array<Boss_t2_marshaled>(____bosses_Length);
    }
    for (int i = 0; i < ____bosses_Length; i++)
    {
    Boss_t2  const& item = *reinterpret_cast<Boss_t2 *>(SZArrayLdElema((Il2CppCodeGenArray*)___bosses, i));
    Boss_t2_marshal(item, (____bosses_marshaled)[i]);
    }

    1

    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Boss_t2_marshaled * ____bosses_marshaled = { 0 } ;
    size_t ____bosses_Length = 0 ;
    if ( ___bosses != NULL )
    {
    ____bosses_Length = ( ( Il2CppCodeGenArray * ) ___bosses ) -> max_length ;
    ____bosses_marshaled = il2cpp_codegen_marshal_allocate_array < Boss_t2_marshaled > ( ____bosses_Length ) ;
    }
    for ( int i = 0 ; i < ____bosses_Length ; i ++ )
    {
    Boss _ t2  const & item = * reinterpret_cast < Boss_t2 * > ( SZArrayLdElema ( ( Il2CppCodeGenArray * ) ___bosses , i ) ) ;
    Boss_t2_marshal ( item , ( ____bosses_marshaled ) [ i ] ) ;
    }

    Of course all of these allocations are cleaned up after the native method call is completed as well.

    当然,在本机方法调用完成后,也会清理所有这些分配。

    结论 (Conclusion)

    The IL2CPP scripting backend supports the same marshalling behaviors as the Mono scripting backend. Because IL2CPP produces generated wrappers for extern methods and types, it is possible to see the cost of managed to native interop calls. For blittable types, this cost is often not too bad, but non-blittable types can quickly make interop very expensive. As usual, we’ve just scratched the surface of marshaling in this post. Please explore the generated code more to see how marshaling is done for return values and out parameters, native function pointers and managed delegates, and user-defined reference types.

    IL2CPP脚本后端支持与Mono脚本后端相同的编组行为。 因为IL2CPP为外部方法和类型生成了生成的包装器,所以可以看到托管到本机互操作调用的成本。 对于可折叠类型,此成本通常不太差,但不可折叠类型会使互操作性非常昂贵。 像往常一样,在本文中,我们仅涉及了编组的表面。 请进一步研究生成的代码,以了解如何对返回值和out参数,本机函数指针和托管委托以及用户定义的引用类型进行封送处理。

    Next time we will explore how IL2CPP integrates with the garbage collector.

    下次,我们将探讨IL2CPP如何与垃圾收集器集成。

    翻译自: https://blogs.unity3d.com/2015/07/02/il2cpp-internals-pinvoke-wrappers/

    il2cpp

    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    在Unity中,可以使用IL2CPP编译器将C#代码编译成C++代码,以提高应用程序的性能和安全性。要导出Android Export Project并使用代码执行IL2CPP编译,请按照以下步骤进行操作: 1. 首先,需要确保使用的Unity版本已经启用了IL2CPP编译器。可以在Unity Editor中的“Player Settings”中的“Other Settings”选项卡中查看。 2. 在Unity Editor中,选择“File”菜单,然后选择“Build Settings”。 3. 在“Build Settings”对话框中,选择“Android”平台,并单击“Export”按钮。选择要导出项目的文件夹并保存。 4. 打开Android Studio,并选择“Import Project”菜单。选择导出项目的文件夹并导入。 5. 在Android Studio中,打开“build.gradle(Module:app)”文件,并添加以下代码: ``` android.applicationVariants.all { variant -> def task = tasks.create "il2cpp_${variant.name.capitalize()}", Exec task.dependsOn variant.getTaskByName("compile${variant.name.capitalize()}Sources") task.commandLine "${projectDir}/il2cpp/build/il2cpp.bat", "--platform=Android", "--architecture=ARM64", "--configuration=Release", "--output-dir=${buildDir}/intermediates/il2cpp/${variant.name}/libs/arm64-v8a", "--cachedirectory=${buildDir}/intermediates/il2cpp/${variant.name}/cache", "--additional-include-directories=${projectDir}/il2cpp/include", "--libil2cpp-static", "--generate-object-code" } ``` 这将创建一个名为“il2cpp_${variant.name.capitalize()}”的任务,并将其添加到所有android应用程序变体中。该任务依赖于编译源代码的任务,并使用指定的参数调用IL2CPP编译器。 6. 现在可以使用以下命令之一来执行IL2CPP编译: - 在Android Studio中,选择“View”菜单,然后选择“Tool Windows”>“Terminal”。在终端窗口中,导航到项目文件夹并运行以下命令: ``` ./gradlew il2cpp_<VariantName> ``` 其中“<VariantName>”是您要编译的应用程序变体的名称,例如“debug”或“release”。 - 在命令行中,导航到项目文件夹并运行以下命令: ``` ./gradlew il2cpp_<VariantName> ``` 同样,其中“<VariantName>”是您要编译的应用程序变体的名称。 在执行IL2CPP编译后,生成的C++代码会放在“/app/build/intermediates/il2cpp/<VariantName>/libs/arm64-v8a/”文件夹中。
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值