Date: | August 19, 2015 / year-entry #174 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20150819-00/?p=91781 |
Comments: | 12 |
Summary: | Even if you remember to set SetLastError=true in your p/invoke signature, you still have to be careful with Marshal.GetLastWin32Error because there is only one last-error code, and it gets overwritten each time. |
Even if you
remember to
set So let's try this program: using System; using System.Runtime.InteropServices; class Program { [DllImport("user32.dll", SetLastError=true)] public static extern bool OpenIcon(IntPtr hwnd); public static void Main() { // Intentionally pass an invalid parameter. var result = OpenIcon(IntPtr.Zero); Console.WriteLine("result: {0}", result); Console.WriteLine("last error = {0}", Marshal.GetLastWin32Error()); } }
The expectation is that the call to
But when you run the program, it prints this: result: False last error = 0 Zero?
Zero means "No error".
But the function failed.
Where's our error code?
We printed the result immediately after
calling Oh wait, printing the result to the screen involves a function call. That function call might itself do a p/invoke!
We have to call
using System; using System.Runtime.InteropServices; class Program { [DllImport("user32.dll", SetLastError=true)] public static extern bool OpenIcon(IntPtr hwnd); public static void Main() { // Intentionally pass an invalid parameter. var result = OpenIcon(IntPtr.Zero); var lastError = Marshal.GetLastWin32Error(); Console.WriteLine("result: {0}", result); Console.WriteLine("last error = {0}", lstError); } } Okay, now the program reports the error code as 1400: "Invalid window handle." This one was pretty straightforward, because the function call that modified the last-error code was right there in front of us. But there are other ways that code can run which are more subtle.
|
Comments (12)
Comments are closed. |
In the UNIX world, I'm sure you've heard of the famous "Not a typewriter" bug? Historically, sendmail, on various error conditions, would invariably get errno, regardless of whether or not the error actually came from a system call. Thus, trying to send an email to a person who didn't have an account on the server would produce the wonderful message "user unknown: not a typewriter".
The "typewriter" in question is just another word for a teletype, ie an interactive terminal. The error is produced by the isatty() library function attempting to determine whether or not the terminal is interactive.
Raymond, What happens if you have multiple threads p/invoking functions from your managed app? Is there any scope for a context switch to another thread that does a p/invoke, before the original p/invokers call to GetLastWin32Error() is made, but after they've invoked their win32 function?
@Jimmy Queue: GetLastError() returns a per thread value, I believe Marshal.GetLastWin32Error() does the same.
@Jimmy Queue
The last error is a thread-global variable, not a process-global variable.
This is pretty much a reflection of what happens in pure unmanaged code with the same API. So if you're familiar with that you'd likely catch this problem right away.
@Jimmy msdn.microsoft.com/…/ms679360(v=vs.85).aspx I dunno about the managed function but GetLastError is thread safe according to that. And the way the function works with Raymond's example today suggests it is a thin wrapper around a p/invoke call so I would imagine it's fine. The managed function docs don't say for sure though.
Interesting, thanks for the responses guys! Yes, it makes a lot of sense to have GetLastError work on a per thread basis, a simple, yet elegant solution.
@Raymond: Perhaps I'm just being over-cautious, but ISTM a static class initializer should not be running around doing scary things like p/invoke…
(Of course, you can't *rely* on people following best practices, so it's certainly a valid concern)
Which raises the question when and where you think the corresponding native code should be loaded…
@Kevin
Perhaps the static class does not explicitly call p/invoke, but can you guarantee it does not call some other method that does? The .NET Framework calls p/invoke whenever it needs to do Windows specific things.
@Kevin — even a Trace.WriteLine will do it and that's hardly a scary thing.
For some reason this doesn't really seem that crazy to me. I tend to think of GetLastError() as a second return value from the PInvokeCall() — that is, they're essentially one logical operation. It doesn't seem surprising that separating and putting code between them would cause problems. Stuff like the static constructor or field initializers is kind of sneaky, sure, but it still won't happen if you treat the Win32 method call and GetLastError() as a single operation.
Actually, the program will not compile, highlighting the last line: lstError
:)