I am working on a Windows-only Qt application, and I need to receive data from a Microsoft OneNote plugin. The plugin is written in C#, and can send WM_COPYDATA messages. How do I receive these messages in a C++ Qt app?
I need to:
Be able to specify the "class name" a window registers as when it calls RegisterClassEx, so that I can make sure the plugin sends WM_COPYDATA messages to the correct window.
Have access to the message id to check if it's WM_COPYDATA and lParam, which contains the COPYDATASTRUCT with the actual data. This information is passed in WndProc, but I am unable to find a hook where I can intercept these messages.
This can all be handled within Qt:
Extend QWidget with a class that will capture the WM_COPYDATA messages:
class EventReceiverWindow : public QWidget
{
Q_OBJECT
public:
EventReceiverWindow();
signals:
void eventData(const QString & data);
private:
bool winEvent ( MSG * message, long * result );
};
Generate a GUID to set as the QWidget's windowTitle:
EventReceiverWindow::EventReceiverWindow()
{
setWindowTitle("ProjectName-3F2504E0-4F89-11D3-9A0C-0305E82C3301::EventReceiver");
}
Override winEvent to handle the WM_COPYDATA structure and emit a signal when you get it:
bool EventReceiverWindow::winEvent ( MSG * message, long * result )
{
if( message->message == WM_COPYDATA ) {
// extract the string from lParam
COPYDATASTRUCT * data = (COPYDATASTRUCT *) message->lParam;
emit eventData(QString::fromAscii((const char *)data->lpData, data->cbData));
// keep the event from qt
*result = 0;
return true;
}
// give the event to qt
return false;
}
In another class, you can use this class to receive the message strings:
EventReceiverWindow * eventWindow = new EventReceiverWindow;
QObject::connect(eventWindow, SIGNAL(eventData(const QString &)), this, SLOT(handleEventData(const QString &)));
...
void OneNoteInterface::handleEventData(const QString &data)
{
qDebug() << "message from our secret agent: " << data;
}
And in the program that is sending the messages, simply find the window by the unique window caption. Here's an example in C#:
private struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
private const int WM_COPYDATA = 0x4A;
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);
private void sendMessageTo(IntPtr hWnd, String msg)
{
int wParam = 0;
int result = 0;
if (hWnd != IntPtr.Zero )
{
byte[] sarr = System.Text.Encoding.Default.GetBytes(msg);
int len = sarr.Length;
COPYDATASTRUCT cds;
cds.dwData = IntPtr.Zero;
cds.lpData = msg;
cds.cbData = len + 1;
result = SendMessage(hWnd, WM_COPYDATA, wParam, ref cds);
}
}
then you can:
IntPtr hwnd = FindWindowByCaption(IntPtr.zero, "ProjectName-3F2504E0-4F89-11D3-9A0C-0305E82C3301::EventReceiver");
sendMessageTo(hwnd, "omg hai");
You can also create a dummy window just for receiving that message with the Win32 API. I guess you won't have access to a Qt-Window's window proc, so this should be the easiest way.
You could (I wouldn't) also subclass the window by setting a new WndProc (with SetWindowLong(Ptr), the window's handle can be obtained with QWidget::winId()). In this WndProc, you could just handle your specific WM_COPYDATA and pass all other window messages to the old WndProc.
To handle messages your window receives, override your QCoreApplication::winEventFilter. If that doesn't work you can take a look at QAbstractEventDispatcher.
For the class name you could try using QWidget::winId along with Win32 API. I would try and find it for you but I can't right now, maybe try GetClassName.
You can use QWinHost from Qt solutions to create a dummy window. Following the guide will show you how to specify your class name and check the event loop for your message.
Related
I want to get the MessageBoxIcons, that get displayed when the user is presented with a MessageBox. Earlier I used SystemIcons for that purpose, but now it seems that it returns icons different than the ones on the MessageBox.
This leads to the conclusion that in Windows 8.1 SystemIcons and MessageBoxIcons are different. I know that icons are taken using WinApi MessageBox, but I can't seem to get the icons themselves in any way.
I would like to ask for a way of retrieving those icons.
Update:
You should use the SHGetStockIconInfo function.
To do that in C# you will have to define a few enums and structs (consult this excellent page for more information):
public enum SHSTOCKICONID : uint
{
//...
SIID_INFO = 79,
//...
}
[Flags]
public enum SHGSI : uint
{
SHGSI_ICONLOCATION = 0,
SHGSI_ICON = 0x000000100,
SHGSI_SYSICONINDEX = 0x000004000,
SHGSI_LINKOVERLAY = 0x000008000,
SHGSI_SELECTED = 0x000010000,
SHGSI_LARGEICON = 0x000000000,
SHGSI_SMALLICON = 0x000000001,
SHGSI_SHELLICONSIZE = 0x000000004
}
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SHSTOCKICONINFO
{
public UInt32 cbSize;
public IntPtr hIcon;
public Int32 iSysIconIndex;
public Int32 iIcon;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260/*MAX_PATH*/)]
public string szPath;
}
[DllImport("Shell32.dll", SetLastError = false)]
public static extern Int32 SHGetStockIconInfo(SHSTOCKICONID siid, SHGSI uFlags, ref SHSTOCKICONINFO psii);
After that you can easily get the required icon:
SHSTOCKICONINFO sii = new SHSTOCKICONINFO();
sii.cbSize = (UInt32)Marshal.SizeOf(typeof(SHSTOCKICONINFO));
Marshal.ThrowExceptionForHR(SHGetStockIconInfo(SHSTOCKICONID.SIID_INFO,
SHGSI.SHGSI_ICON ,
ref sii));
pictureBox1.Image = Icon.FromHandle(sii.hIcon).ToBitmap();
This is how the result will look like:
Please note:
If this function returns an icon handle in the hIcon member of the
SHSTOCKICONINFO structure pointed to by psii, you are responsible for
freeing the icon with DestroyIcon when you no longer need it.
i will not delete my original answer, as - I think - it contains useful information regarding this issue, and another way (or workaround) of retrieving this icon.
Original answer:
Quite interestingly the icons present in the SystemIcons differ from the ones displayed on the MessageBoxes in the case of Asterisk, Information and Question. The icons on the dialog look much flatter.
In all other cases they look exactly the same, e.g.: in case of Error:
When you try to get the icon using the SystemIcons you get the one on the left in the above images.
// get icon from SystemIcons
pictureBox1.Image = SystemIcons.Asterisk.ToBitmap();
If you try a little bit harder, using the LoadIcon method from user32.dll, you still get the same icon (as it can be seen in center of the above images).
[DllImport("user32.dll")]
static extern IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName);
...
public enum SystemIconIds
{
...
IDI_ASTERISK = 32516,
...
}
...
// load icon by ID
IntPtr iconHandle = LoadIcon(IntPtr.Zero, new IntPtr((int)SystemIconIds.IDI_ASTERISK));
pictureBox2.Image = Icon.FromHandle(iconHandle).ToBitmap();
But when you show a MessagBox you get a different one (as seen in the MessageBox on the images). One has no other choice, but to get that very icon from the MessageBox.
For that we will need a few more DllImports:
// To be able to find the dialog window
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
// To be able to get the icon window handle
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
// To be able to get a handle to the actual icon
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
The idea is the following: First we display a MessageBox, after that (while it is still displayed) we find it's handle, using that handle we will get another handle, now to the static control which is containing the icon. In the end we will send a message to that control (an STM_GETICON message), which will return with a handle to the icon itself. Using that handle we can create an Icon, which we can use anywhere in our application.
In code:
// show a `MessageBox`
MessageBox.Show("test", "test caption", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
...
var hwnd = FindWindow(null, "test caption");
if (hwnd != IntPtr.Zero)
{
// we got the messagebox, get the icon from it
IntPtr hIconWnd = GetDlgItem(hwnd, 20);
if (hIconWnd != IntPtr.Zero)
{
var iconHandle = SendMessage(hIconWnd, 369/*STM_GETICON*/, IntPtr.Zero, IntPtr.Zero);
pictureBox3.Image = Icon.FromHandle(iconHandle).ToBitmap();
}
}
After the code runs the PictureBox called pictureBox3 will display the same image as the MessageBox (as it can be seen on the right on the image).
I really hope this helps.
For reference here is all the code (it's a WinForms app, the Form has three PicturBoxes and one Timer, their names can be deducted from the code...):
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
[DllImport("user32.dll")]
static extern IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
public enum SystemIconIds
{
IDI_APPLICATION = 32512,
IDI_HAND = 32513,
IDI_QUESTION = 32514,
IDI_EXCLAMATION = 32515,
IDI_ASTERISK = 32516,
IDI_WINLOGO = 32517,
IDI_WARNING = IDI_EXCLAMATION,
IDI_ERROR = IDI_HAND,
IDI_INFORMATION = IDI_ASTERISK,
}
public Form1()
{
InitializeComponent();
// Information, Question and Asterix differ from the icons displayed on MessageBox
// get icon from SystemIcons
pictureBox1.Image = SystemIcons.Asterisk.ToBitmap();
// load icon by ID
IntPtr iconHandle = LoadIcon(IntPtr.Zero, new IntPtr((int)SystemIconIds.IDI_ASTERISK));
pictureBox2.Image = Icon.FromHandle(iconHandle).ToBitmap();
}
private void pictureBox1_Click(object sender, EventArgs e)
{
MessageBox.Show("test", "test caption", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
private void timer1_Tick(object sender, EventArgs e)
{
var hwnd = FindWindow(null, "test caption");
if (hwnd != IntPtr.Zero)
{
// we got the messagebox, get the icon from it
IntPtr hIconWnd = GetDlgItem(hwnd, 20);
if (hIconWnd != IntPtr.Zero)
{
var iconHandle = SendMessage(hIconWnd, 369/*STM_GETICON*/, IntPtr.Zero, IntPtr.Zero);
pictureBox3.Image = Icon.FromHandle(iconHandle).ToBitmap();
}
}
}
}
}
I am trying to implement global hotkeys on Windows in my C++/Qt application. I used RegisterHotKey, which surprisingly worked and I can see that pressing the combination triggers my event, but since I did not know any other way, I just used a loop, which now blocks my windows and stops it from ever showing up.
You can find my code below. How do I listen for this combination? There certainly has to be another way.
void set_win32_keys(MainWindow *mwin) {
HWND main_hwnd = (HWND)mwin->winId();
RegisterHotKey(main_hwnd, 2, MOD_CONTROL | MOD_SHIFT, 0x32 /*2 key*/);
MSG *msg;
msg = new MSG();
BOOL b_ret;
while ((b_ret = GetMessage(msg, main_hwnd, 0, 0)) != 0) {
if (b_ret == -1) {
qDebug() << "Error";
} else {
if (msg->message == WM_HOTKEY) {
mwin->new_screenshot();
qDebug() << msg;
}
}
}
}
Shameless plug: I have written a library for Qt that provides global hotkeys in a cross-platform manner - and allows the usage of for example QKeySequence to create the hotkey. It allows you to use a QKeySequenceEdit to let the user enter his own shortcut:
https://github.com/Skycoder42/QHotkey
Example:
//MainWindow constructor:
QHotkey *hotkey = new QHotkey(Qt::Key_2, Qt::ControlModifier | Qt::ShiftModifier, true, this);
connect(hotkey, &QHotkey::activated, this, &MainWindow::new_screenshot);
And that's it! Hope it helps
How can I listen to the system hot key bound to the app main window?
Many system events can be caught at main window native event handler. And the original author post actually binds to main window. The below should process the requested message:
class MainWindow : public QMainWindow
{
// ... snip ...
bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
// ... snip ...
};
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
MSG* pMsg = reinterpret_cast<MSG*>(message);
if (pMsg->message == WM_HOTKEY)
{
// process pMsg->wParam / lParam etc.
// the intended action
this->new_screenshot();
}
// call the parent, could be QWidget::nativeEvent
return QMainWidow::nativeEvent(eventType, message, result);
}
Because of assumption that mwin has QMainWindow*:
HWND main_hwnd = (HWND)mwin->winId();
RegisterHotKey(main_hwnd, 2, MOD_CONTROL | MOD_SHIFT, 0x32 /*2 key*/);
Working with C++, I've spent way to many hours already trying to figure this out. This code is from a working program, I'm rewriting it in C#, but there is some things happening I do not understand.
The below code is exactly what runs when I press "Step Into". How is it going from ::SendMessage(...) to ::OnCopyData(..) with pCopyDataStruct containing data now?
main.cpp
void COTP::main()
{
//string will be returned using WM_COPYDATA message
::SendMessage(hWnd, 33508, (WPARAM)GetSafeHwnd(), 11);
// WPARAM is a typedef for UINT_PTR which is an unsigned int.
}
afxwin2.inl
_AFXWIN_INLINE HWND CWnd::GetSafeHwnd() const
{ return this == NULL ? NULL : m_hWnd; }
main.cpp
BOOL COTP::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
CString str, str2;
switch (pCopyDataStruct->dwData)
{
case JRC_COPYDATA_ID_TRACK_FILENAME:
str = (LPCTSTR)pCopyDataStruct->lpData;
break;
}
}
Any help super appreciated, I have looked thru all the msdn documents that I can tonight, it just seems like I'm missing something simple somewhere. Thought it might be a callback, but that doesn't seem right.
The code is sending a message (33508) to a specific window. Apparently the receiving window processes that message by sending a WM_COPYDATA message to the HWND you passed in the WPARAM in the original SendMessage() call. It's essentially implementing a callback mechanism.
The original message number (33508) is not one the standard Win32 messages (at least not that I recognize), so it's likely a custom message. Also, the fact that it's using WM_COPYDATA to respond would suggest that the receiving window is in a different process (i.e., not owned by your application).
Alright got it figured out now. With the help of this page and here. Below is what I'm currently using for C#. This is by no means finished code, but this does work and is good for learning purposes.
Click calculate button,
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetActiveWindow();
private void btnCalculate_Click(object sender, EventArgs e)
{
int hwnd = 0;
hwnd = Win32.FindWindow("The App pulling from", "Window");
int s = (int)GetActiveWindow();
int s3 = Win32.SendMessage(hwnd, 33508, s, 11);
Then what happens is a callback occurs and activates WndProc(ref Message m),
protected override void WndProc(ref Message m)
{
// Prevents error creating window handle message.
base.WndProc(ref m);
// WM_COPYDATA
// m.Msg = 0x4a
//msg=0x4a (WM_COPYDATA) hwnd=0x251e62 wparam=0x69063e lparam=0x1c42cca0 result=0x0
if (m.Msg == 0x4a)
{
Console.WriteLine(m);
WndProc(m.HWnd, m.Msg, m.WParam, m.LParam);
}
}
public struct CopyDataStruct : IDisposable
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
public void Dispose()
{
if (this.lpData != IntPtr.Zero)
{
LocalFree(this.lpData);
this.lpData = IntPtr.Zero;
}
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public unsafe struct DataStruct
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 300)]
public string s;
public double d;
public char c;
};
protected void WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
{
CopyDataStruct cps = (CopyDataStruct)Marshal.PtrToStructure(lParam, typeof(CopyDataStruct));
DataStruct data = (DataStruct)Marshal.PtrToStructure(cps.lpData, typeof(DataStruct));
// data.s is what we needed.
Console.WriteLine(data.s);
}
Thank you very much to everyone that replied! I am confident I would have given up if not for your help. :)
I am trying to send a system message from parent process to a child process.
1. I registered message within parent method:
UINT msg = RegisterWindowMessageA("my message");
2.In the child application I override method nativeEvent.
I found syntax of that method in Qt assistant, but info provided not enough, because argument usage not described there.
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message,
long* result)
{
UINT mssg = RegisterWindowMessage(L"my message");
UINT recivedMssg = *static_cast<UINT*>(message);
if (recivedMssg == mssg)
{
*result = 0;
QMessageBox::information(this,"",QString::number(recivedMssg));
return true;
}
return false;
}
I did this implementation, but it doesn`t work as I expect, I considered that void* message - is number of message.
So my question is: how in nativeEvent I can get message which was sent from the parent process?
I didn't play with Qt5 yet but I doubt that there is any difference.
For Windows you should cast message as follows:
MSG* msg = reinterpret_cast<MSG*>(message);
Where MSG is Windows specific structure declared in some Windows header(windows.h will be enough for sure)
When using the CB_SETCURSEL message, the CBN_SELCHANGE message is not sent.
How to notify a control that the selection was changed ?
P.S.
I found on the Sexchange site, a very ugly hack :
SendMessage( hwnd, 0x014F/*CB_SHOWDROPDOWN*/, 1, 0 );
SendMessage( hwnd, 0x014E/*CB_SETCURSEL*/, ItemIndex, 0 );
SendMessage( hwnd, 0x0201/*WM_LBUTTONDOWN*/, 0, -1 );
SendMessage( hwnd, 0x0202/*WM_LBUTTONUP*/, 0, -1 );
Will do for now... Not really.
P.S.2
For resolving my problem, I'll follow Ken's suggestion in the comments.
This might help the next person out:
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hwnd, int msg, int wParam, int lParam);
[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
private static extern IntPtr GetWindowLongPtr32(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex);
public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size == 8)
return GetWindowLongPtr64(hWnd, nIndex);
else
return GetWindowLongPtr32(hWnd, nIndex);
}
static int MakeWParam(int loWord, int hiWord)
{
return (loWord & 0xFFFF) + ((hiWord & 0xFFFF) << 16);
}
public const int CB_SETCURSEL = 0x014E;
public const int CBN_SELCHANGE = 0x0001;
public enum GWL
{
GWL_WNDPROC = (-4),
GWL_HINSTANCE = (-6),
GWL_HWNDPARENT = (-8),
GWL_STYLE = (-16),
GWL_EXSTYLE = (-20),
GWL_USERDATA = (-21),
GWL_ID = (-12)
}
public static IntPtr Hwnd_select_control_parent = IntPtr.Zero;
public static IntPtr Hwnd_select_control = IntPtr.Zero;
static void changeit()
{
// Google WinSpy for tips on how to figure out how to get window handles from known ctrl_id
Hwnd_select_control = 14298; // or whatever the handle of the combo box is
// Get the parent of the selectbox control
Hwnd_select_control_parent = GetWindowLongPtr(service_window_control, (int)GWL.GWL_HWNDPARENT);
// Get the control id of the selectbox if you don't already have it
IntPtr nID = GetWindowLongPtr(Hwnd_select_control, (int)GWL.GWL_ID);
int ctrl_id = nID.ToInt32();
// Change the combo box to the value "My Value"
SendMessage(Hwnd_select_control, CB_SETCURSEL, "My Value", null);
// low ID is the ctrl_id of the combo box, high id is CBN_SELCHANGE
int send_cbn_selchange = MakeWParam(ctrl_id, CBN_SELCHANGE);
// Send the WM_COMMAND to the parent, not the control itself
SendMessage(Hwnd_serviceselect_control_parent, 0x111 /* WM_COMMAND */, send_cbn_selchange, Hwnd_serviceselect_control.ToInt32());
}
You're not supposed to use CBN_SELCHANGE unless the change in selection was made by the user.
You don't indicate what language you're using; it would make it easier to provide you with a workaround if you did so.
In Delphi, where an OnChange() would be associated with the combobox, you just call the event method directly:
// Send the CB_SETCURSEL message to the combobox
PostMessage(ComboBox1.Handle, CB_SETCURSEL, Whatever, WhateverElse);
// Directly call the OnChange() handler, which is the equivalent to CBN_SELCHANGE
ComboBox1Change(nil);
I just discovered calling these SendMessages to the Combobox twice works... I know it's not perfect but it worked for me. (Written in VB6)
For looper = 1 To 2
bVal = SendMessage(inHandle, COMBO.CB_SHOWDROPDOWN, True, 0)
count = SendMessage(inHandle, COMBO.CB_SETCURSEL, 1, 0)
count = SendMessage(inHandle, WIND.WM_LBUTTONDOWN, 0, -1)
Next