這樣一段代碼:
.class public auto ansi Test extends [mscorlib]System.Object
{
.method public static pinvokeimpl("msvcrt.dll" cdecl)int32 sscanf(string, string, int32&) cil managed { }
.field public static int32 val
.method public static void Main() cil managed
{
.entrypoint
.locals init (int32 n)
ldstr "Enter a number"
call void [mscorlib]System.Console::WriteLine(string)
call string [mscorlib]System.Console::ReadLine()
ldstr "%d"
ldsflda int32 Test::val
call int32 sscanf(string, string, int32&)
stloc.0
ldloc.0
brfalse.s Error
// elide for clarity
}
}
基本功能是從控制台取得用戶輸入的數字,調用 C 的運行庫函數 sscanf 將字符串形式的數字轉換為數值,並根據 sscanf 的返回值判斷字符串是否合法的數字字符串。
奇怪的是,sscanf 總是返回 0 !
查 MSDN,關於返回值,說明如下:
intsscanf(const char* buffer, const char* format [, argument]…);
[Thefunction] returns the number of fields successfully converted and assigned; thereturn value does not include fields that were read but not assigned. A returnvalue of 0 indicates that no fields were assigned.
該函數返回成功轉換的字段數,返回值不包含已讀取但沒有賦值的字段。返回 0 表示沒有字段被賦值。也就是說,轉換不成功!
難道這段 IL 代碼有問題?為了驗證這個疑問,又寫了一小段等價的 C# 代碼:
usingSystem;
usingSystem.Runtime.InteropServices;
publicstatic class Test
{
[DllImport("msvcrt.dll", CharSet=CharSet.Ansi,CallingConvention=CallingConvention.Cdecl)]
public static extern int sscanf(string, string, out int nval);
public static void Main()
{
int n = 0;
int val = 0;
Console.WriteLine("Enter a number");
string line = Console.ReadLine();
n = sscanf(line, "%d", out val);
Console.WriteLine("n = " + n + ", val = " +val);
}
}
編譯運行,n = 1,正確!奇怪!
將生成的 exe 程序反編譯為 IL 代碼,其中 sscanf 函數的聲明如下:
.methodpublic hidebysig static pinvokeimpl("msvcrt.dll" ansi cdecl)
int32 sscanf(string sval,
string fmt,
[out] int32& nval) cilmanaged preservesig
{ }
將這個聲明與上面的 sscanf 的聲明對比一下,發現這里多了一個 preservesig 特性。修改上述 IL 代碼的 sscanf 聲明,增加 preservesig,編譯運行,正確!
MSDN 對 DllImportAttribute.PreserveSig 字段說明如下:
Indicateswhether unmanaged methods that have HRESULT or retval return values aredirectly translated or whether HRESULT or rettval return values areautomatically converted to exceptions.
Set thePreserveSig field to true to directly translate unmanaged signatures withHRESULT or retval values; set it to false to automatically convert HRESULT orretval values to exceptions. By default, the PreserveSig field is true.
也就是說,對於有返回值(不管是HRESULT 還是其他返回值)的非托管代碼,CLR對返回值的處理有兩種方式:或者直接返回給調用者,或者將 HRESULT 或其他返回值轉換為異常拋出(如果返回值不是 S_OK 的話)。到底取何種處理方式,取決於 PreserveSig 的設置。如果 PreserveSig 設置為 true,則直接返回給調用者(這種方式保留了非托管方法的簽名,這也是 Preserve Signature 的本義)。如果 PreserveSig 設置為 false,則將返回值(HRESULT 或其他返回值)轉換為異常拋出(如果返回值不等於 S_OK),此時,如果沒有異常拋出,則調用者也得不到返回值(因為沒有返回)!
這就是為什么上述的 IL 代碼中的 sscanf 函數總是返回 0 的原因。C# 中的 DllImportAttribute.PreserveSig 默認為 true,而在 IL 代碼中必須顯式指定 preservesig。