Quantcast
Channel: Delphi – The Wiert Corner – irregular stream of stuff
Viewing all articles
Browse latest Browse all 1445

Writing a tool that restarts the Google Chat desktop app Window (and hopefully the Google Duo desktop app Window too) and some EnumWindows/EnumChildWindows tricks

$
0
0

Earlier this months I wrote Writing a tool that restarts the Google Chat desktop app Window (and hopefully the Google Duo desktop app Window too) promising I would rewrite the Delphi code into C# and integrate it into PowerShell.

This is the beginning on porting the basics of the Delphi code (which had a flaw!) to C# and contains EnumWindows/EnumChildWindows and error handling tricks and tips.

Actually, there are a lot of links and quotes from those links as having the information in one places makes it way easier to both see the bigger picture and observe the sometimes subtly different descriptions of similar concepts in the various documentation parts.

Before getting to some of the tricks and issues around enumerating Windows using P/Invoke, let’s start with

P/Invoke error handling

The reason for starting with error handling is that those Windows you enumerate usually are from applications you do not control and also run asynchronously as they run in different threads. So windows can appear/disappear during enumeration and introduce unsuspected error codes.

Error handling on Windows is hard, and .NET P/Invoke (which for ages has centered around the extern keyword combined with the DllImportAttribute, see links below) partially confuses this, so let me try to explain a bit before showing relevant .NET, Windows and Linux parts.

Windows has had a thread-local storage error value called LastError that you can retrieve using GetLastError and set through SetLastError. Linux and other platforms have a similar value called errno.

Windows API calls sometimes clear the LastError value upon success (calling SetLastError(0)) thereby overwriting a previous LastError value, so it is important to call GetLastError to preserve the value if needed.

These values can be retrieved in .NET (but not .NET Framework) through Marshal.GetLastSystemError and set through Marshal.SetLastSystemError.

In .NET this LastError/errno got kind of encapsulated by the Marshal.GetLastWin32Error (documented) and Marshal.SetLastWin32Error (undocumented, but present in the reference sources, see links below).

As of .NET 6, there is a public Marshal.GetLastPInvokeError (which is a better name for and now called by Marshal.GetLastWin32Error) and an internal Marshal.SetLastPInvokeError (which is a better name for and now called by Marshal.SetLastWin32Error).

.NET 7 (which by the time you read this should be out) improves P/Invoke big time by moving parts of the plumbing to the compiler. [Wayback/Archive] AArnott (Andrew Arnott) did a great technical explanation of the .NET and proposed situation for CsWin32 in one of the links below.

Back to the “kind of encapsulation” of the Windows LastWin32Error thread local value that .NET does:

  • On each thread, .NET keeps a local copy of the Windows LastWin32Error thread local value.
  • You can obtain this copy using Marshal.GetLastPInvokeError (as of .NET 6.0) or Marshal.GetLastWin32Error (all .NET versions).
  • The copy gets set through either a DllImportAttributecall that has set the SetLastError parameter to true, or a call to the internal Marshal.SetLastPInvokeError  (as of .NET 6.0) or the internal Marshal.SetLastWin32Error (all .NET versions).
  • On .NET (previously called .NET Core), a DllImportAttributecall that has set the SetLastError parameter to true will clear LastError/errno through Marshal.SetLastSystemError (see the example below that Andrew Arnott posted).
  • On .NET Framework (of which the last version is 4.8 and no new versions will be released), a DllImportAttributecall that has set the SetLastError parameter to true will NOT clear LastError/errno. So for calling code that is .NET Framework based, it might make sense to do the clearing yourself.

The Marshal.GetLastPInvokeError method documentation below is important because it shows how to import from both kernel32.dll (on Windows) and libc (on other platforms).

Relevant .NET parts

Below is a mix of MSDN/Microsoft-Learn documentation and other sources around LastError/errno error handling both on OS and .NET level.

First the documentation links:

  • errno.h – Wikipedia

    errno.h is a header file in the standard library of the C programming language. It defines macros for reporting and retrieving error conditions using the symbol errno (short for “error number”).

    A few functions require the caller to preset errno to zero and test it afterwards to see if an error was detected.

    Originally this was a static memory location, but macros are almost always used today to allow for multi-threading, so that each thread will see its own thread-local error number.

  • [Wayback/Archive] extern modifier – C# Reference | Microsoft Learn

    The extern modifier is used to declare a method that is implemented externally. A common use of the extern modifier is with the DllImport attribute when you are using Interop services to call into unmanaged code. In this case, the method must also be declared as static

  • [Wayback/Archive] DllImportAttribute Class (System.Runtime.InteropServices) | Microsoft Learn
  • [Wayback/Archive] Marshal.GetLastPInvokeError Method (System.Runtime.InteropServices) | Microsoft Learn

    Get the last platform invoke error on the current thread.

    The following example defines a p/invoke with [Wayback/Archive] DllImportAttribute.SetLastError set to true and demonstrates using [Wayback/ArchiveGetLastPInvokeError to get the last p/invoke error.

    using System;
    using System.Runtime.InteropServices;
    
    // These functions specify SetLastError=true to propagate the last error from the p/invoke
    // such that it can be retrieved using Marshal.GetLastPInvokeError().
    internal static class Kernel32
    {
        [DllImport(nameof(Kernel32), ExactSpelling = true, SetLastError = true)]
        internal static extern bool SetCurrentDirectoryW([MarshalAs(UnmanagedType.LPWStr)] string path);
    }
    
    internal static class libc
    {
        [DllImport(nameof(libc), SetLastError = true)]
        internal static extern int chdir([MarshalAs(UnmanagedType.LPUTF8Str)] string path);
    }
    
    class Program
    {
        public static void Main(string[] args)
        {
            // Call p/invoke with valid arguments.
            CallPInvoke(AppContext.BaseDirectory);
    
            // Call p/invoke with invalid arguments.
            CallPInvoke(string.Empty);
        }
    
        private static void CallPInvoke(string path)
        {
            if (OperatingSystem.IsWindows())
            {
                Console.WriteLine($"Calling SetCurrentDirectoryW with path '{path}'");
                Kernel32.SetCurrentDirectoryW(path);
            }
            else
            {
                Console.WriteLine($"Calling chdir with path '{path}'");
                libc.chdir(path);
            }
    
            // Get the last p/invoke error and display it.
            int error = Marshal.GetLastPInvokeError();
            Console.WriteLine($"Last p/invoke error: {error}");
        }
    }
    

    The last platform invoke error corresponds to the error set either by the most recent platform invoke that was configured with [Wayback/Archive] DllImportAttribute.SetLastError set to true or by a call to [Wayback/ArchiveSetLastPInvokeError(Int32), whichever happened last.

    This method will only return errors set via the mentioned scenarios. To get the last system error independent of platform invoke usage, use [Wayback/ArchiveGetLastSystemError.

    This method is functionally equivalent to [Wayback/ArchiveGetLastWin32Error. It is named to better reflect the intent of the API and its cross-platform nature. [Wayback/ArchiveGetLastPInvokeError should be preferred over [Wayback/ArchiveGetLastWin32Error.

  • [Wayback/Archive] Marshal.SetLastPInvokeError(Int32) Method (System.Runtime.InteropServices) | Microsoft Learn

    Sets the last platform invoke error on the current thread.

  • [Wayback/Archive] Marshal.GetLastWin32Error Method (System.Runtime.InteropServices) | Microsoft Learn

    Returns

    The last error code set by a call to the Win32 [Wayback/Archive] SetLastError function.

    On Windows systems, [Wayback/ArchiveGetLastWin32Error exposes the Win32 [Wayback/ArchiveGetLastError function from Kernel32.DLL. This method exists because it is not reliable to make a direct platform invoke call to GetLastError to obtain this information. If you want to access this error code, you must call [Wayback/ArchiveGetLastWin32Error instead of writing your own platform invoke definition for GetLastError and calling it. The common language runtime can make internal calls to APIs that overwrite the GetLastError maintained by the operating system.

    You can use this method to obtain error codes only if you apply the [Wayback/Archive] System.Runtime.InteropServices.DllImportAttribute to the method signature and set the [Wayback/Archive] DllImportAttribute.SetLastError field to true. The process for this varies depending upon the source language used: C# and C++ are false by default, but the Declare statement in Visual Basic is true.

    There is a difference in the behavior of the GetLastWin32Error method on .NET Core and .NET Framework when [Wayback/Archive] DllImportAttribute.SetLastError is true. On .NET Framework, the GetLastWin32Error method can retain error information from one P/Invoke call to the next. On .NET Core, error information is cleared before P/Invoke call, and the GetLastWin32Error represents only error information from the last method call.

    On .NET 6 and later versions, this method is functionally equivalent to [Wayback/ArchiveGetLastPInvokeError, which is named to better reflect the intent of the API and its cross-platform nature. [Wayback/ArchiveGetLastPInvokeError should be preferred over [Wayback/ArchiveGetLastWin32Error.

  • [Wayback/Archive] Marshal.GetLastSystemError Method (System.Runtime.InteropServices) | Microsoft Learn

    The system error is based on the current operating system—that is, errno on non-Windows and [Wayback/Archive] GetLastError on Windows.

    This method is provided as a low-level API to allow getting the current system error in a cross-platform manner. It is not tied to platform invoke usage. To get the last platform invoke error, use [Wayback/Archive] GetLastPInvokeError.

  • [Wayback/Archive] Marshal.SetLastSystemError(Int32) Method (System.Runtime.InteropServices) | Microsoft Learn

    Sets the last system error on the current thread.

    The system error is based on the current operating system—that is, errno on Unix and [Wayback/ArchiveSetLastError on Windows.

  • [Wayback/Archive] GetLastError function (errhandlingapi.h) – Win32 apps | Microsoft Learn

    Retrieves the calling thread’s last-error code value. The last-error code is maintained on a per-thread basis. Multiple threads do not overwrite each other’s last-error code.

    The Return Value section of the documentation for each function that sets the last-error code notes the conditions under which the function sets the last-error code. Most functions that set the thread’s last-error code set it when they fail. However, some functions also set the last-error code when they succeed. If the function is not documented to set the last-error code, the value returned by this function is simply the most recent last-error code to have been set; some functions set the last-error code to 0 on success and others do not.

    The error codes returned by a function are not part of the Windows API specification and can vary by operating system or device driver. For this reason, we cannot provide the complete list of error codes that can be returned by each function. There are also many functions whose documentation does not include even a partial list of error codes that can be returned.

    Error codes are 32-bit values (bit 31 is the most significant bit). Bit 29 is reserved for application-defined error codes; no system error code has this bit set. If you are defining an error code for your application, set this bit to one. That indicates that the error code has been defined by an application, and ensures that your error code does not conflict with any error codes defined by the system.

  • [Wayback/Archive] SetLastError function (errhandlingapi.h) – Win32 apps | Microsoft Learn

    Sets the last-error code for the calling thread.

    The last-error code is kept in thread local storage so that multiple threads do not overwrite each other’s values.

    Error codes are 32-bit values (bit 31 is the most significant bit). Bit 29 is reserved for application-defined error codes; no system error code has this bit set. If you are defining an error code for your application, set this bit to indicate that the error code has been defined by your application and to ensure that your error code does not conflict with any system-defined error codes.getlastw
  • [Wayback/Archive] Implement SetLastError ourselves when allowMarshaling is false · Issue #600 · microsoft/CsWin32 (comment by Andrew Arnott):

    DllImportAttribute.SetLastError indicates that the native method may call SetLastError. Some of these methods only call this in error conditions, and calling this method overwrites any previous value. But this value is in TLS (thread local storage) so each thread has its own copy. If you want to read the value set by a prior Win32 call, you must read it immediately after the call to ensure it wasn’t overwritten by later code.

    When the CLR emits the interop code for a DllImport method, if the SetLastError property is set to true, the CLR calls SetLastError(0) first, then invokes the method, and then calls GetLastError and stores the result in a CLR-specific TLS memory location so that even if the CLR internally makes other native calls, the user call to Marshal.GetLastWin32Error will always return the value from the last user-p/invoke on that thread.

    I can’t find it in the documentation now, but I believe when the CLR does do marshaling, it isn’t exactly guaranteed that after an interop call completes that the CLR hasn’t injected other interop calls for its own purposes, in which case the ‘last error’ may be overwritten by the time the first interop call returns. This means that if CsWin32 generates a method that tries to imitate the CLR’s marshaling behavior, there isn’t a guarantee that our own call to GetLastError will actually return the value from our own native call — it might be from a CLR engine’s call into Win32. So we have to be careful.

    Now, by targeting net7.0 and using the [LibraryImport(SetLastError=true)] attribute, we can see what kind of code it generates:

    internal static partial int GetTickCount()
    {
        int __lastError;
        int __retVal;
        {
            System.Runtime.InteropServices.Marshal.SetLastSystemError(0);
            __retVal = __PInvoke();
            __lastError = System.Runtime.InteropServices.Marshal.GetLastSystemError();
        }
    
        System.Runtime.InteropServices.Marshal.SetLastPInvokeError(__lastError);
        return __retVal;
        // Local P/Invoke
        [System.Runtime.InteropServices.DllImportAttribute("Kernel32", EntryPoint = "GetTickCount", ExactSpelling = true)]
        static extern unsafe int __PInvoke();
    }

    Notice how the [DllImport] attribute in its generated code is on a local function, and the attribute lacks the SetLastError=true argument. So the extern API is no longer a method that others can call. The method is now a wrapper of the extern method. And that wrapper is equivalent to what the CLR would have produced if it were marshaling the call. This is what CsWin32 must do.

    But here’s the rub: while net7.0 evidently guarantees the correctness of this code, I don’t think the CLR (e.g. net472) makes the same guarantee. Remember how I said the call into the extern method may inadvertently trigger the CLR to make other native calls before it returns? But I guess with Core CLR it’s safe. Well, that and perhaps because LibraryImport always generates enough code to ensure that marshaling never occurs.

    So… I think CsWin32 should emit this same kind of wrapper, but only when these conditions are true:

    1. The metadata indicates this is a SetLastError = true method.
    2. allowMarshaling has been set to false
    3. we’re targeting net7.0 or later.

Then some source code links:

  • [Wayback/Archive] marshal.cs: Reference Source .NET Framework 4.8 System.Runtime.InteropServices.Marshall
            //====================================================================
            // GetLastWin32Error
            //====================================================================
            [System.Security.SecurityCritical]  // auto-generated_required
            [ResourceExposure(ResourceScope.None)]
            [MethodImplAttribute(MethodImplOptions.InternalCall)]
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
            public static extern int GetLastWin32Error();
        
     
            //====================================================================
            // SetLastWin32Error
            //====================================================================
            [ResourceExposure(ResourceScope.None)]
            [MethodImplAttribute(MethodImplOptions.InternalCall)]
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
            internal static extern void SetLastWin32Error(int error);
        
     
            //====================================================================
            // GetHRForLastWin32Error
            //====================================================================
            [System.Security.SecurityCritical]  // auto-generated_required
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
            public static int GetHRForLastWin32Error()
            {
                int dwLastError = GetLastWin32Error();
                if ((dwLastError & 0x80000000) == 0x80000000)
                    return dwLastError;
                else
                    return (dwLastError & 0x0000FFFF) | unchecked((int)0x80070000);
            }
  • [Wayback/Archive] UnsafeNativeMethodsOther.cs: Reference Source .NET Framework 4.8 MS.Win32.UnsafeNativeMethods
            /// <SecurityNote>
            ///     Critical: This code causes elevation to unmanaged code
            /// </SecurityNote>
            [SecurityCritical,SuppressUnmanagedCodeSecurity]
            [DllImport(ExternDll.Kernel32, ExactSpelling = true, CharSet = CharSet.Auto)]
            internal static extern void SetLastError(int dwErrorCode);
  • [Wayback/Archive] ErrorCodes.cs: MS.Internal.Interop.Win32Error
            /// <summary>Performs the equivalent of Win32's GetLastError()</summary>
            /// <returns>A Win32Error instance with the result of the native GetLastError</returns>
            /// <SecurityNote>
            ///     Critical - Calls critical Marshal method GetLastWin32Error.
            /// </SecurityNote>
            [SecurityCritical]
            public static Win32Error GetLastError()
            {
                return new Win32Error(Marshal.GetLastWin32Error());
            }
    

    Where GetLastError is not the Win32 API, but a call to Marshal.GetLastWin32Error.

  • [Wayback/Archive] runtime/Marshal.CoreCLR.cs at main · dotnet/runtime
    ...
    namespace System.Runtime.InteropServices
    {
    ...
        public static partial class Marshal
        {
    ...
            /// <summary>
            /// Get the last platform invoke error on the current thread
            /// </summary>
            /// <returns>The last platform invoke error</returns>
            /// <remarks>
            /// The last platform invoke error corresponds to the error set by either the most recent platform
            /// invoke that was configured to set the last error or a call to <see cref="SetLastPInvokeError(int)" />.
            /// </remarks>
            [MethodImpl(MethodImplOptions.InternalCall)]
            public static extern int GetLastPInvokeError();
    
            /// <summary>
            /// Set the last platform invoke error on the current thread
            /// </summary>
            /// <param name="error">Error to set</param>
            [MethodImpl(MethodImplOptions.InternalCall)]
            public static extern void SetLastPInvokeError(int error);
    ...
  • [Wayback/Archive] runtime/Marshal.Unix.cs at main · dotnet/runtime
    namespace System.Runtime.InteropServices
    {
        public static partial class Marshal
        {
            /// <summary>
            /// Gets the last system error on the current thread.
            /// </summary>
            /// <returns>The last system error.</returns>
            /// <remarks>
            /// The error is that for the current operating system (for example, errno on Unix, GetLastError on Windows).
            /// </remarks>
            public static int GetLastSystemError()
            {
                return Interop.Sys.GetErrNo();
            }
    
            /// <summary>
            /// Sets the last system error on the current thread.
            /// </summary>
            /// <param name="error">The error to set.</param>
            /// <remarks>
            /// The error is that for the current operating system (for example, errno on Unix, SetLastError on Windows).
            /// </remarks>
            public static void SetLastSystemError(int error)
            {
                Interop.Sys.SetErrNo(error);
            }
    
            /// <summary>
            /// Gets the system error message for the supplied error code.
            /// </summary>
            /// <param name="error">The error code.</param>
            /// <returns>The error message associated with <paramref name="error"/>.</returns>
            public static string GetPInvokeErrorMessage(int error)
            {
                return Interop.Sys.StrError(error);
            }
        }
    }
  • [Wayback/Archive] runtime/Marshal.cs at main · dotnet/runtime
    ...
    namespace System.Runtime.InteropServices
    {
    ...
        public static partial class Marshal
        {
    ...
            public static int GetHRForLastWin32Error()
            {
                int dwLastError = GetLastPInvokeError();
                if ((dwLastError & 0x80000000) == 0x80000000)
                {
                    return dwLastError;
                }
    ...
            public static int GetLastWin32Error()
            {
                return GetLastPInvokeError();
            }
    
            /// <summary>
            /// Gets the system error message for the last PInvoke error code.
            /// </summary>
            /// <returns>The error message associated with the last PInvoke error code.</returns>
            public static string GetLastPInvokeErrorMessage()
            {
                return GetPInvokeErrorMessage(GetLastPInvokeError());
            }
        }
    }
  • [Wayback/Archive] runtime/Interop.GetLastError.cs at main · dotnet/runtime
    // Licensed to the .NET Foundation under one or more agreements.
    // The .NET Foundation licenses this file to you under the MIT license.
    
    using System.Runtime.InteropServices;
    
    internal static partial class Interop
    {
        internal static partial class Kernel32
        {
            [LibraryImport(Libraries.Kernel32)]
            [SuppressGCTransition]
            internal static partial int GetLastError();
        }
    }
  • [Wayback/Archive] runtime/Interop.SetLastError.cs at main · dotnet/runtime
    // Licensed to the .NET Foundation under one or more agreements.
    // The .NET Foundation licenses this file to you under the MIT license.
    
    using System.Runtime.InteropServices;
    
    internal partial class Interop
    {
        internal static partial class Kernel32
        {
            [LibraryImport(Libraries.Kernel32)]
            [SuppressGCTransition]
            internal static partial void SetLastError(int errorCode);
        }
    }
  • [Wayback/Archive] runtime/Interop.ErrNo.cs at main · dotnet/runtime
    // Licensed to the .NET Foundation under one or more agreements.
    // The .NET Foundation licenses this file to you under the MIT license.
    
    using System;
    using System.Runtime.InteropServices;
    
    internal static partial class Interop
    {
        internal unsafe partial class Sys
        {
            [LibraryImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetErrNo")]
            [SuppressGCTransition]
            internal static partial int GetErrNo();
    
            [LibraryImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_SetErrNo")]
            [SuppressGCTransition]
            internal static partial void SetErrNo(int errorCode);
        }
    }
  • zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
  • zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

There were many discussions on the new code. I think I filtered down the most important ones here:

  • [Wayback/Archive] Expose setting/getting of system error code · Issue #46843 · dotnet/runtime is a long interesting read discussing the pros and cons of various approaches and ultimate lead into
    • [Wayback/Archive] Add Get/SetLastPInvokeError and Get/SetLastSystemError APIs by elinor-fung · Pull Request #51505 · dotnet/runtime has some good detail discussion on the pull request to get it really right and changes the flies in this commit:
      • [Wayback/Archive] Add Get/SetLastPInvokeError and Get/SetLastSystemError APIs (#51505) · dotnet/runtime@7ee226d
        1. src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs (System.Runtime.InteropServices.Marshal)
          ...
           namespace System.Runtime.InteropServices
           {
               public static partial class Marshal
          ...
                   [MethodImpl(MethodImplOptions.InternalCall)]
          -        public static extern int GetLastWin32Error();
          +        public static extern int GetLastPInvokeError();
          ...
                   [MethodImpl(MethodImplOptions.InternalCall)]
          -        internal static extern void SetLastWin32Error(int error);
          +        public static extern void SetLastPInvokeError(int error);
          ...
        2. src/libraries/Common/src/Interop/Unix/System.Native/Interop.ErrNo.cs (System.Runtime.InteropServices.Interop.Sys)
          +// Licensed to the .NET Foundation under one or more agreements.
          +// The .NET Foundation licenses this file to you under the MIT license.
          +
          +using System;
          +using System.Runtime.InteropServices;
          +
          +internal static partial class Interop
          +{
          +    internal unsafe partial class Sys
          +    {
          +        [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetErrNo")]
          +        [SuppressGCTransition]
          +        internal static extern int GetErrNo();
          +
          +        [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_SetErrNo")]
          +        [SuppressGCTransition]
          +        internal static extern void SetErrNo(int errorCode);
          +    }
          +}
        3. src/libraries/Common/src/Interop/Windows/Kernel32/Interop.SetLastError.cs (System.Runtime.InteropServices.Marshal.Interop.Kernel32)
          +// Licensed to the .NET Foundation under one or more agreements.
          +// The .NET Foundation licenses this file to you under the MIT license.
          +
          +using System.Runtime.InteropServices;
          +
          +internal partial class Interop
          +{
          +    internal static partial class Kernel32
          +    {
          +        [DllImport(Libraries.Kernel32)]
          +        [SuppressGCTransition]
          +        internal static extern void SetLastError(int errorCode);
          +    }
          +}
        4. src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.Unix.cs (System.Runtime.InteropServices.Marshal)
          ...
           namespace System.Runtime.InteropServices
           {
               public static partial class Marshal
          +        public static int GetLastSystemError()
          +        {
          +            return Interop.Sys.GetErrNo();
          +        }
          ...
          +        public static void SetLastSystemError(int error)
          +        {
          +            Interop.Sys.SetErrNo(error);
          +        }
        5. src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.Windows.cs (System.Runtime.InteropServices.Marshal)
          ...
           namespace System.Runtime.InteropServices
           {
               public static partial class Marshal
          ...
          +        public static int GetLastSystemError()
          +        {
          +            return Interop.Kernel32.GetLastError();
          +        }
          ...
          +        public static void SetLastSystemError(int error)
          +        {
          +            Interop.Kernel32.SetLastError(error);
          +        }
          ...
        6. src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs (System.Runtime.InteropServices.Marshal)
          ...
           namespace System.Runtime.InteropServices
           {
               public static partial class Marshal
          ...
          +        public static int GetLastWin32Error()
          +        {
          +            return GetLastPInvokeError();
          +        }
          ...
        7. src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.Mono.cs (System.Runtime.InteropServices.Marshal)
          ...
           namespace System.Runtime.InteropServices
           {
               public static partial class Marshal
          ...
                   [MethodImplAttribute(MethodImplOptions.InternalCall)]
          -        public static extern int GetLastWin32Error();
          +        public static extern int GetLastPInvokeError();
          ...
          +        [MethodImplAttribute(MethodImplOptions.InternalCall)]
          +        public static extern void SetLastPInvokeError(int error);
          ...

        Plus a bunch of project plumbing, some C/C++ code, and various tests.

        1. 1
          q
  • a

Finally some examples when errors are returned and how to try to prevent those or handle them:

  1. Always show what DllImport attribute your P/Invoke import uses, otherwise a question like [Wayback/Archive] c# – EnumChildWindows fails for the child dialog of the parent window – Stack Overflow asked by [Wayback/Archive] Vladislav goes largely unanswered:

    In my program, i’am trying to find the element on the window, which is created by the other parent window. I want to find the specific button and click on it. For example i took the “settings” window of my browser. The following code:

    IntPtr hwNd = FindWindow("MyBrowserClass", "Settings"); 
    
     List<IntPtr> result = new List<IntPtr>();
                GCHandle listHandle = GCHandle.Alloc(result);
                try
                {
                    Win32Callback childProc = new Win32Callback(EnumWindow);
                    EnumChildWindows(hwNd, childProc, GCHandle.ToIntPtr(listHandle));
                    uint errorCode = GetLastError();
                }
                finally
                {
                    if (listHandle.IsAllocated)
                        listHandle.Free();
                }
                return result;
    

    I’m getting the GetLastError() = 1008 ERROR_NO_TOKEN. I looked in spy++, the window styles are:

    WS_CAPTION | WS_VISIBLE | WS_OVERLAPPED | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS
    

    And extended styles are:

    WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHT_SCROLLBAR | WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE
    

    How do i get the controls of the child window of some other window?

    Here neither the Win32Callback nor EnumChildWindows definition were shown, so it is unclear if SetLastError(0) was called, opening up pending SetLastError results to interfere.

  2. Even EnumWindows (which in short enumerates the top level ones) can fail, so better check that result and clear it before calling: [Wayback/Archive] winapi – Why does EnumWindows fail with ERROR_ALREADY_EXISTS? – Stack Overflow (thanks [Wayback/Archive] CoreTech and [Wayback/Archive] Hans Passant)
    My program’s call to EnumWindows() returns FALSE and GetLastError() occasionally returns ERROR_ALREADY_EXISTS (#183, “Cannot create a file when that file already exists”). What does that error mean in that context?
    Here is a code snippet:
    static BOOL CALLBACK CollectTopLevelWindowsEnum(HWND hWnd, LPARAM lParam)
    {
        // This one is good
        s_windows.push_back(hWnd);
    
        return TRUE;
    }
    
    if (!EnumWindows(CollectTopLevelWindowsEnum, NULL)) {
        DWORD lastError = GetLastError();
        if (lastError != ERROR_SUCCESS) {
            TRACE("EnumWindows failed: %d.\n", lastError);
        }
    }
    

    Comments:

    I’ll try calling SetLastError(0) before EnumWindows and see if that helps, thanks.

    Thanks @Hans – The situation is very rare, but once it happens it continues to happen every time. I have seen EnumWindows fail several times per minute for over an hour (until the PC was rebooted). Not the usual signature of a race condition…

    Next time it happens, start killing of processes one-by-one with Taskmgr. With some luck you’ll find the evil-doer.

  3. Sometimes the documentation is wrong and despite documented as calling SetLastError an API actually does not call SetLastError, and and the previous SetLastError value then is still present:[Wayback/Archive] winapi – FindWindow error 183 – Stack Overflow (thanks [Wayback/Archive] User windev and [Wayback/Archive] Werner Henze):

    MSDN says, that [Wayback/Archive] FindWindowand [Wayback/ArchiveFindWindowEx return NULL if the function fails and that you should check GetLastError. It seems that this documentation is wrong. Take this code fragment:

    SetLastError(12345);
    HWND h = FindWindow(L"class_name_that_does_not_exist", nullptr);
    cout << h << ' ' << GetLastError() << endl;
    

    It will output

    00000000 12345

    So as you can see FindWindow fails to set the last error. In your case this means that the ERROR_ALREADY_EXISTS was the last error set before FindWindow was called.

    It means: when in doubt, always try and write some test code to verify what is really going .

On various types of Windows

Filtering Window Types can be important. One trick is to get only Window having a caption as per [Wayback/Archive] c# – How to use EnumWindows to get only actual application windows? – Stack Overflow (thanks [Wayback/Archive] BrunoLM and [Wayback/Archive] Cory Nelson):

Q

I want to get all Windows that I could take a screenshot, application windows. I am trying to use EnumWindows.

public delegate bool CallBackPtr(IntPtr hwnd, int lParam);

[DllImport("user32.dll")]
private static extern int EnumWindows(CallBackPtr callPtr, int lPar);

public static List<IntPtr> EnumWindows()
{
    var result = new List<IntPtr>();

    EnumWindows(new User32.CallBackPtr((hwnd, lParam) =>
    {
        result.Add(hwnd);
        return true;
    }), 0);

    return result;
}

However this is returning more Windows than I expect, such as:

tooltip

tooltip

blackthing

I want to grab only Windows like Visual Studio, Skype, Explorer, Chrome…

Should I be using another method? Or how do I check if it is a window I want to grab?

A

Perhaps checking the window’s style for a title bar will do what you want:
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

static bool IsAppWindow(IntPtr hWnd)
{
    int style = GetWindowLong(hWnd, -16); // GWL_STYLE

    // check for WS_VISIBLE and WS_CAPTION flags
    // (that the window is visible and has a title bar)
    return (style & 0x10C00000) == 0x10C00000;
}
But this won’t work for some of the fancier apps which are fully custom drawn.

 

Careful though, as the last sentence is important: it will skip most of not all Metro apps (now called UWP) as they do custom drawing trying to be cross-platform.

More to come

A future blog post will explain more about handling handles with HandleRef.

--jeroen

 

 


Viewing all articles
Browse latest Browse all 1445

Trending Articles