I've created a dialogbox with progress bar and a cancel button using CreateDialogParam to show status while copying several files (using CopyFileEx).
How do I cancel the process using CopyFileEx correctly, starting from pressing the cancel button in dialogbox? Is there anyway I can do it without using global variable? And How do I correctly handle the returned PROGRESS_CANCEL? I have provided questions in the code below to make clearer what help do I need.
//copy function
BOOL copy(HWND &hWnd, std::vector <FILECONSOLIDATEPARAMS> &vec)
{
//pass vector as lparam to dialogbox proc
LPARAM lp = reinterpret_cast<LPARAM>(&vec);
HWND hCopy = CreateDialogParam(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_DIALOG1),
hwndmain, (DLGPROC)dlgboxcopyproc, lp);
static HWND hIDC_STATIC, hIDC_STATIC4;
hIDC_STATIC = GetDlgItem(hCopy, IDC_STATIC);
hIDC_STATIC4 = GetDlgItem(hCopy, IDC_STATIC4);
LPBOOL pbCancel = FALSE;
size_t s;
for (s = 0; s != vec.size(); s++)
{
SendMessage(hIDC_STATIC, WM_SETTEXT, 0, (LPARAM)vec[s].filename);
SendMessage(hIDC_STATIC4, WM_SETTEXT, 0,(LPARAM)vec[s].destination);
BOOL b = CopyFileEx(vec[s].filename, vec[s].destination,
&CopyProgressRoutine,(LPVOID)hCopy,pbCancel, NULL);
//how to catch and process PROGRESS_CANCEL?
if (!b)
{
DWORD dw = GetLastError();
ShowErrMsg(dw);
}
}
PostMessage(hCopy, WM_DESTROY, 0, 0);
return TRUE;
}
//dialogbox procedure
INT_PTR CALLBACK dlgboxcopyproc(HWND hWndDlg,UINT Msg,WPARAM wParam,LPARAM
lParam)
{
//translate passed lparam back to vector
std::vector<FILECONSOLIDATEPARAMS>& vect =
*reinterpret_cast<std::vector<FILECONSOLIDATEPARAMS>*>(lParam);
INITCOMMONCONTROLSEX _icex;
_icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
_icex.dwICC = ICC_PROGRESS_CLASS;
InitCommonControlsEx(&_icex);
static HWND hParent;
static HWND hIDCancel;
static HWND hIDC_PROGRESS1;
static HWND hIDC_STATIC;
hParent = GetParent(hWndDlg);
hIDCancel = GetDlgItem(hWndDlg, IDCANCEL);
hIDC_PROGRESS1 = GetDlgItem(hWndDlg, IDC_PROGRESS1);
switch (Msg)
{
case WM_INITDIALOG:
{
SendMessage(hIDC_PROGRESS1, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
}
return (INT_PTR)TRUE;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDCANCEL:
EndDialog(hWndDlg, FALSE); //how to make pbCancel = TRUE?
return (INT_PTR)TRUE;
}
}
break;
case WM_DESTROY:
{
DestroyWindow(hWndDlg);
}
}
return FALSE;
}
//copyprogressroutine callback function
DWORD CALLBACK CopyProgressRoutine(LARGE_INTEGER TotalFileSize,
LARGE_INTEGER TotalBytesTransferred, LARGE_INTEGER StreamSize,
LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber, DWORD
dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID
lpData)
{
HWND hWndDlg = (HWND)lpData;
static HWND hwndIDC_PROGRESS1;
hwndIDC_PROGRESS1 = GetDlgItem(hWndDlg, IDC_PROGRESS1);
DOUBLE Percentage = ((DOUBLE)TotalBytesTransferred.QuadPart /
(DOUBLE)TotalFileSize.QuadPart) * 100;
switch (dwCallbackReason)
{
case CALLBACK_CHUNK_FINISHED:
SendMessage(hwndIDC_PROGRESS1, PBM_SETPOS, (WPARAM) Percentage, 0);
break;
case CALLBACK_STREAM_SWITCH:
Percentage = 0;
break;
}
return PROGRESS_CONTINUE; //how to make conditional return PROGRESS_CANCEL?
}
code like
LPBOOL pbCancel = FALSE;
CopyFileEx(, pbCancel, )
senseless. really we simply pass 0 in place pbCancel and have no ability cancel operation.
we need alocate some variable (let name it bCancel) of type BOOL and pass pointer of this variable to CopyFileEx
something like this:
BOOL bCancel = FALSE;
CopyFileEx(, &bCancel, )
the CopyFileEx will be periodically query value of this variable by passed pointer and if it became true - break operation and return ERROR_REQUEST_ABORTED error.
the next - CopyFileEx not return until operation is complete - this is synchronous api - as result it can not be called from GUI thread (or it simply block GUI). need call it from separate thread.
so basic solution - allocate some data structure, here place BOOL bCancel, and other data, which we will be use during copy. we will access this data structure from 2 threads - gui thread (from dialog procedure) and from separate thread, which will call CopyFileEx. for correct implement this - need use reference counting on structure. the lpProgressRoutine need post messages to gui thread for notify it about progress (gui thread can move progress bars on this notification). dialog can containing for example Cancel button. when user click it - GUI thread simply set bCancel = TRUE, as result CopyFileEx abort operation on next chunk. for start copy - need start new thread, which call CopyFileEx. based on required logic - can do this from WM_INITDIALOG or say when user press some button in dialog
basic code:
struct CopyDlg
{
enum {
WM_COPYRESULT = WM_APP, WM_PROGRESS
};
struct ProgressData {
LARGE_INTEGER TotalFileSize;
LARGE_INTEGER TotalBytesTransferred;
LARGE_INTEGER StreamSize;
LARGE_INTEGER StreamBytesTransferred;
DWORD dwStreamNumber;
};
PCWSTR m_lpExistingFileName, m_lpNewFileName;
HWND m_hwnd, m_hwndFile, m_hwndStream;
ULONG m_shift;
LONG m_dwRefCount;
LONG m_hwndLock;
BOOL m_bCancel;
CopyDlg()
{
m_dwRefCount = 1;
}
void AddRef()
{
InterlockedIncrement(&m_dwRefCount);
}
void Release()
{
if (!InterlockedDecrement(&m_dwRefCount))
{
delete this;
}
}
void LockWnd()
{
InterlockedIncrement(&m_hwndLock);
}
void UnlockWnd()
{
if (!InterlockedDecrement(&m_hwndLock))
{
// want m_hwnd be valid at PostMessage(p->m_hwnd,) time
EndDialog(m_hwnd, 0);
}
}
void OnProgress(DWORD dwCallbackReason, ProgressData* p)
{
if (dwCallbackReason == CALLBACK_STREAM_SWITCH)
{
if (p->dwStreamNumber == 1)
{
m_shift = 0;
LONGLONG QuadPart = p->TotalFileSize.QuadPart;
while (QuadPart > MAXLONG)
{
m_shift++;
QuadPart >>= 1;
}
SendMessage(m_hwndFile, PBM_SETRANGE32, 0, (LPARAM)QuadPart);
SendMessage(m_hwndFile, PBM_SETPOS, 0, 0);
}
SendMessage(m_hwndStream, PBM_SETRANGE32, 0, (LPARAM)(p->StreamSize.QuadPart >> m_shift));
SendMessage(m_hwndStream, PBM_SETPOS, 0, 0);
}
else
{
SendMessage(m_hwndStream, PBM_SETPOS, (LPARAM)(p->StreamBytesTransferred.QuadPart >> m_shift), 0);
SendMessage(m_hwndFile, PBM_SETPOS, (LPARAM)(p->TotalBytesTransferred.QuadPart >> m_shift), 0);
}
}
ULONG StartCopy(PCWSTR lpExistingFileName, PCWSTR lpNewFileName)
{
m_bCancel = FALSE;
m_lpExistingFileName = lpExistingFileName;
m_lpNewFileName = lpNewFileName;
LockWnd();
AddRef();
if (HANDLE hThread = CreateThread(0, 0, reinterpret_cast<PTHREAD_START_ROUTINE>(CopyThread), this, 0, 0))
{
CloseHandle(hThread);
return NOERROR;
}
Release();
UnlockWnd();
return GetLastError();
}
static INT_PTR CALLBACK _DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_INITDIALOG)
{
SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)lParam);
}
if (CopyDlg* p = reinterpret_cast<CopyDlg*>(GetWindowLongPtrW(hwndDlg, DWLP_USER)))
{
p->AddRef();
INT_PTR r = p->DialogProc(hwndDlg, uMsg, wParam, lParam);
p->Release();
return r;
}
return 0;
}
INT_PTR DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_NCDESTROY:
Release();
break;
case WM_DESTROY:
m_bCancel = TRUE;
break;
case WM_COMMAND:
switch (wParam)
{
case MAKEWPARAM(IDCANCEL, BN_CLICKED):
m_bCancel = TRUE;
break;
}
break;
case WM_CLOSE:
m_bCancel = TRUE;
UnlockWnd();
ShowWindow(hwndDlg, SW_HIDE);//for not get wm_close twice
break;
case WM_COPYRESULT:
UnlockWnd();
// lParam == error code from CopyFileExW
DbgPrint("CopyFileExW=%u\n", (ULONG)lParam);
break;
case WM_PROGRESS:
OnProgress((DWORD)wParam, reinterpret_cast<ProgressData*>(lParam));
delete (void*)lParam;
break;
case WM_INITDIALOG:
AddRef();
m_hwnd = hwndDlg;
m_hwndStream = GetDlgItem(hwndDlg, IDC_PROGRESS1);
m_hwndFile = GetDlgItem(hwndDlg, IDC_PROGRESS2);
m_hwndLock = 1;
StartCopy(L"**", L"**");
break;
}
return 0;
}
static ULONG CALLBACK CopyThread(CopyDlg* p)
{
PostMessage(p->m_hwnd, WM_COPYRESULT, 0, CopyFileExW(
p->m_lpExistingFileName, p->m_lpNewFileName,
reinterpret_cast<LPPROGRESS_ROUTINE>(CopyProgressRoutine),
p, &p->m_bCancel, 0) ? NOERROR : GetLastError());
p->Release();
return 0;
}
static DWORD CALLBACK CopyProgressRoutine(
__in LARGE_INTEGER TotalFileSize,
__in LARGE_INTEGER TotalBytesTransferred,
__in LARGE_INTEGER StreamSize,
__in LARGE_INTEGER StreamBytesTransferred,
__in DWORD dwStreamNumber,
__in DWORD dwCallbackReason,
__in HANDLE /*hSourceFile*/,
__in HANDLE /*hDestinationFile*/,
__in_opt CopyDlg* p
)
{
switch(dwCallbackReason)
{
case CALLBACK_CHUNK_FINISHED:
case CALLBACK_STREAM_SWITCH:
if (ProgressData* data = new ProgressData)
{
data->TotalFileSize = TotalFileSize;
data->TotalBytesTransferred = TotalBytesTransferred;
data->StreamSize = StreamSize;
data->StreamBytesTransferred = StreamBytesTransferred;
data->dwStreamNumber = dwStreamNumber;
if (!PostMessage(p->m_hwnd, WM_PROGRESS, dwCallbackReason, (LPARAM)data))
{
delete data;
return PROGRESS_CANCEL;
}
}
break;
}
// for debugging
//Sleep(3000);
//MessageBoxW(0,0,0,0);
return PROGRESS_CONTINUE;
}
};
if (CopyDlg* p = new CopyDlg)
{
DialogBoxParamW((HINSTANCE)&__ImageBase, MAKEINTRESOURCE(IDD_DIALOG1), HWND_DESKTOP, CopyDlg::_DialogProc, (LPARAM)p);
p->Release();
}
Related
Why is D3Dcompiler_43.dll Loaded and Unloaded every frame?
Why does my two window direct-X 11 application drop from 2000 Hz to 12 Hz when I resize one of the windows?
In simplified sample I have attached code from, it starts with two identical sized windows at 2000 Hz, but if I change the size of either one, the frame rate of both goes to 12 Hz.
If I minimize either one, I get back to full speed, 4000Hz with one window.
When it is running at 12 Hz, I get the following Output Trace EVERY FRAME:
'Galaxies.exe' (Win32): Loaded 'C:\Windows\System32\D3Dcompiler_43.dll'
'Galaxies.exe' (Win32): Unloaded 'C:\Windows\System32\D3Dcompiler_43.dll'
This trace comes out when I call IDGXISwapChain->Present().
This trace does not come out when running 2000Hz.
I do not expect to need D3Dcompiler_43.dll at all, since I build my shaders at compile time, not run time. But if it did need it, then it should load it and keep it, not load each frame.
And if I make the window sizes match again, or minimize one, then the trace stops.
C++, Directx 11
My basic template is based on the Microsoft Sample "SimpleTexturePC".
Main changes from that template:
compile textures into program rather than reading at run time
compile shaders into program rather than reading at run time
extend DeviceResources to be able to support more than one window
by making arrays for all the resources, such as RenderTargetView
Extending Main.cpp to create more than one window, and have a WinProc for each.
Each of these unique WinProcs then calls a WinProcCommon with an "index" that identifies the window.
Adding a second camera for use on 2nd window
Adding a trace to the main application "Galaxies.cpp" so I could see the FPS.
(the rest of the main application behavior is not in simplified sample).
Main.cpp
//
// Main.cpp
//
#include ...
namespace
{
std::unique_ptr<Galaxies> g_game_p;
};
LPCWSTR g_szAppName0 = L"World1";
LPCWSTR g_szAppName1 = L"World2";
LPCWSTR g_szAppName2 = L"World3";
LRESULT CALLBACK WndProcWorld1(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProcWorld2(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProcWorld3(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProcCommon(UINT, HWND, UINT, WPARAM, LPARAM);
// Entry point
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
if (!XMVerifyCPUSupport())
return 1;
HRESULT hr = CoInitializeEx(nullptr, COINITBASE_MULTITHREADED);
if (FAILED(hr))
return 1;
g_game_p = std::make_unique<Galaxies>();
int border = getInvisableBorderWidth();
// Register / create window 1
{
// Register class
WNDCLASSEXW wcex = {};
wcex.cbSize = sizeof(WNDCLASSEXW);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProcWorld1;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIconW(hInstance, L"IDI_ICON1");
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
wcex.lpszClassName = L"WindowClass1";
wcex.hIconSm = LoadIconW(wcex.hInstance, L"IDI_ICON1");
if (!RegisterClassExW(&wcex)) // W implies unicode version.
return 1;
// Create window
int w, h;
g_game_p->GetDefaultWindowSize(w, h);
HWND hwnd = CreateWindowExW(0, wcex.lpszClassName, g_szAppName0, WS_OVERLAPPEDWINDOW,
50 - border, 50, w, h, nullptr, nullptr, hInstance, nullptr);
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(g_game_p.get()));
if (!hwnd)
return 1;
g_game_p->Initialize(kGameWindow1, hwnd, w, h); // this chooses non-fullscreen location, though we will go full screen in a sec...
ShowWindow(hwnd, SW_SHOWNORMAL);
}
// Register / create window 2
{
// Register class
WNDCLASSEXW wcex = {};
wcex.cbSize = sizeof(WNDCLASSEXW);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProcWorld2;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIconW(hInstance, L"IDI_ICON2");
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
wcex.lpszClassName = L"WindowClass2";
wcex.hIconSm = LoadIconW(wcex.hInstance, L"IDI_ICON2");
if (!RegisterClassExW(&wcex)) // W implies unicode version.
return 1;
// Create window
int w, h;
g_game_p->GetDefaultWindowSize(w, h);
HWND hwnd = CreateWindowExW(0, wcex.lpszClassName, g_szAppName1, WS_OVERLAPPEDWINDOW,
100 - border, 100, w + 00, h + 00, nullptr, nullptr, hInstance, nullptr);
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(g_game_p.get()));
if (!hwnd)
return 1;
g_game_p->Initialize(kGameWindow2, hwnd, w, h);
ShowWindow(hwnd, SW_SHOWNORMAL);
}
// Main message loop
MSG msg = {};
while (WM_QUIT != msg.message)
{
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
g_game_p->Tick();
}
}
g_game_p.reset();
CoUninitialize();
return static_cast<int>(msg.wParam);
}
// First parameter "index" used to differentiate the three windows.
LRESULT CALLBACK WndProcCommon(UINT index, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
WindowStruct& winInfo = g_deviceResources_p->GetWindowStruct(index);
auto galaxies_p = reinterpret_cast<Galaxies*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
switch (message)
{
case WM_PAINT:
if (winInfo.in_sizemove && galaxies_p)
{
RECT rc;
GetWindowRect(hWnd, &rc);
galaxies_p->OnWindowSizeChanged(index, rc.right - rc.left, rc.bottom - rc.top);
galaxies_p->Tick();
}
else
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
case WM_MOVE:
if (galaxies_p)
{
galaxies_p->OnWindowMoved(index);
}
break;
case WM_SIZE:
if (wParam == SIZE_MINIMIZED)
{
if (!winInfo.minimized)
{
winInfo.minimized = true;
if (!winInfo.in_suspend && galaxies_p)
galaxies_p->OnSuspending();
winInfo.in_suspend = true;
}
}
else if (winInfo.minimized)
{
winInfo.minimized = false;
if (winInfo.in_suspend && galaxies_p)
galaxies_p->OnResuming();
winInfo.in_suspend = false;
}
else if (!winInfo.in_sizemove && galaxies_p)
{
RECT rc;
GetWindowRect(hWnd, &rc);
galaxies_p->OnWindowSizeChanged(index, rc.right - rc.left, rc.bottom - rc.top);
}
break;
case WM_ENTERSIZEMOVE:
winInfo.in_sizemove = true;
break;
case WM_EXITSIZEMOVE:
winInfo.in_sizemove = false;
if (galaxies_p)
{
RECT rc;
GetWindowRect(hWnd, &rc);
galaxies_p->OnWindowSizeChanged(index, rc.right - rc.left, rc.bottom - rc.top);
}
break;
case WM_GETMINMAXINFO:
if (lParam)
{
auto info = reinterpret_cast<MINMAXINFO*>(lParam);
info->ptMinTrackSize.x = 320;
info->ptMinTrackSize.y = 200;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case ... mouse cases
Mouse::ProcessMessage(message, wParam, lParam);
break;
case WM_KEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
Keyboard::ProcessMessage(message, wParam, lParam);
break;
case WM_MENUCHAR:
return MAKELRESULT(0, MNC_CLOSE);
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
// Windows procedure x3
LRESULT CALLBACK WndProcWorld1(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return WndProcCommon(kGameWindow1, hWnd, message, wParam, lParam);
}
LRESULT CALLBACK WndProcWorld2(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return WndProcCommon(kGameWindow2, hWnd, message, wParam, lParam);
}
LRESULT CALLBACK WndProcWorld3(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return WndProcCommon(kGameWindow3, hWnd, message, wParam, lParam);
}
// Exit helper
void ExitGame() noexcept
{
PostQuitMessage(0);
}
Galaxies.cpp
//
// Galaxies.cpp
//
#include ...
// Global variables
std::unique_ptr<DX::StepTimer> g_timer_p;
std::unique_ptr<DX::DeviceResources> g_deviceResources_p;
std::unique_ptr<DirectX::CommonStates> g_states_p;
std::unique_ptr<padInput> g_input_p;
std::unique_ptr<Camera> g_camera_p[2];
std::unique_ptr<HudText> g_text_p;
Galaxies::Galaxies() noexcept(false)
{
g_deviceResources_p = std::make_unique<DX::DeviceResources>(DXGI_FORMAT_B8G8R8A8_UNORM_SRGB);
g_deviceResources_p->RegisterDeviceNotify(this);
}
void Galaxies::Initialize(UINT index, HWND window, int width, int height)
{
static int once = true;
if (once)
{
g_deviceResources_p->CreateDeviceResources();
once = false;
}
switch (index)
{
case kGameWindow1:
{
g_timer_p = std::make_unique<DX::StepTimer>();
g_input_p = std::make_unique<padInput>();
g_camera_p[0] = std::make_unique<Camera>();
g_camera_p[1] = std::make_unique<Camera>();
g_text_p = std::make_unique<HudText>();
// PER OBJECT INITIALIZE
g_camera_p[0]->Initialize(0);
g_camera_p[1]->Initialize(1);
g_input_p ->Initialize();
g_text_p ->Initialize();
g_deviceResources_p->SetWindow(index, window, width, height);
g_deviceResources_p->CreateWindowSizeDependentResources(index);
OnDeviceRestored();
break;
}
case kGameWindow2:
case kGameWindow3:
default:
{
g_deviceResources_p->SetWindow(index, window, width, height);
g_deviceResources_p->CreateWindowSizeDependentResources(index);
break;
}
}
}
void Galaxies::GetDefaultWindowSize(int& width, int& height) const noexcept
{
...
}
void Galaxies::OnDeviceLost()
{
g_states_p.reset();
m_spPerFrameConstantBuffer.Reset();
g_text_p->OnDeviceLost();
}
void Galaxies::OnDeviceRestored()
{
CreateDeviceDependentResources();
CreateWindowSizeDependentResources(0);
CreateWindowSizeDependentResources(1);
WindowSizeChangeConfirmed(0);
WindowSizeChangeConfirmed(1);
g_text_p->OnDeviceRestored();
}
void Galaxies::OnSuspending()
{
}
void Galaxies::OnResuming()
{
g_timer_p->ResetElapsedTime();
}
void Galaxies::OnWindowMoved(UINT index)
{
auto r = g_deviceResources_p->GetOutputSize(index);
g_deviceResources_p->WindowSizeChanged(index, r.width, r.height);
}
// Pass in CURRENT (Last known) window size (icluding frame), so we can compare that with current size
// as read in deviceResources.
void Galaxies::OnWindowSizeChanged(UINT index, int width, int height)
{
// returns false if size unchanged
if (g_deviceResources_p->WindowSizeChanged(index, width, height))
{
CreateWindowSizeDependentResources(0);
WindowSizeChangeConfirmed(index);
}
}
void Galaxies::Tick()
{
UpdateRenderGPUTextures();
g_timer_p->Tick([&]()
{
UpdateInput();
UpdateCameras();
UpdateWorld();
});
RenderWorld(0);
if (!g_deviceResources_p->isWindowMinimized(1))
{
RenderWorld(1);
}
}
void Galaxies::Clear(int index)
{
// Clear the views.
auto context = g_deviceResources_p->GetD3DDeviceContext();
auto renderTargetView = g_deviceResources_p->GetRenderTargetView(index);
auto depthStencilView = g_deviceResources_p->GetDepthStencilView(index);
context->ClearRenderTargetView(renderTargetView, Colors::Black);
context->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
}
void Galaxies::CreateDeviceDependentResources()
{
auto device = g_deviceResources_p->GetD3DDevice();
g_states_p = std::make_unique<CommonStates>(device);
// Create constant buffers.
D3D11_BUFFER_DESC bufferDesc = {};
bufferDesc.Usage = D3D11_USAGE_DYNAMIC;
bufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
bufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
bufferDesc.MiscFlags = 0;
bufferDesc.ByteWidth = sizeof(PerFrameConstantBuffer);
DX::ThrowIfFailed(device->CreateBuffer(&bufferDesc, nullptr, m_spPerFrameConstantBuffer.ReleaseAndGetAddressOf()));
}
// Allocate all memory resources that change on a window SizeChanged event.
void Galaxies::CreateWindowSizeDependentResources(UINT index)
{
// TODO: Initialize windows-size dependent objects here.
}
void Galaxies::UpdateRenderGPUTextures()
{
// Don't try to render anything before the first Update.
if (g_timer_p->GetFrameCount() == 0)
{
return;
}
}
void Galaxies::UpdateInput()
{
g_input_p->Update();
if (g_input_p->isKeyDownEdge(escape))
{
ExitGame();
}
}
void Galaxies::UpdateCameras()
{
g_camera_p[0]->Update();
// g_camera_p[1]->Update();
}
// Updates the world.
void Galaxies::UpdateWorld()
{
}
void Galaxies::RenderWorld(int index) // index 0, 1. not 2 (not edit window)
{
if (g_timer_p->GetFrameCount() == 0)
{
return;
}
Clear(index);
// Update Constants Buffer with per-frame variables.
// Though I now do this up to 3 times per frame - once per window.
auto context = g_deviceResources_p->GetD3DDeviceContext();
auto d3dBuffer = m_spPerFrameConstantBuffer.Get();
// Exclude 3rd window for now
if (index != 2)
{ // PRE-LOCK
MapGuard mappedResource(context, d3dBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0);
PerFrameConstantBuffer* data = reinterpret_cast<PerFrameConstantBuffer*>(mappedResource.get());
g_camera_p[index]->GetPerFrameData(data);
data->frameTime = g_timer_p->GetElapsedSeconds();
if (g_input_p->isButtonToggleSet(leftShoulder))
{
data->frameTime *= 5;
}
data->totalTime = g_timer_p->GetTotalSeconds();
int fps = (int)g_timer_p->GetFramesPerSecond();
char buffer[TEXT_NUM_OF_CHAR];
memset(buffer, 127, sizeof(buffer));
if (index == 0)
{
snprintf(&buffer[0 * TEXT_NUM_CHAR_PER_ROW], TEXT_NUM_CHAR_PER_ROW + 1, "Window 1 ");
}
else
{
snprintf(&buffer[0 * TEXT_NUM_CHAR_PER_ROW], TEXT_NUM_CHAR_PER_ROW + 1, "Window 2 ");
}
snprintf(&buffer[TEXT_NUM_CHAR_PER_ROW], TEXT_NUM_CHAR_PER_ROW + 1, "Galaxies V0.01 ");
snprintf(&buffer[2 * TEXT_NUM_CHAR_PER_ROW], TEXT_NUM_CHAR_PER_ROW + 1, " FPS = %d ", fps);
for (int i = 0; i < TEXT_NUM_OF_CHAR; i++)
{
data->alpha[4 * i] = buffer[i] - 0x20; // 0x20 is the first ascii char in our texture atlas. It is " "
}
} // UNLOCK (in mappedResource destructor)
context->VSSetConstantBuffers(CONSTANTS_PER_FRAME, 1, &d3dBuffer);
context->PSSetConstantBuffers(CONSTANTS_PER_FRAME, 1, &d3dBuffer);
auto renderTargetView = g_deviceResources_p->GetRenderTargetView(index);
auto depthStencilView = g_deviceResources_p->GetDepthStencilView(index);
context->OMSetRenderTargets(1, &renderTargetView, depthStencilView);
// Set the viewport.
auto viewport = g_deviceResources_p->GetScreenViewport(index);
context->RSSetViewports(1, &viewport);
if (index != 2)
{
g_text_p->Render();
}
g_deviceResources_p->Present(index);
}
void Galaxies::WindowSizeChangeConfirmed(UINT index)
{
auto size = g_deviceResources_p->GetOutputSize(index);
switch (index)
{
case (kGameWindow1):
g_camera_p[0]->OnWindowSizeChanged(size.width, size.height);
g_text_p ->OnWindowSizeChanged(size.width, size.height);
break;
case (kGameWindow2):
g_camera_p[1]->OnWindowSizeChanged(size.width, size.height);
break;
case (kGameWindow3):
break;
default:
break;
}
}
DeviceResources.cpp
//
// DeviceResources.cpp
//
#include ...
using namespace DirectX;
using namespace DX;
using Microsoft::WRL::ComPtr;
DeviceResources::DeviceResources(
DXGI_FORMAT backBufferFormat,
DXGI_FORMAT depthBufferFormat,
UINT backBufferCount,
D3D_FEATURE_LEVEL minFeatureLevel,
unsigned int flags) noexcept :
m_backBufferFormat{ backBufferFormat },
m_depthBufferFormat{ depthBufferFormat },
m_backBufferCount{backBufferCount},
m_d3dMinFeatureLevel(minFeatureLevel),
m_window(),
m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1),
m_colorSpace(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709),
m_options(flags | c_FlipPresent | c_AllowTearing),
m_deviceNotify(nullptr)
{
m_windowCount = 0;
for (int index = 0; index < c_MaxWindows; index++)
{
m_window[index] = nullptr;
m_WindowStruct[index] = WindowStruct();
m_screenViewport[index] = { 100, 100, 800, 600, 0, 1 };
}
}
void DeviceResources::CreateDeviceResources()
{
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
CreateFactory();
...
// Determine feature levels
...
ComPtr<IDXGIAdapter1> adapter;
GetHardwareAdapter(adapter.GetAddressOf());
// Create the Direct3D 11 API device / context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
HRESULT hr = E_FAIL;
if (adapter)
{
hr = D3D11CreateDevice(
adapter.Get(),
D3D_DRIVER_TYPE_UNKNOWN,
nullptr,
creationFlags,
s_featureLevels,
featLevelCount,
D3D11_SDK_VERSION,
device.GetAddressOf(),
&m_d3dFeatureLevel,
context.GetAddressOf()
);
}
if (FAILED(hr))
{
// fall back to WARP device.
...
}
ThrowIfFailed(hr);
ThrowIfFailed(device.As(&m_d3dDevice));
ThrowIfFailed(context.As(&m_d3dContext));
}
// recreate every time the window size is changed.
void DeviceResources::CreateWindowSizeDependentResources(UINT index)
{
if (!m_window[index])
{
throw std::exception("need valid window handle");
}
// Clear the previous window size specific context.
ID3D11RenderTargetView* nullViews[] = { nullptr };
m_d3dContext->OMSetRenderTargets(_countof(nullViews), nullViews, nullptr);
m_d3dRenderTargetView[index].Reset();
m_d3dDepthStencilView[index].Reset();
m_renderTargetTexture[index].Reset();
m_depthStencilTexture[index].Reset();
m_d3dContext->Flush();
// Determine the render target size in pixels.
const UINT backBufferWidth = std::max<UINT>(static_cast<UINT>(m_WindowStruct[index].currentPos.width), 1u);
const UINT backBufferHeight = std::max<UINT>(static_cast<UINT>(m_WindowStruct[index].currentPos.height), 1u);
const DXGI_FORMAT backBufferFormat = (m_options & (c_FlipPresent | c_AllowTearing | c_EnableHDR)) ? NoSRGB(m_backBufferFormat) : m_backBufferFormat;
if (m_swapChain[index])
{
// If the swap chain already exists, resize it.
HRESULT hr = m_swapChain[index]->ResizeBuffers(
m_backBufferCount,
backBufferWidth,
backBufferHeight,
backBufferFormat,
(m_options & c_AllowTearing) ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0u
);
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
{
...
}
else
{
ThrowIfFailed(hr);
}
}
else
{
// Create a descriptor for the swap chain.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = backBufferWidth;
swapChainDesc.Height = backBufferHeight;
swapChainDesc.Format = backBufferFormat;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = m_backBufferCount;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = (m_options & (c_FlipPresent | c_AllowTearing | c_EnableHDR)) ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
swapChainDesc.Flags = (m_options & c_AllowTearing) ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0u;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fsSwapChainDesc = {};
fsSwapChainDesc.Windowed = TRUE;
// Create a SwapChain
ThrowIfFailed(m_dxgiFactory->CreateSwapChainForHwnd(
m_d3dDevice.Get(),
m_window[index],
&swapChainDesc,
&fsSwapChainDesc,
nullptr, m_swapChain[index].ReleaseAndGetAddressOf()
));
ThrowIfFailed(m_dxgiFactory->MakeWindowAssociation(m_window[index], DXGI_MWA_NO_ALT_ENTER));
}
// Create a render target view of the swap chain back buffer.
ThrowIfFailed(m_swapChain[index]->GetBuffer(0, IID_PPV_ARGS(m_renderTargetTexture[index].ReleaseAndGetAddressOf())));
CD3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc(D3D11_RTV_DIMENSION_TEXTURE2D, m_backBufferFormat);
ThrowIfFailed(m_d3dDevice->CreateRenderTargetView(
m_renderTargetTexture[index].Get(),
&renderTargetViewDesc,
m_d3dRenderTargetView[index].ReleaseAndGetAddressOf()
));
if (m_depthBufferFormat != DXGI_FORMAT_UNKNOWN)
{
// Create a depth stencil view for use with 3D rendering if needed.
CD3D11_TEXTURE2D_DESC depthStencilDesc(
m_depthBufferFormat,
backBufferWidth,
backBufferHeight,
1,
1,
D3D11_BIND_DEPTH_STENCIL
);
ThrowIfFailed(m_d3dDevice->CreateTexture2D(
&depthStencilDesc,
nullptr,
m_depthStencilTexture[index].ReleaseAndGetAddressOf()
));
CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
ThrowIfFailed(m_d3dDevice->CreateDepthStencilView(
m_depthStencilTexture[index].Get(),
&depthStencilViewDesc,
m_d3dDepthStencilView[index].ReleaseAndGetAddressOf()
));
}
// Set the 3D rendering viewport
m_screenViewport[index] = CD3D11_VIEWPORT(
0.0f,
0.0f,
static_cast<float>(backBufferWidth),
static_cast<float>(backBufferHeight)
);
}
void DeviceResources::SetWindow(UINT index, HWND window, int width, int height) noexcept
{
m_window[index] = window;
int indent = 50 * (index + 1);
RECTxywh windowPos = { indent - getInvisableBorderWidth(), indent, width, height };
m_WindowStruct[index].setWindowedByWindowPos(windowPos, 0, true);
}
bool DeviceResources::WindowSizeChanged(UINT index, int width, int height)
{
RECTxywh newRc = { 0, 0, width, height };
RECTxywh oldRc = { 0, 0, m_WindowStruct[index].currentPos.width, m_WindowStruct[index].currentPos.height };
if (newRc == oldRc)
{
// i.e. size didnt actually change
return false;
}
m_WindowStruct[index].setWindowedByWindowPos(newRc, 0, false);
CreateWindowSizeDependentResources(index);
return true;
}
// Recreate all device resources
void DeviceResources::HandleDeviceLost(UINT index)
{
if (m_deviceNotify)
{
m_deviceNotify->OnDeviceLost();
}
m_d3dDepthStencilView[index].Reset();
m_d3dRenderTargetView[index].Reset();
m_renderTargetTexture[index].Reset();
m_depthStencilTexture[index].Reset();
m_swapChain[index].Reset();
m_d3dContext.Reset();
m_d3dDevice.Reset();
m_dxgiFactory.Reset();
CreateDeviceResources();
CreateWindowSizeDependentResources(index);
if (m_deviceNotify)
{
m_deviceNotify->OnDeviceRestored();
}
}
int DeviceResources::getNumberOfMonitors()
{
ComPtr<IDXGIAdapter1> adapter;
GetHardwareAdapter(adapter.GetAddressOf());
IDXGIOutput* output = NULL;
int count = 0;
for (int i = 0; DXGI_ERROR_NOT_FOUND != adapter->EnumOutputs(i, &output); ++i)
{
count++;
}
return count;
}
RECTxywh DeviceResources::getMonitorSize(int monitor)
{
RECT position = getMonitorPos(monitor);
RECTxywh size(0, 0, position.right - position.left, position.bottom - position.top);
return size;
}
// 1 based index into monitors
RECT DeviceResources::getMonitorPos(int monitor)
{
ComPtr<IDXGIAdapter1> adapter;
GetHardwareAdapter(adapter.GetAddressOf());
RECT pos = {};
IDXGIOutput* output = NULL;
int count = -1;
if ( DXGI_ERROR_NOT_FOUND != adapter->EnumOutputs(monitor, &output) )
{
DXGI_OUTPUT_DESC outputDesc;
HRESULT hr = output->GetDesc(&outputDesc);
pos = outputDesc.DesktopCoordinates;
}
return pos;
}
RECT DeviceResources::getWindowPos(int index)
{
RECT windowRect;
HWND window = m_window[index];
GetWindowRect(window, &windowRect);
return windowRect;
}
void DeviceResources::setWindowPosition(int index, RECTxywh pos)
{
HWND hWnd = m_window[index];
SetWindowPos(hWnd, HWND_TOP, pos.left, pos.top, pos.width, pos.height, SWP_FRAMECHANGED)
ShowWindow(hWnd, SW_SHOWNORMAL)
}
bool DeviceResources::isWindowMinimized(int index)
{
return IsIconic(m_window[index]);
}
// Present the contents of the swap chain to the screen.
void DeviceResources::Present(int index)
{
HRESULT hr = E_FAIL;
if (m_options & c_AllowTearing)
{
hr = m_swapChain[index]->Present(0, DXGI_PRESENT_ALLOW_TEARING);
Print(L"TRACE: Present Present %i \n", index);
}
else
{
hr = m_swapChain[index]->Present(1, 0);
}
m_d3dContext->DiscardView(m_d3dRenderTargetView[index].Get());
if (m_d3dDepthStencilView[index])
{
// Discard the contents of the depth stencil.
m_d3dContext->DiscardView(m_d3dDepthStencilView[index].Get());
}
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
{
HandleDeviceLost(index);
}
else
{
ThrowIfFailed(hr);
if (!m_dxgiFactory->IsCurrent())
{
CreateFactory();
}
}
}
void DeviceResources::CreateFactory()
{
ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(m_dxgiFactory.ReleaseAndGetAddressOf())));
}
void DeviceResources::GetHardwareAdapter(IDXGIAdapter1** ppAdapter)
{
...
}
On Windows 10 I have been experimenting with replacing the "Window snap" feature to work better with ultra wide monitors. While I have had no problem capturing the Windows Key+arrow cursors to handle the keyboard shortcut, I now want to detect when another application Window has been dragged to the top/right/left/bottom of the current monitor.
Current code:
#include <iostream>
#include <Windows.h>
HHOOK _hook_keyboard;
KBDLLHOOKSTRUCT kbdStruct;
CONST int HORIZONTAL_SLOTS = 4;
CONST int VERTICAL_SLOTS = 1;
// horizontalPosition/verticalPosition specifies which "slot" starting at 0 to place Window in
// horizontalSlots/verticalSlots specifies how many slots to divide the screen into
void MoveAndResizeActiveWindow(int horizontalPosition, int verticalPosition, int horizontalSlots, int verticalSlots)
{
// get work area on primary monitor
HWND currentWindow = GetForegroundWindow();
if (currentWindow != NULL)
{
HMONITOR currentMonitor = MonitorFromWindow(currentWindow, MONITOR_DEFAULTTONEAREST);
MONITORINFO monitorInfo;
monitorInfo.cbSize = sizeof(MONITORINFO);
if (GetMonitorInfo(currentMonitor, &monitorInfo))
{
long width = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
long height = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
long snappedWidth = width / horizontalSlots;
long snappedHeight = height / verticalSlots;
long snappedLeft = (snappedWidth * horizontalPosition) + monitorInfo.rcWork.left;
long snappedTop = (snappedHeight * verticalPosition) + monitorInfo.rcWork.top;
MoveWindow(currentWindow, snappedLeft, snappedTop, snappedWidth, snappedHeight, true);
}
}
}
LRESULT __stdcall HookCallbackKeyboard(int nCode, WPARAM wParam, LPARAM lParam)
{
BOOL bEatkeystroke = false;
short keyState;
if (nCode >= 0)
{
kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
switch (wParam)
{
case WM_KEYDOWN:
keyState = GetAsyncKeyState(VK_LWIN);
if (keyState)
{
switch (kbdStruct.vkCode)
{
case VK_LEFT:
bEatkeystroke = true;
break;
case VK_RIGHT:
bEatkeystroke = true;
break;
case VK_UP:
bEatkeystroke = true;
break;
case VK_DOWN:
bEatkeystroke = true;
break;
};
};
break;
case WM_KEYUP:
keyState = GetAsyncKeyState(VK_LWIN);
if (keyState)
{
switch (kbdStruct.vkCode)
{
case VK_LEFT:
MoveAndResizeActiveWindow(0, 0, 4, 1);
bEatkeystroke = true;
break;
case VK_RIGHT:
MoveAndResizeActiveWindow(3, 0, 4, 1);
bEatkeystroke = true;
break;
break;
case VK_UP:
MoveAndResizeActiveWindow(1, 0, 4, 1);
bEatkeystroke = true;
break;
case VK_DOWN:
MoveAndResizeActiveWindow(2, 0, 4, 1);
bEatkeystroke = true;
break;
};
}
break;
};
}
if (bEatkeystroke)
{
return 1;
}
else
{
return CallNextHookEx(_hook_keyboard, nCode, wParam, lParam);
}
}
void SetHook()
{
if (!(_hook_keyboard = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallbackKeyboard, NULL, 0)))
{
MessageBox(NULL, L"Failed to install hook on keyboard!", L"Error", MB_ICONERROR);
}
}
int main(int argc, char** argv[])
{
SetHook();
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
Any suggestions how to identify when Windows have been dragged to a particular location on the screen?
As per advice in replies to original question I have tried used SetWinEventHook with the following code, planning to restrict EVENT_MIN and EVENT_MAX once correct events to watch for worked out.
g_hook_winevent = SetWinEventHook(
EVENT_MIN, EVENT_MAX,
NULL, // Handle to DLL.
HandleWinEvent, // The callback.
0, 0, // Process and thread IDs of interest (0 = all)
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); // Flags.
}
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd,
LONG idObject, LONG idChild,
DWORD dwEventThread, DWORD dwmsEventTime)
{
// process event here
}
While this easily tracks start or end of a Windows move with EVENT_SYSTEM_MOVESIZESTART and EVENT_SYSTEM_MOVESIZEEND I can't see an event here that tracks the moving of Window prior to EVENT_SYSTEM_MOVESIZEEND.
While that will work if only good option, ideally I want to be able to detect Window location from start of EVENT_SYSTEM_MOVESIZESTART until EVENT_SYSTEM_MOVESIZEEND completes. Testing with notepad the only event getting raised during the move is EVENT_OBJECT_NAMECHANGE, which seems to constantly trigger during Window move, at least with Notepad. However based on description in documentation I'm not sure if this is suitable for my use case: "An object's Name property has changed. The system sends this event for the following user interface elements: check box, cursor, list-view control, push button, radio button, status bar control, tree view control, and window object. Server applications send this event for their accessible objects."
I'm using low level hooks.
I have made this class:
class Kayz {
static int VKEY;
static void (*funcDown)();
static void (*funcUp)();
static HHOOK TheHook;
static KBDLLHOOKSTRUCT TheHookStruct;
static LRESULT _stdcall HookCallback(int, WPARAM, LPARAM);
public:
bool SetHook(int VKey, void(*FunctionDown)(), void(*FunctionUp)()) {
if (VKey < 0x07) {
if (!(TheHook = SetWindowsHookEx(WH_MOUSE_LL, &HookCallback, NULL, 0))) {
return false;
}
}
else if(VKey > 0x07){
if (!(TheHook = SetWindowsHookEx(WH_KEYBOARD_LL, &HookCallback, NULL, 0))) {
return false;
}
}
VKEY = VKey; funcDown = FunctionDown; funcUp = FunctionUp;
return true;
}
void UnSetHook() {
UnhookWindowsHookEx(TheHook);
}
};
int Kayz::VKEY;
void(*Kayz::funcDown)();
void(*Kayz::funcUp)();
HHOOK Kayz::TheHook;
KBDLLHOOKSTRUCT Kayz::TheHookStruct;
LRESULT _stdcall Kayz::HookCallback(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0) {
if (wParam == WM_KEYDOWN) {
TheHookStruct = *((KBDLLHOOKSTRUCT*)lParam);
if (TheHookStruct.vkCode == VKEY) {
(*funcDown)();
}
}
else if (wParam == WM_KEYUP)
{
TheHookStruct = *((KBDLLHOOKSTRUCT*)lParam);
if (TheHookStruct.vkCode == VKEY) {
(*funcUp)();
}
}
}
return CallNextHookEx(TheHook, nCode, wParam, lParam);
}
All the functions I put into SetHook do is change a bool variable in the main program so I can know whether or not the key is pressed. It seems to me that it's the most optimal way because I don't have to check for the key's state every time I loop in the main program.
Now.
Using a blocking timer such as Sleep() in the main program will block the program, including
return CallNextHookEx(TheHook, nCode, wParam, lParam);
That means that, as this is a low level hook, every other program is only gonna get the input when sleep ends. So if I press a key while in notepad, it's only gonna get typed when sleep ends and the program loops again, if I type a lot, they're gonna get typed most likely one at a time.
The only thing I've seen that is able to "bypass" this is
while(GetMessage(&msgVar, NULL, 0, 0)){}
GetMessage never or rarely returns, so it doesn't take up any system resources or processing power. It doesn't block because while is waiting for it to return. So basically, it's not doing anything but it isn't blocking either.
I need to have a thread that is doing something similar to this. That thread will be receiving the key press "events" and executing the functions that change the variables in the main program.
But this is dirty. I don't like dirty.
So I'd very much like to know:
How can I achieve non-blocking nothing, consuming the least possible resources, in a clean way?
Thank you.
EDIT:
As you asked: I'm making a memory aimbot strictly for learning purposes.
I have now spent quite a bit of time reading about MsgWaitForMultipleObjectsEx, and apparently you can just null the first 2 parameters, which comes in handy.
I was also thinking of doing this the wrong way, I was going to make a thread for the program to "hold" and still receive the async input from the hooks(which is why I didn't want it to block), and then the other(always-running) thread would work based on the bools that the functions the hook called would change.
I've now realized that's a rather bad design, so I'm thinking of using MsgWaitForMultipleObjectsEx in the main program, and checking that bool with it, pausing or resuming the aimbot thread if needed.
I'm now beginning to understand what #HarryJohnston said about the spaghetti logic, because I've got to organize what the async hook functions do with what the code that comes after MsgWaitForMultipleObjectsEx does, and those seem some rather difficult decisions.
I want to follow these hooks and get a full understanding of how this can all work, which is why I won't be using raw input right away, though thank you #nikau6 for informing me about it, I'll surely look into it when I'm done with hooks.
Once again thank you everyone.
"It seems to me that it's the most optimal way because I don't have to check for the key's state every time I loop in the main program."
There's a better way than hooks, not well known, to monitor the keyboard events on all the system. This is Raw Input.
With raw inputs, your application get informed of each keyboard, mouse, etc.., event, straight from the HID (Human Device Interface) driver. This is more efficient than hooks, and very simple to use. Your application don't need to export a procedure from a DLL, and because raw inputs are not hooks, no message have to be passed to an another procedure, to a another thread, after it was treated. (see one of my comments below about the DefRawInputProc procedure). The application gets the raw input through the WM_INPUT message. Unlike hooks, a window must be created, that's an obligation, a handle is asked.
Here's how I use Raw Input :
EDIT : And you'll not get the problem you have about the non-blocking thread.
#include <Windows.h>
#define HID_ISMOUSE(x) ((x).header.dwType == RIM_MOUSE)
#define HID_ISKEYBOARD(x) ((x).header.dwType == RIM_TYPEKEYBOARD)
#define HID_SCODE(x) ((x).data.keyboard.MakeCode) // scan code
#define HID_VKEY(x) ((x).data.keyboard.VKey) // virtual key code
#define HID_WMSG(x) ((x).data.keyboard.Message) // corresponding window message, WM_KEYDOWN, WM_SYSKEYDOWN, WM_KEYUP, WM_SYSKEYUP.
#define HID_ISKEYUP(x) ((x).data.keyboard.Flags & RI_KEY_BREAK)
#define HID_ISKEYDOWN(x) (((x).data.keyboard.Flags & 0x01) == RI_KEY_MAKE)
#define RAWINPUT_ERROR (UINT)-1
namespace HID
{
const USHORT MOUSE = 2;
const USHORT KEYBOARD = 6;
// Register a raw input device
bool RegisterDevice(HWND hTarget, USHORT usage)
{
RAWINPUTDEVICE hid;
hid.usUsagePage = 1; // generic desktop page
hid.usUsage = usage; // device id
hid.hwndTarget = hTarget; // window handle
hid.dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK ; // RIDEV_INPUTSINK to monitor all the system, RIDEV_NOLEGACY if you don't want legacy keyboard events.
return !!RegisterRawInputDevices(&hid, 1, sizeof(RAWINPUTDEVICE));
}
// Unregister a raw input device.
void UnregisterDevice(USHORT usage)
{
RAWINPUTDEVICE hid;
hid.usUsagePage = 1;
hid.usUsage = usage;
hid.dwFlags = RIDEV_REMOVE; // RIDEV_REMOVE to remove a device.
hid.hwndTarget = NULL; // NULL to remove a device.
RegisterRawInputDevices(&hid, 1, sizeof(RAWINPUTDEVICE));
}
// Get raw input data
bool GetInputData(HRAWINPUT hInput, RAWINPUT* RawInput)
{
UINT size = sizeof(RAWINPUT); // size = 40
if( GetRawInputData((HRAWINPUT)hInput, RID_INPUT, RawInput, &size, sizeof(RAWINPUTHEADER)) != RAWINPUT_ERROR )
return true;
else
return false;
}
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR cmd_line, int cmd_show)
{
WNDCLASSW wc = {0};
wc.lpfnWndProc = WindowProc;
...
HWND hwnd = ::CreateWindowW(...);
...
HID::RegisterDevice(hwnd, HID::KEYBOARD);
MSG msg;
while(GetMessageW(&msg, NULL, 0, 0))
{
DispatchMessageW(&msg);
}
HID::UnregisterDevice(HID::KEYBOARD);
return (int)msg.wParam;
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if(msg == WM_INPUT) // Raw input message.
{
RAWINPUT Input;
if(HID::GetInputData((HRAWINPUT)lParam, &Input))
{
if(HID_ISKEYBOARD(Input))
{
if(HID_ISKEYUP(Input))
{
return 0;
}
else // if(HID_ISKEYDOWN(Input))
{
return 0;
}
}
}
}
return ::DefWindowProc(hWnd, msg, wParam, lParam);
}
you need use MsgWaitForMultipleObjectsEx in loop this is most power function for you. with this you will be wait for windows(and hooks) messages, for multiple events (up to 63) also you can receiver user mode APC calls and periodically (by timeout do same tasks). example:
void ZApp::Run()
{
for (;;)
{
HANDLE* pHandles;
DWORD nCount = GetWaitHandles(&pHandles);
DWORD r = MsgWaitForMultipleObjectsEx(nCount, pHandles, GetTimeout(), QS_ALLINPUT, MWMO_ALERTABLE);
if (r < nCount)
{
OnSignalObject(r);
continue;
}
if (r == nCount)
{
BOOL bIdle = FALSE;
MSG msg;
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
if (!bIdle)
{
bIdle = IsIdleMessage(msg.message);
}
if (PreTranslateMessage(&msg)) continue;
if (msg.message == WM_QUIT)
{
return ;
}
if (!IsDialogMessageEx(&msg))
{
if (msg.message - WM_KEYFIRST <= WM_KEYLAST - WM_KEYFIRST)
{
TranslateMessage(&msg);
}
DispatchMessage(&msg);
}
}
if (bIdle)
{
OnIdle();
}
continue;
}
if (r - WAIT_ABANDONED_0 < nCount)
{
OnAbandonedObject(r - WAIT_ABANDONED_0);
continue;
}
switch(r)
{
case WAIT_TIMEOUT:
OnTimeout();
break;
case WAIT_IO_COMPLETION:
OnApcAlert();
break;
default: __debugbreak();
}
}
}
I've realized that having a thread permanently "on hold" when waiting for hooks to execute other functions is just a bad way of doing what I was looking for, you should always have every thread doing something. If you're following the same path I suggest you get off of it and organize your code in a way you don't have to have these "loose ends".
Thanks everyone. Mainly #RbMm who informed me of MsgWaitForMultipleObjectsEx and guided me through it, and #nikau6 who informed about RawInput, which I'll be using in the future.
I've also finalized the class and included a function that returns when your key is either pressed or released(false when MsgWaitForMultipleObjectsEx returns anything other than WAIT_OBJECT_0), figured I'd post it here in case anyone ever needs it since most of the conversation was made in the comments and I often skip those when browsing stackoverflow.
class Kayz {
static bool KDown[2];
static int VKEY;
static void (*funcDown)();
static void (*funcUp)();
static HHOOK TheHook;
static KBDLLHOOKSTRUCT TheHookStruct;
static LRESULT _stdcall HookCallback(int, WPARAM, LPARAM);
public:
bool SetHook(int VKey, void(*FunctionDown)(), void(*FunctionUp)()) {
if (VKey < 0x07) {
if (!(TheHook = SetWindowsHookEx(WH_MOUSE_LL, &HookCallback, NULL, 0))) {
return false;
}
}
else if(VKey > 0x07){
if (!(TheHook = SetWindowsHookEx(WH_KEYBOARD_LL, &HookCallback, NULL, 0))) {
return false;
}
}
VKEY = VKey; funcDown = FunctionDown; funcUp = FunctionUp;
return true;
}
void UnSetHook() {
UnhookWindowsHookEx(TheHook);
}
bool WaitOnKey()
{
MSG msg;
while (true) {
if (MsgWaitForMultipleObjectsEx(0, 0, INFINITE, QS_ALLINPUT, 0) == WAIT_OBJECT_0) {
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
if (msg.message != WM_QUIT) return false;
TranslateMessage(&msg); DispatchMessage(&msg);
}
if(KDown[0] == 0 && KDown[1] == 0){
continue;
}else if (KDown[0] == true) {
return true;
}else{
KDown[1] = false;
return true;
}
} else {
return false;
}
}
}
};
bool Kayz::KDown[2];
int Kayz::VKEY;
void(*Kayz::funcDown)();
void(*Kayz::funcUp)();
HHOOK Kayz::TheHook;
KBDLLHOOKSTRUCT Kayz::TheHookStruct;
LRESULT _stdcall Kayz::HookCallback(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0) {
if (wParam == WM_KEYDOWN) {
TheHookStruct = *((KBDLLHOOKSTRUCT*)lParam);
if (TheHookStruct.vkCode == VKEY) {
KDown[0] = true;
(*funcDown)();
}
}
else if (wParam == WM_KEYUP)
{
TheHookStruct = *((KBDLLHOOKSTRUCT*)lParam);
if (TheHookStruct.vkCode == VKEY) {
KDown[1] = true;
KDown[0] = false;
(*funcUp)();
}
}
}
return CallNextHookEx(TheHook, nCode, wParam, lParam);
}
I am trying to add a bitmap image(.bmp) to background of a main Dialog in visual studio c++. I created a close button which normally works well but after putting background image and when I press close button I get this error:
Debug assertion failed.
File:c:\program files(x86)\microsoft visual
studio12.0\vc\atlmfc\include\atlwin.h
Line:3604 Expression:m_hWnd==0
For information on how your program can cause an assertion failure,
see the Visual C++ documantation an asserts.
Abort Retry Ignore
For loading bitmap image a class defined.
Header:
#if !defined(AFX_PICTUREWINDOW_H__44323373_9E89_11D3_A393_00C0DFC59237__INCLUDED_)
#define AFX_PICTUREWINDOW_H__44323373_9E89_11D3_A393_00C0DFC59237__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <atlbase.h>
#include <atlwin.h>
class CPictureWindow : public CWindowImpl<CPictureWindow>
{
public:
typedef enum HandlerTypeEnum
{
ClientPaint = WM_PAINT,
BackGroundPaint = WM_ERASEBKGND
} HandlerTypeEnum;
CPictureWindow()
{
m_nMessageHandler = ClientPaint;
};
virtual ~CPictureWindow()
{
};
BEGIN_MSG_MAP(CPictureWindow)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkGnd)
END_MSG_MAP()
virtual BOOL Load(LPCTSTR szFileName)
{
BOOL bResult = FALSE;
Close();
if (szFileName)
{
OFSTRUCT of;
HANDLE hFile = NULL;;
if ((hFile = (HANDLE)OpenFile((LPCSTR)szFileName, &of, OF_READ | OF_SHARE_COMPAT)) != (HANDLE)HFILE_ERROR)
{
DWORD dwHighWord = NULL, dwSizeLow = GetFileSize(hFile, &dwHighWord);
DWORD dwFileSize = dwSizeLow;
HRESULT hResult = NULL;
if (HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize))
if (void* pvData = GlobalLock(hGlobal))
{
DWORD dwReadBytes = NULL;
BOOL bRead = ReadFile(hFile, pvData, dwFileSize, &dwReadBytes, NULL);
GlobalUnlock(hGlobal);
if (bRead)
{
CComPtr<IStream> spStream;
_ASSERTE(dwFileSize == dwReadBytes);
if (SUCCEEDED(CreateStreamOnHGlobal(hGlobal, TRUE, &spStream)))
if (SUCCEEDED(hResult = OleLoadPicture(spStream, 0, FALSE, IID_IPicture, (void**)&m_spPicture)))
bResult = TRUE;
}
}
CloseHandle(hFile);
}
}
Invalidate();
return bResult;
}
HandlerTypeEnum m_nMessageHandler;
protected:
inline virtual BOOL IsHandlerMessage(UINT uMsg)
{
return m_nMessageHandler == (HandlerTypeEnum)uMsg;
}
void PutPicture(IPicture* pPicture, HDC hDC, RECT rPicture)
{
OLE_XSIZE_HIMETRIC nWidth = NULL; OLE_YSIZE_HIMETRIC nHeight = NULL;
pPicture->get_Width(&nWidth); pPicture->get_Height(&nHeight);
pPicture->Render(hDC, rPicture.left, rPicture.top, rPicture.right - rPicture.left, rPicture.bottom - rPicture.top, 0, nHeight, nWidth, -nHeight, NULL);
};
LRESULT OnEraseBkGnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (IsHandlerMessage(uMsg))
{
if (m_spPicture)
{
BeginPaint(NULL);
RECT r; GetClientRect(&r);
HDC hDC = GetDC();
HWND hWndChild = GetWindow(GW_CHILD);
while (::IsWindow(hWndChild))
{
if (::IsWindowVisible(hWndChild))
{
RECT rChild; ::GetWindowRect(hWndChild, &rChild);
ScreenToClient(&rChild);
ExcludeClipRect(hDC, rChild.left, rChild.top, rChild.right, rChild.bottom);
}
hWndChild = ::GetWindow(hWndChild, GW_HWNDNEXT);
}
PutPicture(m_spPicture, hDC, r);
ReleaseDC(hDC);
EndPaint(NULL);
return TRUE;
}
}
bHandled = FALSE;
return FALSE;
};
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (IsHandlerMessage(uMsg))
{
if (m_spPicture)
{
BeginPaint(NULL);
RECT r; GetClientRect(&r);
HDC hDC = GetDC();
PutPicture(m_spPicture, hDC, r);
ReleaseDC(hDC);
EndPaint(NULL);
}
}
bHandled = FALSE;
return NULL;
};
void Close()
{
m_spPicture = NULL;
}
//Attributes
CComPtr<IPicture> m_spPicture;
};
#endif
and in DoDataExchange of .Cpp file:
objPictureWindow.SubclassWindow(m_hWnd);
objPictureWindow.m_nMessageHandler = CPictureWindow::BackGroundPaint;
objPictureWindow.Load((LPCTSTR)"D:\\bitmap2.bmp");
Size of this bitmap is about 2Mb. However I resized it to 500Kb but again I had the same error after pressing the close button to close this dialog.
By following this error compiler shows this line to me:
template <class TBase, class TWinTraits>
BOOL CWindowImplBaseT< TBase, TWinTraits >::SubclassWindow(_In_ HWND hWnd)
{
ATLASSUME(m_hWnd == NULL);
ATLASSERT(::IsWindow(hWnd));
I'm using rawinput with directx...i'm trying to zoom with the camera when mouse wheel is used...when I run the program with the following code, the data I get from rawinput for the usbuttondata goes to 120 when I push mouse wheel forward...then it goes out of control...up to 65000...I thought the data was supposed to be 1 or -1 or 0...what does rawinput send as the mouse wheel data?
code:
LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg,
WPARAM wParam, LPARAM lParam)
{
switch(Msg)
{
case WM_CREATE:
{
RAWINPUTDEVICE Rid[2];
// Keyboard
Rid[0].usUsagePage = 1;
Rid[0].usUsage = 6;
Rid[0].dwFlags = 0;
Rid[0].hwndTarget=Inst.Wnd.hWnd;
// Mouse
Rid[1].usUsagePage = 1;
Rid[1].usUsage = 2;
Rid[1].dwFlags = 0;
Rid[1].hwndTarget=Inst.Wnd.hWnd;
if (!RegisterRawInputDevices(Rid,2,sizeof(RAWINPUTDEVICE)))
{
MessageBox(NULL, L"Failed to Register Input Devices!", L"ALERT", MB_OK);
exit(1);
}
return 0;
}
case WM_INPUT:
{
// Determine how big the buffer should be
UINT iBuffer;
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &iBuffer,
sizeof(RAWINPUTHEADER));
LPBYTE lpb = new BYTE[iBuffer];
if (lpb == NULL)
{
return 0;
}
UINT readSize = GetRawInputData( (HRAWINPUT)lParam, RID_INPUT, lpb, &iBuffer, sizeof(RAWINPUTHEADER) ) ;
if( readSize != iBuffer )
puts( "ERROR: GetRawInputData didn't return correct size!" ) ;
RAWINPUT *raw = (RAWINPUT*) lpb;
if (raw->header.dwType== RIM_TYPEMOUSE)
{
riProcessMouseMessage(&raw->data.mouse);
}
if (raw->header.dwType== RIM_TYPEKEYBOARD)
{
//riProcessKeyboardMessage(&raw->data.keyboard);
}
}
return 0;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDM_FILE_NEW:
{
// Create the game object
pGame = new CGame(dxMgr.getD3DDevice());
// Initialize the game object
if (!pGame->init(Inst.Wnd.hWnd))
return 0;
break;
}
case IDM_FILE_OPEN:
pGame->m_animCollection->LoadXFile("oxana.x", 0);
//objects.CreateNewObject(1, L"oxana.x", NULL);
break;
case IDM_FILE_SAVE:
break;
case IDM_FILE_SAVEAS:
break;
case IDM_FILE_EXIT:
PostQuitMessage(WM_QUIT);
break;
}
return 0;
case WM_DESTROY:
PostQuitMessage(WM_QUIT);
return 0;
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return TRUE;
}
void riProcessMouseMessage( const RAWMOUSE* rmouse )
{
if(pGame != NULL)
{
//MessageBox(NULL, L"Game Found", L"SUCCESS", MB_OK);
if ( MOUSE_MOVE_RELATIVE == rmouse->usFlags )
{
riMgr.mxr = &rmouse->lLastX;
riMgr.myr = &rmouse->lLastY;
}
riMgr.mzr = (RI_MOUSE_WHEEL & rmouse->usButtonFlags) ? &rmouse->usButtonData : 0;
}
}
I suspect it is the same as WM_MOUSEWHEEL:
The high-order word indicates the distance the wheel is rotated, expressed in multiples or divisions of WHEEL_DELTA, which is 120. A positive value indicates that the wheel was rotated forward, away from the user; a negative value indicates that the wheel was rotated backward, toward the user.
The low-order word indicates whether various virtual keys are down.
Therefore you need to extract the high order word. You need to take care to handle negative values correctly. You probably don't as you get large values instead.
If you want you can use the following macro for this: GET_WHEEL_DELTA_WPARAM(wParam)
Add the following in the switch statment
case WM_MOUSEWHEEL:
{
int delta = GET_WHEEL_DELTA_WPARAM(wparam);
if(delta > 0)
{
//Mouse Wheel Up
}
else
{
//Mouse Wheel Down
}
return 0;
}