【C#】请教DllImportAttribute.SetLastError属性的作用?

C#-请教DllImportAttribute.SetLastError属性的作用?

在调用win32 API时,会用到DllImport特性类,该类中有一个属性是SetLastError,文档在此:

https://msdn.microsoft.com/zh-cn/library/system.runtime.interopservices.dllimportattribute.setlasterror(v=vs.80).aspx

我对该属性大概的理解是,如果将它设为true,那么会在api函数执行完成后调用SetLastError这个API,将api函数执行期间发生的错误代码set到调用者的线程中,调用者可以通过调用Marshal.GetLastWin32Error()来获知api函数返回的错误。

但问题是我尝试把SetLastError设为false,通过传入错误的参数故意令api函数出错,随后我仍然通过Marshal.GetLastWin32Error()得到了错误码,设为true也一样,那这样一来,SetLastError设不设还有什么区别呢?求解答,感谢。

win32的错误代码是使用线程本地存储的,类似于linux下的errno,每个线程只有一个。

也就是说你使用GetLastError得到的是前面刚刚出现的错误代码,如果后面再有错误,就会覆盖掉这个值。

你设置这个属性为true,CLR就会保存下这个值,后面可以使用 Marshal.GetLastWin32Error获得使用PInvoke调用的UnManaged函数的最后一个返回值。

这个问题的关键就是通过PInvoke调用这几个字,因为CLR本身也可能调用win32函数,有可能覆盖掉线程的错误代码。

 

你这么说我好像明白了,“CLR也会调API”作为理由很说得通,但不知道这个说法有没有相对权威的出处?有的话还望告知,感谢。另外我还是不明白:

A();//代表一个api函数

Marshal.GetLastWin32Error();

按你的说法,A执行完后,Getxx执行前,在这期间有可能CLR会调其它API,从而覆盖A刚刚返回的errorcode,导致Getxx拿不到A的返回码,是这样吗?那我就奇怪,CLR的工作也有那么飘忽不可捉摸吗,在这两句之间它可能会干些什么?

us/library/vstudio/system.runtime.interopservices.marshal.getlastwin32error(v=vs.110).aspx, msdn里的原文,Remarks里第一段最后一句。

@ahdung: 它只是告诉 runtime marshaler 是不是需要在在调用完目标函数后立即调用 GetLastError 并缓存值。

@Launcher: 请进一步明示区别,缓不缓存对我后面Marshal.GetLastWin32Error有什么影响?或者说哪种极端情况下,必须设为true,否则后果严重?


true 指示被调用方将调用 SetLastError;否则为 false。默认值为 false,但在 Visual Basic 中除外。

运行时封送拆收器将调用 GetLastError 并缓存返回的值,以防其被其他 API 调用重写。可通过调用 GetLastWin32Error 来检索错误代码。

 

你想要的影响就是:以防其被其他 API 调用重写。—— 这就是你认为的极端情况和严重后果。

 

@Launcher: 无妨,交流才是重点,我的测试代码是这样:

SetLastError(5);//调用SetLastError人工set一个错误码
GetPrivateProfileString("aa", "k", "", new StringBuilder(), 20, @"c:\a.ini");//如果a.ini不存在,该API会输出错误码2,成功则输出0
Console.WriteLine(Marshal.GetLastWin32Error());//输出错误码

当GetPrivateProfileString的SetLastError为true时,Marshal.GetLastWin32Error()始终显示GetPrivateProfileString的返回码,即0或2;为false时,无论成功失败,Marshal.GetLastWin32Error()都得到5,即第一句人工set的错误码。这样看来,SetLastError为true,则api无论成功失败,稍后的GetLastWin32Error都能得到它的返回码;反之则得不到。这与我开始的理解是吻合的,但为什么会有问题中说的现象,是因为一开始我的测试代码是:

SetLastError(5);
Console.WriteLine(GetPrivateProfileString("aa", "k", "", new StringBuilder(), 20, @"c:\a.ini"));
Console.WriteLine(Marshal.GetLastWin32Error());

与上面的区别是,GetPrivateProfileString这个API被Console.WriteLine了一下,这样无论GetPrivateProfileString的SetLastError如何设置,均能得到它的返回码。至于为什么,不得而知。

还请大师指点。而且我好奇如果我没有说我明白了,你接下来会怎样点化我?

支持(0)反对(0)回复 | ahdung | 园豆:206 (菜鸟二级) | 2015-09-11 14:46

@ahdung: 在本机代码中:

GetPrivateProfileString("aa", "k", "", new StringBuilder(), 20, @"c:\a.ini");

DWORD lastError = GetLastError();

这里的 lastError 总是正确的,它总是 GetPrivateProfileString 设置的值。因为在这两条语句之间没有更多的 API 函数调用,根本就不会出现当前线程的 last error 被修改的情况。

而如果在托管代码中这样写:

GetPrivateProfileString("aa", "k", "", new StringBuilder(), 20, @"c:\a.ini");

int lastError = Marshal.GetLastWin32Error();

当你将 DllImportAttribute.SetLastError 为 false 时,lastError 并不总会是 GetPrivateProfileString 设置的值,因为在语句 GetPrivateProfileString("aa", "k", "", new StringBuilder(), 20, @"c:\a.ini"); 和 int lastError = Marshal.GetLastWin32Error(); 之间可能执行了其它的 API 函数,而这些 API 函数恰好又修改了当前线程的 Last Error 值,最常见的就是 GC 回收时。

支持(0)反对(0)回复 | Launcher | 园豆:45045 (高人七级) | 2015-09-11 15:24

@Launcher: 能明白您说的,但隐约感觉这个事情没那么简单,比如:

SetLastError(5);

GetPrivateProfileString(xx);//当该API的SetLastError为false

Marshal.GetLastWin32Error();//输出5

Marshal.GetLastWin32Error();//输出GetPrivateProfileString的返回码

而当API的SetLastError为true,稍后的若干次Marshal.GetLastWin32Error()都只会得到它的返回码,总之有点古怪。GetLastWin32Error的文档中MS说是基于GetLastError api,但同时又强烈警告不要直接使用api,推荐使用GetLastWin32Error,这当中必有蹊跷。

anyway,不纠结了,以后有机缘再探究,现在只要知道如果想获得可靠的返回码,就设置setlasterror=true就好,谢谢您的关注和指教。

@ahdung: 当然有蹊跷,Marshal.GetLastWin32Error  的实现跟 .Net 的版本有关,总之在 .Net 中,应该始终使用 Marshal.GetLastWin32Error ,而且,如果被调用的 API 使用了 SetLastError 来设置错误码,那么你就应该总是将 SetLastError 设置为 ture。

 

你把你的代码用 while(ture)给包起来,让它每隔 5 秒执行一次看看。

while(true) {

   SetLastError(5);
   Console.WriteLine(GetPrivateProfileString("aa", "k", "", new StringBuilder(), 20, @"c:\a.ini"));
   Console.WriteLine(Marshal.GetLastWin32Error());

   sleep(5000);

}

@Launcher: 你老说这些不痛不痒的,你始终没有说出点干货,true/false在什么情况下有重大区别?啥原因?你说的在调用api和获取errcode之间可能执行其他API这个说法是站不住脚的,errcode应该是不跨线程的,不同的线程有不同的lasterrorcode,一个线程里,调api后立马getcode怎么可能执行有其它动作,退一万步,就算有其它动作,那跟SetLastError为true/false又有什么关系?为true它该被覆盖的还得覆盖啊。总之你对这个问题的捣鼓未必比我多,认识也未必比我深~当然我也没多深,反正你没说出个所以然来就是了,不知道你能不能接受这种直率。

@ahdung: 算了不说了,一说就又要展开,自己给自己找麻烦。打字还是挺忙的,还要解释很多东西,如果是当面讲的话就比较容易解释,这个要靠写字的实在是太麻烦。

你可能感兴趣的:(C#)