I am writing a test automation program, that must interact with a grid control in a legacy MFC application. My program must connect to the legacy app, and read the data from the grid.
I know the HWND of the window that contains the grid, and so now I need to find the CWnd-derived class pointer associated with that HWND. CWnd::FromHandlePermanent seems to be my friend, but yes, I know, you cannot call CWnd::FromHandlePermanent from outside the application, as the AFX window map (afxMapHWND()) is only contained in the target application.
Therefore, I inject a DLL (using CreateRemoteThread/LoadLibrary) into the target app, and get it to call FromHandlePermanent. But even this is not good enough, because I am not in the thread of the HWND, and afxMapHWND() is looking at the wrong local thread storage.
Therefore, in my injected DLL, I also (temporarily) subclass the WndProc of the HWND (SetWindowLong etc), then call SendMessage. Now I am in the correct thread (the main thread of the target application) and I try to call CWnd::FromHandlePermanent with my HWND, but it returns NULL!!
If I look at afxMapHWND()->m_permanentMap->m_nCount, we see it is 0. So there are NO attached classes in the permanentMap, which seems wrong to me.
So how can I get the derived CWnd pointer??
Some other info:
The Target application is statically linked to MFC
It's a different version of MFC, the windowclass is AfxWnd70s
I am using VS2010 to compile the injected DLL
The injected DLL also links statically to the (VS2010) MFC libraries.
Here is the code in the injected DLL:
// this is how we pass the HWND to the target DLL
// (this shared segment is also loaded in calling app)
#pragma data_seg (".shared")
__declspec(dllexport) HWND g_hWnd = 0;
#pragma data_seg ()
#pragma comment(linker,"/SECTION:.shared,RWS")
BEGIN_MESSAGE_MAP(CLibSpyMFCDllApp, CWinApp)
END_MESSAGE_MAP()
CLibSpyMFCDllApp::CLibSpyMFCDllApp()
{
}
CLibSpyMFCDllApp theApp;
extern CHandleMap* PASCAL afxMapHWND(BOOL bCreate = FALSE);
UINT g_WM_GETGRIDDATA = 0;
WNDPROC wpOrigEditProc;
LRESULT CALLBACK SpySubProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (g_WM_GETGRIDDATA != 0 && message == g_WM_GETGRIDDATA) {
CWnd * target1 = CWnd::FromHandlePermanent(hWnd);
// fails - target1 is null
return 0;
}
return CallWindowProc((WNDPROC ) wpOrigEditProc, hWnd, message, wParam, lParam);
}
BOOL CLibSpyMFCDllApp::InitInstance()
{
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
CWinApp::InitInstance();
if (g_hWnd) {
g_WM_GETGRIDDATA = RegisterWindowMessage(L"GetGridData");
wpOrigEditProc = (WNDPROC) SetWindowLong(g_hWnd, GWL_WNDPROC, (LONG) SpySubProc);
::SendMessage(g_hWnd, g_WM_GETGRIDDATA, 0, 0);
SetWindowLong(g_hWnd, GWL_WNDPROC, (LONG) wpOrigEditProc);
}
return TRUE;
}
Related
I'm currently using Windows 10, MSVC v142 (with VS2019) and wxWidgets 3.1.3. I have an old Windows C++ application that uses WinAPI for its GUI features, i.e Windows message loop, using "CreateWindow", and having to "manually" create all window procedures and event handling.
I want to improve this application by gradually replacing the UI using wxWidgets so I don't have to start over from scratch. I would implement new, independent UI features in wxWidgets (e.g specific dialogs), and then work my way back and replace all the old UI code with a wxWidgets implementation, without having to break the app along the way.
Below is a skeleton of how my app is currently set up:
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Handling window messages, e.g menus, buttons, etc.
}
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char* lpCmdLine, int nCmdShow)
{
// Initialize resources, register main window class using MainWndProc, etc.
// ...
HWND mainwnd = CreateWindow(/* CreateWindow args... */);
do
{
MSG msg = {};
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Per-frame application logic
// ...
} while (msg.message != WM_QUIT);
// Clean up resources
// ...
return 0;
}
How would I need to modify this so that all the WinAPI objects continue to function, but I can now also create windows using wxWidgets? I previously tried replacing the message loop above by initializing wxWidgets through a custom class derived from wxApp, but my application kept crashing during the cleanup code (which it sometimes wouldn't even reach).
EDIT: I managed to make it work, updated skeleton can be found below.
// Main WinAPI window procedure
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
// Code to handle other messages
// ...
case WM_CLOSE:
// Closing this window should shut down the app
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
class MyApp : public wxApp
{
public:
virtual bool OnInit() override
{
if(!Old_Init())
{
// Perform cleanup in case something goes wrong
Old_Exit();
return false;
}
// Wrap the WinAPI window in a dummy wxWindow
m_dummyMainWindow = new wxWindow();
m_dummyMainWindow->SetHWND(m_mainWnd);
return true;
}
int OnExit() override
{
// Unset the dummy window HWND and delete it (is this necessary?)
m_dummyMainWindow->SetHWND(NULL);
delete m_dummyMainWindow;
// Clean up everything else
return Old_Exit();
}
private:
bool Old_Init()
{
// Perform the old initialization
// ...
m_mainWnd = CreateWindow(/* CreateWindow args... */);
if(m_mainWnd)
{
return true;
}
else
{
return false;
}
}
int Old_Exit()
{
// Perform the old cleanup (previously done after exiting the Windows message loop)
// ...
return 0;
}
HWND m_mainWnd;
wxWindow* m_dummyMainWindow;
};
wxIMPLEMENT_APP_NO_MAIN(MyApp);
// App entrypoint
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char* lpCmdLine, int nCmdShow)
{
if (wxEntry())
{
// Exit if something goes wrong (this might not be the correct way to do it?)
wxExit();
}
return 0;
}
This does seem to function, all the old UI elements are working as before, but I am not 100% sure this is a stable solution. My debugger is warning me about memory leaks, which appear to increase in number when I activate UI elements (e.g open and close dialogs). I suspect WinAPI resources might not be cleaned up correctly. Am I missing anything?
EDIT2: I did some more debugging, and the code in my original app (i.e without wxWidgets) causes those "memory leaks" as well, and I can't replicate it in a minimal working example, so I suspect the issue is not related to wxWidgets at all. I am therefore confident that the approach described above should solve my problem, but I would not mind a second opinion.
I recommend looking at the MFC sample to see a working example of something close to what you want to do. MFC is, of course, not quite the same as Win32 API, but it should still be a good starting point.
Notably it shows how to pass the messages to wx event loop when you're running your own one (this part is in wxMFCApp class in include/wx/msw/mfc.h). Of course, if you can switch to running wx event loop, it would be even simpler.
You may also find it helpful to know that you can wrapp HWNDs you create into wxWindows using AssociateHandle() and handle their messages by overriding MSWHandleMessage() or even just using wxNativeWindow directly.
Good luck!
I tried to call a function-pointer from a dll that hooks the WM_LBUTONDOWN or WM_TOUCH message on all windows displayed at screen.
I have the following dll source code:
typedef void (*PtrFonct)(int nCode, WPARAM wParam, LPARAM lParam);
PtrFonct pf;
HHOOK global;
extern "C" __declspec(dllexport) LRESULT WINAPI procedure(int nCode, WPARAM wParam,LPARAM lParam)
{
if (nCode == HC_ACTION){
MSG* pMSG = (MSG*)lParam;
if (pMSG->message == WM_LBUTTONDOWN){
pf(nCode, wParam, lParam);
}
}
return CallNextHookEx(global, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) BOOL setCallback(void ((*callbackFunc)(int, WPARAM, LPARAM))){
pf = callbackFunc;
if (pf)
return TRUE;
return FALSE;
}
and my listener source code is the following one:
MSG message;
HMODULE lib = LoadLibrary(L"C:/HookTouch.dll");
if (lib) {
HOOKPROC procedure = (HOOKPROC)GetProcAddress(lib, "_procedure#12");
dllFunct fonctionCallback = (dllFunct)GetProcAddress(lib, "setCallback");
if (fonctionCallback)
fonctionCallback(MyCallback);
if (procedure)
hook = SetWindowsHookEx(WH_GETMESSAGE, procedure, lib, 0);
}
else
printf("Can't find dll!\n");
while (GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
FreeLibrary(lib);
UnhookWindowsHookEx(hook);
My own callback to display "Hello click" is this one:
void MyCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
printf("Hello Click\n");
}
I know my hook is working because I can display a message on click by using a message box instead of pf(nCode, wParam, lParam) but when I use this function-pointer, MyCallback is not triggered.
I checked if my function was well affected to the pf function-pointer and all seems to be ok.
Do you know why the call of pf(nCode, wParam, lParam) don't trigger the MyCallback function of the listener?
You are calling setCallback only for the dll that's loaded in your process. The dll's that will be injected by SetWindowsHookEx in all the other process will not have the callback set. On top of that, MyCallback is only defined in your own process; the Dll's injected in other processes have no trivial way of accessing it.
Since you cannot know which processes Windows has injected for you, you will need the Dll to broadcast its 'location' to you, via Inter-Process communcation, eg. Named Pipes. Once you have the process ID of each of the DLL's that were injected, you can use CreateRemoteThread to call a function inside the Dll, like setCallback.. there's still some work to be done so that the Dll can directly call you callback: You will need to give the Dll the exact offset of your callback from the module base, and the Dll will then need to use CreateRemoteThread itself to issue the call.
This all quickly becomes too tedious, and you would be wise to just use Named Pipe communication instead of issuing direct function calls.
Tip: An easy way to log things from other processes is using OutputDebugString. Alternatively, write to a file.
This approach will not work.
A message hook runs in the context of each thread that is being hooked. Each hooked process will get its own copy of your DLL injected into it. As such, only the copy that originally installed the hook will have a valid function pointer set. Besides, you can't call your callback function across process boundaries anyway.
You need to use a different IPC mechanism to let your injected hooks communicate back to your main app process.
For instance, you could create a hidden HWND and store it in a block of global shared memory and then each injected hook can send window messages to it, like WM_COPYDATA. Or, your main app can open a named pipe that each injected hook can then connect to and send data to.
I've been looking at creating a custom control with WinApi for my application, and I have made a class which contains the CustomDialogProc and CreateWindowEx and RegisterClass() functions.
I can set a breakpoint inside the CustomDialogProc and it hits, so the class is registered correctly.
However, I have to declare the CustomDialogProc function as static int he header of my class
static LRESULT CALLBACK CustomDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam,LPARAM lParam);
If I don't set it to static, I get the error
Error C3867 'CustomControl::CustomDialogProc': non-standard syntax; use '&' to create a pointer to member
IS this necessary, this requires all my controls created within this control to be static as well. What if I want multiple instances of this control?
How can I get around this? The main MsgProc doesn't seem to be a static function. Neither is the UpDownDialogProc in the first link shown below
Below is my code for CustomControl.h in case anyone needs it.
Put together from code found at:
https://msdn.microsoft.com/en-us/library/windows/desktop/hh298353(v=vs.85).aspx
https://www.codeproject.com/Articles/485767/True-Windows-control-subclassing
Thanks,
#pragma once
#include <windows.h>
#include <commctrl.h>
#pragma comment(lib, "comctl32.lib")
class CustomControl
{
public:
CustomControl();
~CustomControl();
LRESULT CALLBACK CustomDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam,LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
//DO STUFF HERE
break;
}
}
bool CreateControl(HWND hwnd, HINSTANCE* m_hApp_instance)
{
g_hInst = m_hApp_instance;
RegisterSubClass(*g_hInst, WC_LISTBOX, TEXT("CustomControl"), CustomDialogProc);
HWND hwndCustom = CreateWindow(TEXT("CustomControl"), NULL, WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hwnd, (HMENU)100, *g_hInst, NULL);
return true;
}
private:
HINSTANCE* g_hInst;
WNDPROC RegisterSubClass(HINSTANCE hInstance, LPCTSTR ParentClass, LPCTSTR ChildClassName, WNDPROC ChildProc) {
WNDCLASSEX twoWayStruct;
WNDPROC parentWndProc;
if (GetClassInfoEx(NULL, ParentClass, &twoWayStruct)) {
parentWndProc = twoWayStruct.lpfnWndProc; // save the original message handler
twoWayStruct.cbSize = sizeof(WNDCLASSEX); // does not always get filled properly
twoWayStruct.hInstance = hInstance;
twoWayStruct.lpszClassName = ChildClassName;
twoWayStruct.lpfnWndProc = ChildProc;
/* Register the window class, and if it fails return 0 */
if (RegisterClassEx(&twoWayStruct))
return parentWndProc; // returns the parent class WndProc pointer;
// subclass MUST call it instead of DefWindowProc();
// if you do not save it, this function is wasted
}
return 0;
}
};
The most common way is to use SetWindowLongPtr to store a pointer to the object associated with the window handle.
HWND hWnd = CreateWindow(...);
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) this);
And then in your dialog proc, get that pointer and call into your class:
// this static method is registered with your window class
static LRESULT CALLBACK CustomDialogProcStatic(HWND hWnd, UINT uMsg, WPARAM wParam,LPARAM lParam)
{
auto pThis = (CustomControl*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
if (pThis != NULL)
return pThis->CustomDialogProcInstance(hWnd, uMsg, wParam, lParam);
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
// this instance method is called by the static method
LRESULT CustomDialogProcInstance(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
...
}
Make sure you manage your window and class life cycle appropriately to prevent the window proc from calling a deleted object instance. In many cases, this is as simple as ensuring DestroyWindow is called if your class is destructed.
The Windows API is C language based. It knows nothing about C++, non-static member functions, objects, etc.
So yes, all of your functions that will communicate with the Windows API directly must be static class member functions, or non-class / global / "free" functions.
That doesn't preclude you from writing a C++ wrapper for a C API. That's what libraries such as Microsoft's MFC or the old Borland OWL libraries accomplish. Other independent groups have also written wrappers for the Windows API.
Note that these differing libraries accomplish the goal of hooking a C based API to C++ in different ways. One is to use the SetWindowLongPtr method mentioned in the answer given by #MichaelGunter. Another method is to use maps to associate window handles and static Window procedures.
I would suggest before you try this on your own (creating a wrapper), you investigate how others have done this already, and choose the best approach that fits. Another suggestion is that before you even create a wrapper, you should know the C based API on much more than a cursory level. You need advanced to expert knowledge of any C API you plan to create a C++ wrapper for if you want the wrapper to work relatively flawless under different scenarios.
This article explains brilliantly the options to call a class member WndProc. I've seen this response in stackoverflow but the main problem associating class member WndProc after CreateWindow is that some messages will be lost (including the important WM_CREATE) as explained in the mentioned article.
My question: I would like to hear the opinion from an expert on which of the methods exposed below or new one is the best one (performance, maintanability, ...) to create a class member WndProc.
Briefing the two final solutions exposed in the article (suposing that it exists a Window class with WndProc method):
Per-window data with this global pointer storage, protecting it with CRITICAL_SECTION to make it thread safe (extracted from here):
// The helper window procedure
// It is called by Windows, and thus it's a non-member function
// This message handler will only be called after successful SetWindowLong call
// We can assume that pointer returned by GetWindowLong is valid
// It will route messages to our member message handler
LRESULT CALLBACK WndProc2(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
// Get a window pointer associated with this window
Window *w = (Window *) GetWindowLong(hwnd, GWL_USERDATA);
// It should be valid, assert so
_ASSERT(w);
// Redirect messages to the window procedure of the associated window
return w->WndProc(hwnd, msg, wp, lp);
}
// The temporary global this pointer
// It will be used only between CreateWindow is called and the first message is processed by WndProc
// WARNING: it is not thread-safe.
Window *g_pWindow;
// Critical section protecting the global Window pointer
CRITICAL_SECTION g_WindowCS;
// The helper window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
// Stash global Window pointer into per-window data area
SetWindowLong(hwnd, GWL_USERDATA, (long) g_pWindow);
// Unlock global critical section
g_pWindow->HaveCSLock = false;
LeaveCriticalSection(&g_WindowCS);
// Reset the window message handler
SetWindowLong(hwnd, GWL_WNDPROC, (long) WndProc2);
// Dispatch first message to the member message handler
return WndProc2(hwnd, msg, wp, lp);
}
And now we can create the window:
InitializeCriticalSection(&g_WindowCS);
// Enter the critical section before you write to protected data
EnterCriticalSection(&g_WindowCS);
// Set global Window pointer to our Window instance
// Moved the assignment here, where we have exclusive access to the pointer
g_pWindow = &w;
// Set a flag indicating that the window has the critical section lock
// Note: this must be executed after the above assignment
g_pWindow->HaveCSLock = true;
// Create window
// Note: lpParam is not used
HWND hwnd = CreateWindow(TEXT("BaseWnd"), TEXT("Hello, World!"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hinst, 0);
// Leave critical section if window creation failed and our window procedure hasn't released it
if (g_pWindow->HaveCSLock)
LeaveCriticalSection(&g_WindowCS);
// Destroy critical section
// In production code, you'd do this when application terminates, not immediately after CreateWindow call
DeleteCriticalSection(&g_WindowCS);
Using CBT hook procedure (extracted from here):
// The helper window procedure
// It is called by Windows, and thus it's a non-member function
// This message handler will only be called after successful SetWindowLong call from the hook
// We can assume that pointer returned by GetWindowLong is valid
// It will route messages to our member message handler
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
// Get a window pointer associated with this window
Window *w = (Window *) GetWindowLong(hwnd, GWL_USERDATA);
// It should be valid, assert so
_ASSERT(w);
// Redirect messages to the window procedure of the associated window
return w->WndProc(hwnd, msg, wp, lp);
}
// The CBT hook procedure
// It is called during CreateWindow call before WndProc receives any messages
// Its job is to set per-window Window pointer to the one passed through lpParam to CreateWindow
LRESULT CALLBACK CBTProc(int code, WPARAM wp, LPARAM lp)
{
if (code != HCBT_CREATEWND) {
// Ignore everything but create window requests
// Note: generally, HCBT_CREATEWND is the only notification we will get,
// assuming the thread is hooked only for the duration of CreateWindow call.
// However, we may receive other notifications, in which case they will not be passed to other CBT hooks.
return 0;
}
// Grab a pointer passed to CreateWindow as lpParam
std::pair<Window *, HHOOK> *p = (std::pair<Window *, HHOOK> *) LPCBT_CREATEWND(lp)->lpcs->lpCreateParams;
// Only handle this window if it wasn't handled before, to prevent rehooking windows when CreateWindow is called recursively
// ie, when you create windows from a WM_CREATE handler
if (p->first) {
// Stash the associated Window pointer, which is the first member of the pair, into per-window data area
SetWindowLong((HWND) wp, GWL_USERDATA, (long) p->first);
// Mark this window as handled
p->first = 0;
}
// Call the next hook in chain, using the second member of the pair
return CallNextHookEx(p->second, code, wp, lp);
}
And now we can create the window:
// Install the CBT hook
// Note: hook the thread immediately before, and unhook it immediately after CreateWindow call.
// The hook procedure can only process window creation nofitications, and it shouldn't be called for other types of notifications
// Additionally, calling hook for other events is wasteful since it won't do anything useful anyway
HHOOK hook = SetWindowsHookEx(WH_CBT, CBTProc, 0, GetCurrentThreadId());
_ASSERT(hook);
// Create window
// Pass a pair consisting of window object pointer and hook as lpParam
std::pair<Window *, HHOOK> p(&w, hook);
HWND hwnd = CreateWindow(TEXT("BaseWnd"), TEXT("Hello, World!"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hinst, &p);
// Unhook first
UnhookWindowsHookEx(hook);
I personally would not use either of these methods. The global variable approach works, but feels dirty. Especially with the lock. And the CBT hook is, well over the top. Although it points in the right direction.
The standard way to pass state information to your window procedure during creation is through lpParam parameter of CreateWindow or CreateWindowEx. So the technique is as follows:
Pass your instance pointer in the lpParam parameter of CreateWindow or CreateWindowEx.
Read this value in your WM_NCCREATE handler. That message supplies the information as part of the CREATESTRUCT struct.
Still in WM_NCCREATE call SetWindowLongPtr to set the user data of the window to the instance pointer.
All future calls to the window procedure can now obtain the instance pointer by calling GetWindowLongPtr.
Raymond Chen illustrates the details here: How can I make a WNDPROC or DLGPROC a member of my C++ class?
I think I've fallen in the same trap as many before me where I try to impose a nice OO methodology on win32 API programming. No MFC, no AFX, I'm not even using VC++, I'm using C::B with gcc.
I think what I'm trying to do is impossible, but since MFC exists (although I'm not using it) there must be some way.
I've created a class to contain several window controls. It implements handlers for WM_CREATE and WM_COMMAND, and keeps track of all the associated data around my small group of controls (ID codes and HWNDs).
It works great for buttons, static controls, even light GDI methods, but it all breaks down when I try to subclass an edit control.
Really, I just want to capture the "enter" key, but as anybody who's been down that road before will attest, when an edit control has focus, the parent window doesn't receive WM_KEYDOWN or WM_COMMAND, we are left to implement our own proc. Super lame.
OK, so subclassing an edit control is fine, if the editProc is global or static. I know that is because SetWindowLongPtr needs a function address, and that concept is nebulous for a member function.
So the object of my class is declared as "static" inside the parent WndProc. But the function is not "static" because then I wouldn't have access to non-static data members (completely defeating the purpose of this exercise). I'm hoping that because the objest is itself static, I should be able to properly define the address of one of its member functions.
Readers that have tried this before will either have given up and used MFC or something else, or perhaps have found a clever work-around.
I'll let this sample code do the rest of the talking: (simplified - will not compile as such)
/**** myprogram.c ****/
#include "MyControlGroup.h"
int winMain(){ // etc... }
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// object is static becuse it only needs to be initialized once
static MyControlGroup myControl;
if (msg == WM_CREATE)
myControl.onWMCreate(hWnd);
else if (msg == WM_COMMAND)
myControl.onWMCommand( wParam, lParam );
else if (msg == WM_DESTROY)
PostQuitMessage(0);
return DefWindowProcW(l_hWnd, l_msg, l_wParam, l_lParam);
}
The header file for my class:
/**** MyControlGroup.h ****/
class MyControlGroup
{
private:
HWND m_hWndParent;
HWND m_hWndEditBox;
int m_editBoxID;
public:
MyControlGroup();
void onWMCreate(HWND);
void onWMCommand(WPARAM, LPARAM);
// want to find a way to pass the address of this function to SetWindowLongPtr
LRESULT myEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
};
...and the implementation:
/**** MyControlGroup.cpp ****/
static int staticID = 1;
MyControlGroup::MyControlGroup()
{
m_editBoxID = staticID++;
}
void MyControlGroup::onWMCreate(HWND hWnd)
{
// My control group has buttons, static controls, and other stuff which are created here with CreateWindowW. It also has an edit control:
m_hWndEditBox = CreateWindowW(L"EDIT", L"initial text", WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 10, 150, 20, hWnd, (HMENU)m_editBoxID, NULL, NULL);
/*
To subclass the edit control, I need a pointer to my customized proc. That means I
need a pointer-to-member-function, but SetWindowLongPtr needs a pointer to global or
static function (__stdcall or CALLBACK, but not __thiscall).
*/
// I'd like to do something like this, adapted from a great write-up at
// http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
LERSULT (MyControlGroup::*myEditProcPtr)(HWND, UINT, WPARAM, LPARAM);
myEditProcPtr = &MyControlGroup::myEditProc;
// Up to now it compiles ok, but then when I try to pass it to SetWindowLongPtr, I get
// an "invalid cast" error. Any ideas?
SetWindowLongPtr(m_hWndEditBox, GWLP_WNDPROC, (LPARAM)myEditProcPtr);
}
void MyControlGroup::onWMCommand(WPARAM wParam, LPARAM lParam){ /* process parent window messages. Editboxes don't generate WM_COMMAND or WM_KEYDOWN in the parent :''( */}
LRESULT MyControlGroup::myEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// process messages like IDOK, WM_KEYDOWN and so on in the edit control
}
Even once I get this done, I'll still need to figure out a way to pass the address of the parent WndProc to myEditProc for the return value, but until I get past this there is no point in worrying about that.
Thanks in advance for reading!
myEditProc needs to be a static function.
Once you've done that you can pass the address of the function directly without going through the intermediate variable:
static LRESULT myEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
...
SetWindowLongPtr(m_hWndEditBox, GWLP_WNDPROC, (LPARAM)myEditProc);
To access your class data from the static function, you can save it in the userdata field of the edit control, e.g.:
// before sub-classing the control
SetWindowLongPtr(m_hWndEditBox, GWLP_USERDATA, (LPARAM)this);
// in the sub-class procedure
MyControlGroup* pThis = (MyControlGroup*)GetWindowLongPtr(m_hWndEditBox, GWLP_USERDATA);
But as #K-ballo suggested, SetWindowSubclass is definitely the way to do this unless you want compatibility with pre-XP. It handles the sub-classing procedure for you automatically, lets you associate a userdata pointer (e.g. this) that is automatically passed to the sub-class procedure, and safely handles removing the sub-class at the end.