convert C++ code to C#: SendMessageTimeout()

2019-05-23 07:48发布

问题:

First of all docu for SendMessageTimeout:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms644952%28v=vs.85%29.aspx

i have this C++ code and i want to convert it to C#:

LRESULT success = SendMessageTimeout(
    HWND_BROADCAST,
    WM_SETTINGCHANGE,
    0,
    (LPARAM) "Environment",
    SMTO_ABORTIFHUNG,
    5000,
    NULL
);

What i did in C#:

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessageTimeout(
        IntPtr hWnd,
        uint Msg,
        UIntPtr wParam,
        IntPtr lParam,
        uint fuFlags,
        uint uTimeout,
        out UIntPtr lpdwResult
    );

    SendMessageTimeout(
        (IntPtr)0xFFFFFFFF,    //HWND_BROADCAST
        0x001A,                //WM_SETTINGCHANGE
        (UIntPtr)0,
        (IntPtr)"Environment", // ERROR_1: can't convert string to IntPtr
        0x0002,                // SMTO_ABORTIFHUNG
        5000,
        out UIntPtr.Zero       // ERROR_2: a static readonly field can not be passed ref or out
    );

回答1:

For your issues.

  1. HWND_BROADCAST is 0xFFFF not 0xFFFFFFFF
  2. You will have to allocate memory for the LPARAM value manually using Marshal.StringToHGlobalUni and then free it after the call using Marshal.FreeHGlobal. You must free this memory or it will leak. Marshal'd memory is not garbage collected.
  3. For lpdwResult just create an IntPtr variable and pass that in. You can just ignore its value.

The code should be something like this:

IntPtr result = IntPtr.Zero;
IntPtr setting = Marshal.StringToHGlobalUni("Environment");

SendMessageTimeout(
    (IntPtr)0xFFFF,        //HWND_BROADCAST
    0x001A,                //WM_SETTINGCHANGE
    (UIntPtr)0,
    (IntPtr)setting,
    0x0002,                // SMTO_ABORTIFHUNG
    5000,
    out result
);

Marshal.FreeHGlobal(setting);

In general you need to be careful when freeing memory that you pass to a SendMessage call since you don't know what the receving window will do with the pointer that you pass to it. Howerver since WM_SETTINGCHANGE is a built in Windows message, Windows will handle this pointer for you.



回答2:

SendMessage is a bit painful due to the non-descript argument types it uses. Necessary because it needs to do many jobs. Necessary in the C language, but not in C#. What you want to do here is take advantage of the C# language supporting overloads. The IntPtr arguments can just be reference type references, the pinvoke marshaller will properly convert them to a pointer and take care of the memory management hassle. So just craft another one that's compatible with the way you want to use it:

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageTimeout(
    IntPtr hWnd,
    int Msg,
    IntPtr wParam,
    string lParam,
    int fuFlags,
    int uTimeout,
    IntPtr lpdwResult
);

Now you can use:

 SendMessageTimeout((IntPtr)0xffff, 0x001A, IntPtr.Zero, "Environment", 
                    2, 5000, IntPtr.Zero);