If you are going to call Marshal.GetLastWin32Error, the function whose error you're retrieving had better have SetLastError=true


A customer reported that their p/invoke to a custom DLL was failing, and the error code made no sense.

// C#
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;

class Program
{
  [DllImport("contoso.dll", CallingConvention=CallingConvention.Cdecl)]
  public static extern int Fribble();

  public static void Main()
  {
    Console.WriteLine("About to call Fribble");

    var result = Fribble();
    if (result >= 0) {
      Console.WriteLine("succeeded {0}", result);
    } else {
      Console.WriteLine("failed {0}, last error = {1}",
                        result, Marshal.GetLastWin32Error());
    }
  }
}

// C++

int __cdecl Fribble()
{
 HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE,
                           TEXT("FribbleEvent"));
 if (hEvent == nullptr)
  return -1;
 }

 if (!SetEvent(hEvent)) {
  CloseHandle(hEvent);
  return -2;
 }

 CloseHandle(hEvent);
 return 1;
}

The customer reported that their Fribble function was returning −1, indicating a failure to open the event, but the error code returned by Marshal.Get­Last­Win32­Error is 87, "The parameter is incorrect." But all of the parameters to Open­Event look correct. Why are we getting this strange error code?

My psychic powers tell me that if the customer had taken the time to troubleshoot their problem by writing a C++ program that calls the Fribble function, Get­Last­Error would have returned the more reasonable error 2, meaning that the event does not exist.

That's because Get­Last­Error is working fine. The last error code is 2.

The problem is with the p/invoke declaration.

The documentation for the Marshal.Get­Last­Win32­Error function says as its very first line

Returns the error code returned by the last unmanaged function that was called using platform invoke that has the DllImportAttribute.SetLastError flag set.

(Emphasis mine.)

This reminder about Dll­Import­Attribute.Set­Last­Error is repeated in the Remarks.

You can use this method to obtain error codes only if you apply the System.Runtime.Interop­Services.Dll­Import­Attribute to the method signature and set the Set­Last­Error field to true.

Observe that the Set­Last­Error field was not set in the p/invoke declaration. Therefore, what you are actually getting when you call Marshal.Get­Last­Win32­Error is whatever error was lying around after the previous call to a p/invoke function that did specify Set­Last­Error = true.

Changing the p/invoke to

[DllImport("contoso.dll", SetLastError=true,
           CallingConvention=CallingConvention.Cdecl)]
public static extern int Fribble();

fixed the problem.

Comments (16)
  1. Joshua says:

    Throw new System.ComponentModel.Win32Exception() doesn't seem to care though.

  2. Dan Bugglin says:

    @Joshua It probably does not use Marshal.GetLastWin32Error and P/Invokes GetLastError directly. If you have .NET Reflector or a similar tool you can check this yourself (I don't have it on me at the moment).

  3. Cesar says:

    > Returns the error code returned by the last unmanaged function [...] that has the DllImportAttribute.SetLastError flag set.

    That's a good thing. I've seen a problem (in a different language) where enabling the interpreter's "trace" mode clobbered the last error between a call to a Win32 function ans a call to the GetLastError Win32 function, causing spurious errors in code which branched depending on the error (the error is "already exists"? good, do nothing; all other errors, throw an exception).

    But IMO the real problem is with the "last error" model (aka the "errno" model), which uses a global variable shared by all functions to hold the error state. It's much better to keep the error state local to the function call; for instance, returning an HRESULT or a NTSTATUS, or the model used by Linux system calls where values in the -4095 to -1 range (if the function returns a pointer, these values are in the unaccessible last page of memory) are the negated "errno" value.

  4. ipoverscsi says:

    There is a more vexing problem when using GetLastError() with P/Invoke functions: Error message functions.

    The developers I work with don't like to call FormatErrorMessage() and so require me to implement some error message function in my DLLs. This function is usually of the form "BOOL GetErrorMessage(LPCTSTR buffer, DWORD size)". This function calls FormatErrorMessage() on the error code reported by GetLastError() and places the result into the buffer.

    Now imagine this scenario:

    1) Managed program DllImports Foo(), properly marking it as SetLastError=true

    2) Managed program DllImports GetErrorMessage().

    3) Managed program calls Foo() and it reports failure.

    4) Managed program calls GetErrorMessage() to get the error message.

    5) Managed program prints the returned string "The operation completed successfully."

    Huh?

    It appears that the CLR only preserves the last error on transition from unmanaged to managed and not the other way around. Therefore, when GetErrorMessage() is called, it sees the last error from some random function called by the CLR (usually zero in my experience) and calls FormatErrorMessage() on that error code.

    This happens not just in C# code, but in C++/CLR code, too. Problems with error codes are particularly difficult to diagnose in C++/CLR, especially when it occurs in transitions between managed code to unmanaged code *within the same translation unit*.

    I've had to resort to some very nasty code to properly handle these cases.

  5. meh says:

    Too bad a successful CloseHandle() call will overwrite the last error for the -2 return case (right?). Tho' it's likely the code above is simplified from the real thing for the purpose focusing on the blog's subject.

  6. John Ludlow says:

    @Cesar I'd like all languages to support returning a tuple of the form

     (the data you wanted, whether the thing succeeded)

    So you could do this

     var (data, status) = Fribble()

     if (status.OK)

     {

       // use data

     }

     else

     {

       Console.WriteLine("Error occurred : " + status.Message)

     }

    F# has some nice constructs where you can do stuff a bit like this with discriminated unions.

  7. SimonRev says:

    @ipoverscsi:  How nasty does it need to be?  Just write a native wrapper for GetErrorMessage that takes an extra parameter of the error code you want the error message of.  The native wrapper can then call SetLastError followed by GetErrorMessage.  Slightly annoying yes, but normally when I am P/Invoking I find that I want to be writing native wrappers for a bunch of stuff anyway because it is often easier to write a native function to call a bunch of other native functions than try to P/Invoke a series of native functions.  Or because a native wrapper function to massage data formats is easier than trying heroic P/Invokes on complicated structures.

  8. Yuri says:

    @ipoverscsi

    I can feel your comment about Heroic P/Invoke structures. I maintain a C# Tapi application that makes heavy use of P/Invoke structure and callbacks. When adding new native calls with complex structures I find it easier to just allocate a block of memory in C# fill the bytes pass the structure on and deallocate it after the call.

  9. skSdnW says:

    @meh: CloseHandle is not supposed to change the lasterror on success but it can happen if someone hooked the function: blogs.msdn.com/.../10568872.aspx

  10. Joshua A. Schaeffer says:

    This may not be entirely on-topic but I write a LOT of Win32 interop framework code and I figured I'd share the way I report "last errors". Maybe it helps someone. It's saved me more time than you can imagine.

       [DebuggerDisplay("{this.ToDebugString(),nq}")]

       public enum Win32ErrorCode : int

       {

           ///<summary>The operation completed successfully.</summary>

           ERROR_SUCCESS = 0,

           ///<summary>Incorrect function.</summary>

           ERROR_INVALID_FUNCTION = 1,

           // All the other codes follow here...

       }

    ToDebugString() actually calls a meticulously P/Invoke'd ::FormatMessage() if you can believe it.

       [SuppressUnmanagedCodeSecurity]

       public static partial class Win32Api

       {

           [MethodImpl(MethodImplOptions.AggressiveInlining)]

           public static Win32ErrorCode GetLastError()

           {

               return (Win32ErrorCode)Marshal.GetLastWin32Error();

           }

           // Other P/Invoke imports here.

       }

    Instead of throwing Win32Exception, I throw this:

       [DebuggerDisplay("{Win32ErrorCode.ToString(),nq} : {Win32Utilities.FormatMessage(Win32ErrorCode)}")]

       public class Win32Exception : System.ComponentModel.Win32Exception

       {

           public new Win32ErrorCode Win32ErrorCode

           {

               get

               {

                   return (Win32ErrorCode)base.NativeErrorCode;

               }

           }

           // Other methods here...

       }

    You'll never have to look up integer error codes again, especially in the Watch window.

  11. Medinoc says:

    The first time I made a mixed-mode C++/CLI assembly to call Win32 functions, rather than deal with this, I made unmanaged wrapper functions that all returned HRESULTs.

    Nowadays I'd feel confident enough to use SetLastError=true and GetLastWin32Error(), though.

  12. gdalsnes says:

    Docs says: "The default is false, except in Visual Basic."

    Very confusing. I thought the clr runtime (mscorlib) was language agnostic. In Reflector i see no sign of how SetLastError is magically set to true for VB.

  13. Damien says:

    @arghhhhhhhhhhh - Because VB doesn't have people write the DllImport attribute, normally. They write a Declare statement (msdn.microsoft.com/.../4zey12w5.aspx). That Declare statement is transformed, by the VB compiler, into the DllImport. And when it does that transformation, it sets SetLastError to be true.

  14. David Totzke says:

    @ipoverscsi

    >>The developers I work with don't like to call FormatErrorMessage()

    Is there a technical reason they don't like to do this?  Forcing you to jump through hoops because they "don't like it" seems like behavior that requires a swat on the nose with rolled up newspaper.

    I have been mercifully spared from interaction via p/Invoke and have no C++ experience to speak of.  Just curious.  Thanks.

  15. meh says:

    @skSdnW. Ah right. I suspected I was forgetting something there.

  16. Neil says:

    This leaves me wondering why you would ever not want to set that.

Comments are closed.

Skip to main content