使用未公開關鍵字在 C# 中匯入外部 printf 等參數數量可變函數 [2] C# 實現

來源:互聯網
上載者:User
http://www.blogcn.com/user8/flier_lu/index.html?id=2602647&run=.0A0B923

實際上,在 C# 中也提供了隱藏的對 vararg 類型方法定義和調用的支援,那就是 __arglist 關鍵字。

以下內容為程式碼:

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));
  }
}

    可以看到 __arglist 關鍵字實際上起到了和 C++ 中 va_list 類似的作用,直接將任意多個參數按順序壓入堆棧,並在調用時處理。而在 IL 代碼一級,則完全類似於上述 IL 彙編和 Managed C++ 的例子:

以下內容為程式碼:

.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       vararg int32 UndocumentedCSharp::printf(string,
                                                               ...,
                                                               string,
                                                               int32)
}

    __arglist 除了可以用於與現有代碼進行互操作,還可以在 C# 內作為與 params 功能上等同的特性來使用。只不過因為沒有 C# 編譯器在語義一級的支援,必須用相對複雜的方式進行操作。

以下內容為程式碼:

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));
  }
}

    與 C++ 中不同,__arglist 參數不需要一個前置參數來確定其在棧中的起始位置。
    ArgIterator則是一個專用迭代器,支援對參數列表進行單向遍曆。對每個參數項,GetNextArg 將會返回一個 TypedReference 類型,表示指向參數。
    要理解這裡的實現原理,就必須單獨先介紹一下 TypedReference 類型。
    我們知道 C# 提供了很多 CLR 內建實值型別的名稱映射,如 Int32 在 C# 中被映射為 int 等等。但實際上有三種 CLR 類型並沒有在 C# 中被映射為語言一級的別名:IntPtr, UIntPtr 和 TypedReference。這三種類型在 IL 一級分別被稱為 native int、native unsigned int 和 typedref。但在 C# 一級,則只能通過 System.TypedReference 類似的方式訪問。而其中就屬這個 TypedReference 最為奇特。
    TypedReference 在 MSDN 中的描述如下:

以下為引用:

    Describes objects that contain both a managed pointer to a location and a runtime representation of the type that may be stored at that 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:

Assembly.Load("mscorlib.dll").GetType("System.TypedReference[]");

    也就是說,實值型別 TypedReference 是專門用於儲存託管指標及其指向內容類型的,查看其實現代碼(bclsystemTypedReference.cs:28)可以驗證這一點:

以下內容為程式碼:

public struct TypedReference
{
private int Value;
private int Type;

// 其他方法
}

    這兒 Value 儲存了對象的指標,Type 儲存了對象的類型控制代碼。
    使用的時候可以通過 __arglist.GetNextArg() 返回,也可以使用 __makeref 關鍵字構造,如:

以下內容為程式碼:

int i = 21;

TypedReference tr = __makeref(i);

    而其中儲存的對象和類型,則可以使用 __refvalue 和 __reftype 關鍵字來擷取。

以下內容為程式碼:

int i = 32;

TypedReference tr1=__makeref(i);

Console.Out.WriteLine("{0}: {1}", __refvalue(tr, int), __reftype(tr1));

    注意這兒的 __refvalue 關鍵字需要指定目標 TypedReference 和轉換的目標類型,如果結構中儲存的類型不能隱式轉換為目標類型,則會拋出轉換異常。相對來說,TypedReference.ToObject 雖然要求強制性 box 目標值,但易用性更強。

    從實現角度來看,__refvalue 和 __reftype 是直接將 TypedReference 的內容取出,因而效率最高。

以下內容為程式碼:

int i=5;
TypedReference tr = __makeref(i);
Console.Out.WriteLine("{0}: {1}", __refvalue(tr, int), __reftype(tr));

    上面這樣一個代碼片斷,將被編譯成:

以下內容為程式碼:

  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}"
  IL_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)

    可以看到 __makeref、__refvalue 和 __reftype 是通過 IL 語言的關鍵字 mkrefany、refanyval 和 refanytype 直接實現的。而這樣的實現是通過直接對堆棧進行操作完成的,無需 TypedReference.ToObject 那樣隱式的 box/unbox 操作,故而效率最高。
    JIT 中對 refanyval 的實現(fjit jit.cpp:8361)如下:

以下內容為程式碼:

FJitResult FJit::compileCEE_REFANYTYPE()
{

    // There should be a refany on the stack
    CHECK_STACK(1);
    // There has to be a typedref on the stack
    // This should be a validity check according 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(offsetof(CORINFO_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;
}

    從以上代碼可以看到,JIT 在處理 refanyval 指令時,並沒有對堆棧內容進行任何操作,而是直接操作堆棧。

    如果希望進一步瞭解相關資訊,可以參考以下介紹:

    Undocumented C# Types and Keywords

    Undocumented TypedReference

    A Sample Chapter from C# Programmers Reference - Value types

ps: 實測了一下發現,MS不公開 vararg 這種調用方式,大概是因為考慮效率方面的原因。與 params 相比,使用 vararg 的調用方式,純粹函數調用的速度要降低一個數量級 :(
    下面這篇文章也討論了這個問題,結論是不到萬不得已情況下盡量少用,呵呵

    Why __arglist is undocumented

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.