Use unapproved key to introduce the number of parameters such as external printf in C #

zhaozj2021-02-12  181

Http://www.blogcn.com/User8/flier_lu/index.html?id=2602611 http://www.blogcn.com/User8/flier_lu/index.html?id=2602647

C language Because the CDECL call mode is default, the parameter variable parameters can be easily implemented. Detailed principle can refer to my other article "The History of Calling Conventions". Specifically, we are the most commonly used Printf series functions:

The following is program code: int printf (const char * format, ...);

Corresponding to C #, similar syntax is similar to the PARAMS keyword:

The following is program code: use system; public class myclass {public static void useparams (params int [] list) {for (int i = 0; i

It can be seen that this params keyword is actually a semantic semantic to the array. The grammatical enhancement is made in the C # compiler level to simulate the syntax and semantics of C ... Looking at it at the IL code level.

The following program code is: .class public auto ansi beforefieldinit MyClass extends [mscorlib] System.Object {.method public hidebysig static void UseParams (int32 [] list) cil managed {// ...} .method public hidebysig static void UseParams2 (Object [] list) CIL Managed {// ...} .method public hidebysig static void main () CIL managed {.entrypoint // code size 93 (0x5d) .maxstack 3 .locals init (int32 [] v_0, int32 [] V_1, object [] v_2) IL_0000: ldc.i4.3 il_0001: newarr [mscorlib] system.int32 // Constructs a size 3 INT array // ... IL_0014: Call void myclass :: useparams (int32 []) // ...}} This syntax sugar should be enough to meet the demand at this level, but if there is a problem with the interaction with the existing C code, the disadvantage of its simulation is exposed. . For example, the Signature of the Printf function mentioned earlier is not the use of params using analog syntax. The solution given in MSDN is:

The following is a program code: using System; using System.Runtime.InteropServices; public class LibWrap {// C # does not support varargs so all arguments must be explicitly defined // CallingConvention.Cdecl must be used since the stack is //. Cleated up by the caller. // int printf (const char * format [, argument] ... [img] /images/wink.gif [/ img] [DLLIMPORT ("msvcrt.dll", charset = charset.ansi, CallingConvention = CallingConvention.Cdecl)] public static extern int printf (String format, int i, double d); [DllImport ( "msvcrt.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern int printf (String Format, INT I, STRING S); PUBLIC CLASS App {Public Static Void Main () {libwrap.printf ("Print Params:% I% F", 99, 99.99); Libwrap.Printf ("Print Params: % I% s ", 99," abcd "[img] /images/wink.gif [/ img];}}

Enumerate the possible form by defining a plurality of possible function prototypes. This implementation feels really dirty, and the word "龌龊" is appropriate, huh, huh. But actually C # or the ability of the CLR is not only this, and it has already been built in the CLR level, which has been built into the processing variable quantity parameters.

Take a closer look at the CLR library structure, you will find two ways to call the function, there are two descriptions:

The following is a program code: namespace System.Runtime.InteropServices {using System; [Serializable] public enum CallingConvention {Winapi = 1, Cdecl = 2, StdCall = 3, ThisCall = 4, FastCall = 5,}} namespace System.Reflection { using System.Runtime.InteropServices; using System; [Flags, Serializable] public enum CallingConventions {Standard = 0x0001, VarArgs = 0x0002, Any = Standard | VarArgs, HasThis = 0x0020, ExplicitThis = 0x0040,}}

System.Runtime.InteropServices.CallingConvention is used when using the DLLIMPORT property to define an external reference function, so the names used are similar to existing programming language naming. SYSTEM.REFLECTION.CALLINGCONVENSTEM.REFLECTION.CALLINGCONVENVENVENVENVENSTENS is internal for Reflection operations, so the names used are directly defined directly to the CLR.

Callingconventions.varargs here is the key to our problem. In the Tool Developers Guide provided with .NET Framework SDK, this is the way in the Partition II Metadata.doc documentation describing the VARARGS call mode:

The following is quoted:

Vararg Methods

VARARG METHODS Accept A Variable Number of Arguments. They Shall Use The VARARG CALLING CONVENTION (See Section 14.3).

At each call site, a method reference shall be used to describe the types of the actual arguments that are passed. The fixed part of the argument list shall be separated from the additional arguments with an ellipsis (see Partition I).

The vararg arguments shall be accessed by obtaining a handle to the argument list using the CIL instruction arglist (see Partition III). The handle may be used to create an instance of the value type System.ArgIterator which provides a typesafe mechanism for accessing the arguments . (see Partition IV) below as program code: [b] Example (informative): [/ b] The following example shows how a vararg method is declared and how the first vararg argument is accessed, assuming that at least one additional argument was passed to the method: .method public static vararg void MyMethod (int32 required) {.maxstack 3.locals init (valuetype System.ArgIterator it, int32 x) ldloca it // initialize the iteratorinitobj valuetype System.ArgIteratorldloca itarglist // obtain the argument handlecall instance void System.ArgIterator ::. ctor (valuetype System.RuntimeArgumentHandle) // call constructor of iterator / * argument value will be stored in x when retrieved, so load address of x * / ldloca xldloca it // retrieve the ar gument, the argument for required does not mattercall instance typedref System.ArgIterator :: GetNextArg () call object System.TypedReference :: ToObject (typedref) // retrieve the objectcastclass System.Int32 // cast and unboxunbox int32cpobj int32 // copy the value INTO X // first var Vararg argument is stored in xret}

It can be seen that the CLR level is actually supported by the number of variable parameters of the parameters, but the PARAMS keyword of the C # is not used because some reasons are not used. And if you examine the implementation of Managed C , it will find that it is using this mechanism.

The following is the program code: // cl / clr param.cpp # include #include void show (const char * fmt, ...) {va_list args; va_start (args, fmt) Vprintf (fmt, args); va_end (args);} int main (int Argc, const char * argv []) {show ("% s% d", "flier lu", 1024)

Managed code is compiled into which signature functions as follows: The following program code is: .method public static pinvokeimpl (/ * No map * /) vararg void modopt ([mscorlib] System.Runtime.CompilerServices.CallConvCdecl) show (int8 modopt ( [Microsoft.visualc] Microsoft.visualc.nosignSpecifiedModifier) ​​Modopt ([Microsoft.visualc] Microsoft.visualc.isconstmodifier * A_0) Native unmanaged preservesig {// ...}

In fact, hidden support for the VARARG type method definition and call is also provided in C #, that is, __ARGLIST keyword.

The following program code is: public class UndocumentedCSharp {[DllImport ( "msvcrt.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] extern static int printf (string format, __arglist); public static void Main (String [ ] Args) {Printf ("% s% d", __ARGLIST ("Flier Lu", 1024));}}

It can be seen that __ARGLIST keyword actually plays a similar role in VA_LIST in C , directly pressing any multiple parameters in order, and processes when calling. At the IL code level, it is completely similar to the above IL assembly and managed C :

The following program code:. .Method private hidebysig static pinvokeimpl ( "msvcrt.dll" ansi cdecl) vararg int32 printf (string format) cil managed preservesig {} method public hidebysig static void Main (string [] args) cil managed {IL_0033 : Ldstr "% s% D" IL_0038: ldstr "flier lu" IL_003D: ldc.i4 0x400 IL_0042: Call Varg Int32 undocumentedcsharp :: Printf (string, ..., string, int32)}

__ARGLIST can be used to use the existing code to be used in C # as a feature that is equivalent to the params function in C #. Only because there is no C # compiler in semantic level support, you must operate in a relatively complex manner.

The following is a program code: using System; using System.Runtime.InteropServices; public class UndocumentedCSharp {private static void Show (__ arglist) {ArgIterator it = new ArgIterator (__ arglist); while (it.GetRemainingCount ()> 0) {TypedReference tr = IT.GetNextarg (); console.out.writeline ("{0}: {1}", typedreference.toobject (tr), __reftype (tr));}} public static void main (String [] args) {show (__ARGLIST ("Flier Lu", 1024);}} Different from C , __ arglist parameters do not require a leading parameter to determine their starting position in the stack.

Argiterator is a dedicated iterator that supports unidirectional traversal of the parameter list. For each parameter item, getNextarg will return a TypedReference type to indicate the pointing parameter.

To understand the principles of implementation here, you must first introduce the TypedReference type.

We know that C # provides a number of name mappings of many CLR internal value types, such as INT32 being mapped to int, etc. in C #. However, there are actually three CLR types that are not mapped to language level alias: intptr, uintptr, and typedreference. These three types are called Native Int, Native Unsigned Int and Typedref at the IL level, respectively. But at C # level, you can only access in a similar way in System.TypedReference. The TypedReference is the most strange.

The description of TypedReference in MSDN is as follows:

The following is quoted:

Describes Objects That Contain Both a Managed Pointer To a location and a runtime representation of the Type That May Be stored at this location.

[CLSCompliant (false)]

Public struct TypedReference

Remarks

A Typed Reference Is A Type / Value Combination Used for VARARGS AND Other Support. TypedReference IS A Built-in Value Type That Can Be Used for Parameters and Local Variables.

Arrays of TypedReference Objects Cannot Be created. For Example, The Following Call is invalid:

AskEMBLY.LOAD ("mscorlib.dll"). Gettype ("System.typedReference []");

That is, the value type TypedReference is specifically designed to save the hosting pointer and its pointing content type, viewing its real current code (BclsystemTypedReference.cs: 28) can verify this:

The following is the program code: public struct type; // other method} This property} The value of the object is saved, and Type saves the object's type handle.

You can use __arglist.getnextarg () when you use it, you can also use the __makeref keyword construct, such as:

The following is program code: int i = 21; typedreference tr = __makeref (i);

The objects and types stored in it can be obtained using the __refvalue and __reftype keywords.

The following is the program code: int i = 32; TypedReference TR1 = __ Makeref (i); console.out.writeline ("{0}: {1}", __refview (tr, int), __refType (TR1));

Note that the __refvalue keywords need to specify the target type type of target typedreference and conversion. If the type saved in the structure cannot be implicitly converted to the target type, the conversion exception will be thrown. Relatively, the TypedReference.ToObject is required to require a mandatory BOX target value, but it is more ease of use.

From the perspective of implementation, __ refValue and __refType are directly removed by TypedReference, so the highest efficiency.

The following is program code: int i = 5; type.out.writeline ("{0}: {1}", __refview (tr, int), __refview (tr), __refty (tr));

The above one code segment will be compiled into:

The following is program code: IL_0048: LDC.i4.5 IL_0049: STLOC.0 IL_004A: LDLOCA.S V_0 IL_004C: mkrefany [mscorlib] System.Int32 IL_0051: Stloc.1 IL_0052: Call Class [mscorlib] system.io.textwriter [mscorlib] system.console :: get_out () il_0057: ldstr "{0}: {1}" ip_005c: ldloc.1 il_005d: refanyval [mscorlib] system.int32 il_0062: ldind.i4 IL_0063: Box [mscorlib] System. int32 IL_0068: ldloc.1 IL_0069: refanytype IL_006b: call class [mscorlib] System.Type [mscorlib] System.Type :: GetTypeFromHandle (valuetype [mscorlib] System.RuntimeTypeHandle) IL_0070: callvirt instance void [mscorlib] System.IO.TextWriter :: WriteLine (String, Object, Object)

You can see __makeref, __ refview and __reftype are directly implemented by the keywords Mkrefany, RefanyVal, and REFANyTYPE through the IL language. And this implementation is done by directing the stack, there is no need to implicit Box / UNBOX operation as TypedReference.ToObject, so the efficiency is the highest. The implementation of Refanyval in JIT (Fjit Jit.cpp: 8361) is as follows:

The following is the program code: fjitresult fjit :: compilecee_refanytype () {// there be a refany on the stack check_stack (1); // there Has to be a typedref on the stack // this stay be a validity check accounting to the spec, because the spec says // that REFANYTYPE is always verifiable However, V1 .NET Framework throws verification exception // so to match this behavior this is a verification check as well VERIFICATION_CHECK (topOpE () == typeRefAny);.. / / POP OFF THE REFANY POP_STACK (1); _ASSERTE (OFFSETO_REFANY, TYPE) == sizeof (void *)); // type is the second thing Emit_win32 (Emit_POP_I4 ()) Emit_Win64 (Emit_POP_I8 ()); // Just pop off the data, leaving the type CORINFO_CLASS_HANDLE s_TypeHandleClass = jitInfo-> getBuiltinClass (CLASSID_TYPE_HANDLE);. VALIDITY_CHECK (s_TypeHandleClass = NULL!); pushOp (OpType (typeValClass, s_TypeHandleClass)); return FJIT_OK;}

As can be seen from the above code, JIT does not perform any operation on the stack content when processing the RefanyVal instruction, but the stack is directly operating.

If you want to learn more about information, you can refer to the following description:

Undocunted C # types and keywords

Undocumented TypedReference

A Sample Chapter from C # programmers Reference - Value Types

PS: It was found that MS did not disclose VARARG this call mode, which is probably because of the efficiency. Compared with Params, use VARARG's call mode, the speed of pure function calls to reduce a quantitude: (

Here's this article also discusses this problem. The conclusion is not available as possible, huh, huh.

Why __ARGLIST IS undocumented

转载请注明原文地址:https://www.9cbs.com/read-6583.html

New Post(0)