If you are going to call Marshal.GetLastWin32Error, the function whose error you’re retrieving had better be the one called most recently

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 Set­Last­Error=true in your p/invoke signature, you still have to be careful with Marshal.Get­Last­Win32­Error because there is only one last-error code, and it gets overwritten each time.

Even if you remember to set Set­Last­Error=true in your p/invoke signature, you still have to be careful with Marshal.Get­Last­Win32­Error because there is only one last-error code, and it gets overwritten each time.

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 Open­Icon will fail, and the error code will be some form of invalid parameter.

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 Open­Icon. We didn't call any other p/invoke functions. The last-error code should still be there.

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 Marshal.Get­Last­Win32­Error immediately after calling Open­Icon. Nothing else can sneak in between.

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.

  • If you retrieve a property, the property retrieval may involve a p/invoke.

  • If you access a class that has a static constructor, the static constructor will secretly run if this is the first time the class is used.

Comments (12)
  1. Muzer says:

    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.

  2. Jimmy Queue says:

    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?

  3. boogaloo says:

    @Jimmy Queue: GetLastError() returns a per thread value, I believe Marshal.GetLastWin32Error() does the same.

  4. Damien says:

    @Jimmy Queue

    The last error is a thread-global variable, not a process-global variable.

  5. Dan Bugglin says:

    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.

    [True, but the managed world has a few hidden gotchas. What looks like a C/C++ field access is actually a method call, and merely mentioning a static class causes code to run! -Raymond]
  6. Jimmy Queue says:

    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.

  7. Kevin says:

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

  8. @Kevin says:

    Which raises the question when and where you think the corresponding native code should be loaded…

  9. Ken in NH says:

    @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.

  10. Mark Sowul says:

    @Kevin — even a Trace.WriteLine will do it and that's hardly a scary thing.

  11. Nico says:

    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.

  12. Cube 8 says:

    Actually, the program will not compile, highlighting the last line: lstError

    :)

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index