首页 新闻 会员 周边

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

0
悬赏园豆:5 [已解决问题] 解决于 2015-09-16 08:51

在调用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设不设还有什么区别呢?求解答,感谢。

ahdung的主页 ahdung | 菜鸟二级 | 园豆:206
提问于:2015-09-11 13:48
< >
分享
最佳答案
1

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

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

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

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

收获园豆:4
hsdtt | 菜鸟二级 |园豆:293 | 2015-09-13 23:02

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

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

Marshal.GetLastWin32Error();

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

ahdung | 园豆:206 (菜鸟二级) | 2015-09-14 08:55

@ahdung: https://msdn.microsoft.com/en-us/library/vstudio/system.runtime.interopservices.marshal.getlastwin32error(v=vs.110).aspx, msdn里的原文,Remarks里第一段最后一句。

hsdtt | 园豆:293 (菜鸟二级) | 2015-09-15 21:53

@hsdtt: 谢谢耐心讲解,在提这个问题之前那句话我也反复琢磨过好一会,但无论是中文还是英文原文都不好理解,结合你的讲解算是理解了~原来说的是这个,再次感谢。分不多,很惭愧。

ahdung | 园豆:206 (菜鸟二级) | 2015-09-16 08:49
其他回答(1)
0

你理解错了嘛!

收获园豆:1
Launcher | 园豆:45045 (高人七级) | 2015-09-11 13:53

那请教正确的理解?

支持(0) 反对(0) ahdung | 园豆:206 (菜鸟二级) | 2015-09-11 13:54

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

支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2015-09-11 14:08

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

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

@ahdung: 你看你给的文档中的这段话:

 

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

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

 

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

支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2015-09-11 14:13

@Launcher: 我就没看懂何谓【以防其它API调用重写】,请举例说明。

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

@ahdung: 我先问你一个问题,是不是所有的 Win32 API 都会使用 SetLastError 来返回错误信息给调用方?

支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2015-09-11 14:17

@Launcher: 应该不是,不确定。刚刚试出true/false的区别了,我想我明白了。不过您还愿意指教的话,愿听高见。

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

@ahdung: 没高见,我不懂。你说你明白了,能请教一下你吗?

支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2015-09-11 14:27

@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就好,谢谢您的关注和指教。

支持(0) 反对(0) ahdung | 园豆:206 (菜鸟二级) | 2015-09-11 15:33

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

}

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

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

支持(0) 反对(0) ahdung | 园豆:206 (菜鸟二级) | 2015-09-11 15:56

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

支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2015-09-11 16:12

@Launcher: 无论如何,谢谢你的关注。

支持(0) 反对(0) ahdung | 园豆:206 (菜鸟二级) | 2015-09-11 16:14
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册