Search code examples
c#winapiclipboard

Monitor changes in Clipboard via WinApi32


I want to monitor clipboard changes (only text). I try to find some solutions and I found the following questions on StackOverflow:

But my problem/requirement is that I don't want to add a dependency to Windows.Forms nor WPF (my app is a console app). I try to look at available methods in user32.dll. I wrote a code that creates a window and window styles. Override a WndPrc method to a custom one and then add a listener to clipboard changes. WndProcFunction was invoked 4 times for the following messages:

  • WM_GETMINMAXINFO
  • WM_NCCREATE
  • WM_NCCALCSIZE
  • WM_CREATE

But when I change the content of a clipboard, message of type WM_CLIPBOARDUPDATE nor any message that would fit into WndProcFunction was sent. I try to use an "old API" (SetDashboardViewer) but it changes nothing. Code looks as follows:

using PInvoke;

...
internal static class User32Ext
{
    [DllImport("user32.dll", SetLastError=true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool AddClipboardFormatListener(IntPtr hwnd);

    [DllImport("user32.dll", SetLastError=true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool RemoveClipboardFormatListener(IntPtr hwnd);

    [DllImport("kernel32.dll")]
    internal static extern uint GetLastError();
}

class Program
{
    private static IntPtr _nextInChain = IntPtr.Zero;

    internal static unsafe IntPtr WndProcFunction(IntPtr hwnd, User32.WindowMessage windowMessage, void* wParam1, void* lParam1)
    {
        if (windowMessage == User32.WindowMessage.WM_CREATE)
        {
            var listener = User32Ext.AddClipboardFormatListener(hwnd);
            var result = User32.OpenClipboard(window);
            //_nextInChain = User32Ext.SetClipboardViewer(hwnd);
        }
        if (windowMessage == User32.WindowMessage.WM_CLIPBOARDUPDATE)
        {
            var pointerToText = User32.GetClipboardData_IntPtr(1);
            var text = Marshal.PtrToStringAnsi(pointerToText);
            Console.WriteLine(text);
        }

        if (windowMessage == User32.WindowMessage.WM_DRAWCLIPBOARD)
        {
            if (_nextInChain != IntPtr.Zero)
            {
                User32.SendMessage(_nextInChain, windowMessage, wParam1, lParam1);
            }
        }

        if (windowMessage == User32.WindowMessage.WM_CHANGECBCHAIN)
        {
            _nextInChain = hwnd;
            //send message...
        }

        if (windowMessage == User32.WM_DESTROY)
        {
            //chain msg
            User32Ext.RemoveClipboardFormatListener(hwnd);
        }

        return hwnd; //success
    }

    static void Main(string[] args)
    {
        unsafe
        {
            var hInstance = Marshal.GetHINSTANCE(typeof(Program).Module);
            string name = "Test";
            User32.WNDCLASSEX wndClassEx = new User32.WNDCLASSEX
            {
                cbSize = Marshal.SizeOf(typeof(User32.WNDCLASSEX)),
                style = User32.ClassStyles.CS_GLOBALCLASS,
                cbClsExtra = 0,
                cbWndExtra = 0,
                hbrBackground = IntPtr.Zero,
                hCursor = IntPtr.Zero,
                hIcon = IntPtr.Zero,
                hIconSm = IntPtr.Zero,
                lpszMenuName = null,
                hInstance = hInstance,
                lpfnWndProc = new User32.WndProc(WndProcFunction)
            };

            var stringPtr = Marshal.StringToHGlobalAuto(name);
            wndClassEx.lpszClassName_IntPtr = stringPtr;

            var register = User32.RegisterClassEx(ref wndClassEx);
            var window = User32.CreateWindowEx(
                User32.WindowStylesEx.WS_EX_TRANSPARENT,
                name,
                "Test Window",
                0,
                0, 0, 0, 0,
                IntPtr.Zero,
                IntPtr.Zero,
                wndClassEx.hInstance,
                IntPtr.Zero
            );

            User32.SetClipboardData(13, stringPtr);
            ...
        }
    }
}

Is there something I'm still missing in the code, some additional listener? I look at error codes after each call to a method(s) from user32.dll. But all succeed without any failure.


Solution

  • A message loop is required for receiving messages. If you don't want a window you can create a message-only window which enables you to send and receive messages.

    It is not visible, has no z-order, cannot be enumerated, and does not receive broadcast messages. The window simply dispatches messages.

    In C#, at the end of your main function, it will like this:

        MSG msg;  
        while (User32.GetMessage(out msg, IntPtr.Zero, 0, 0) != 0)  
        {  
            User32.TranslateMessage(ref msg);  
            User32.DispatchMessage(ref msg);  
        }  
    

    Refer to MSG, Creating a Message Loop.