I am using ISpVoice to speak an input string. Now, even though I use SPF_ASYNC and SPF_PURGEBEFORESPEAK tags in the Speak method, the tts doesn't stop whenever Pause is called instead it continues until the tts finishes a word.
Here's how I do it:
void speakSentence()
{
pVoice->Pause();
pVoice->Speak(L"This is a sentence.", SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
pVoice->Resume();
}
Whenever I try to call this function at the middle of the word "sentence", the tts doesn't pause and instead continues to say the word until the end.
From microsoft documentation:
ISpVoice::Pause pauses the voice at the nearest alert boundary and closes the output device, allowing access to pending speak requests from other voices.
I tried changing the alert boundary by:
pVoice->SetAlertBoundary(SPEI_PHONEME);
and it doesn't work.
There is NVDA Screen Reader that solved this problem but I don't know how they did it.
Is there anyway to solve my problem?
EDIT:
Here's my full code. I am creating a small screen reader program that uses both UIAutomation and MSAA.
The program may somewhat unstable when comparing UI objects but most times it works.
screeenreader.h:
#ifndef _SCREENREADER_H_
#define _SCREENREADER_H_
#define WIN32_LEAN_AND_MEAN
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <memory>
#include "speechsynthesis.h"
#include "uiautomator.h"
class ScreenReader
{
public:
explicit ScreenReader(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdShow);
virtual ~ScreenReader();
LRESULT CALLBACK MessageHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int Exec();
private:
void InitializeWindows();
void InitRawInputDevices();
bool IsMouseMove();
private:
LPCWSTR m_applicationName;
HINSTANCE m_hInstance;
HINSTANCE m_hPrevInstance;
PSTR m_pScmdline;
int m_iCmdShow;
HWND m_hWnd;
SpeechSynthesis *m_pSpeech;
UIAutomator *m_pAutomator;
RAWINPUTDEVICE rid[2];
LONG m_prevMouseX;
LONG m_prevMouseY;
BSTR currItem;
};
static ScreenReader *application;
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
#endif
screenreader.cpp: In this part I called ISpVoice at the messageloop section. At ScreenReader::MessageHandler() function at IsMouseMove condition.
#include "screenreader.h"
ScreenReader::ScreenReader(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdShow)
{
CoInitialize(NULL);
m_pSpeech = new SpeechSynthesis;
m_pAutomator = new UIAutomator;
// Get current Cursor position.
POINT pt;
GetCursorPos(&pt);
m_prevMouseX = pt.x;
m_prevMouseY = pt.y;
// Notify user the program is loading.
m_pSpeech->Speak(L"Loading Rescan. Please wait.", SPF_DEFAULT, NULL);
m_hInstance = hInstance;
m_hPrevInstance = hPrevInstance;
m_pScmdline = pScmdline;
m_iCmdShow = iCmdShow;
application = this;
InitializeWindows();
InitRawInputDevices();
}
ScreenReader::~ScreenReader()
{
if (m_pSpeech != nullptr)
{
delete m_pSpeech;
m_pSpeech = nullptr;
}
if (m_pAutomator != nullptr)
{
delete m_pAutomator;
m_pAutomator = nullptr;
}
if (currItem != NULL)
{
SysFreeString(currItem);
currItem = NULL;
}
CoUninitialize();
}
LRESULT CALLBACK ScreenReader::MessageHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INPUT:
{
UINT dwSize;
GetRawInputData(
(HRAWINPUT)lParam,
RID_INPUT,
NULL,
&dwSize,
sizeof(RAWINPUTHEADER)
);
std::unique_ptr<BYTE[]> lpb(new BYTE[dwSize]);
if (!lpb)
return 0;
if (GetRawInputData(
(HRAWINPUT)lParam,
RID_INPUT,
lpb.get(),
&dwSize,
sizeof(RAWINPUTHEADER)
) != dwSize)
OutputDebugString(L"GetRawInputData does not return correct size!\n");
RAWINPUT *raw = (RAWINPUT*)lpb.get();
if (raw->header.dwType == RIM_TYPEKEYBOARD)
{
UINT mess = raw->data.keyboard.Message;
UINT vKey = raw->data.keyboard.VKey;
if (mess == WM_KEYDOWN)
{
}
}
else if (raw->header.dwType == RIM_TYPEMOUSE)
{
if (IsMouseMove())
{
BSTR item;
HRESULT hr = m_pAutomator->GetUIAutomationItemNameAtMousePoint(&item);
if (item == NULL)
return 0;
if (currItem == NULL)
currItem = SysAllocString(item);
if (wcscmp(currItem, item) != 0)
{
m_pSpeech->Stop();
m_pSpeech->Speak(item);
if (currItem != NULL)
SysFreeString(currItem);
currItem = SysAllocString(item);
}
SysFreeString(item);
}
}
}
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
int ScreenReader::Exec()
{
MSG msg;
ShowWindow(m_hWnd, m_iCmdShow);
// Tell the user that the program is ready.
m_pSpeech->Speak(L"Rescan ready.", SPF_PURGEBEFORESPEAK);
// The message loop
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
void ScreenReader::InitializeWindows()
{
// Create Window class.
WNDCLASSEX wc;
m_applicationName = L"Rescan Screen Reader";
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = m_hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = m_applicationName;
wc.hIconSm = wc.hIcon;
// Register the window class.
RegisterClassEx(&wc);
m_hWnd = CreateWindowEx(
WS_EX_OVERLAPPEDWINDOW,
m_applicationName,
L"Rescan Screen Reader",
WS_CAPTION | WS_MINIMIZEBOX | WS_OVERLAPPED | WS_SYSMENU,
(GetSystemMetrics(SM_CXSCREEN) - 500) / 2,
(GetSystemMetrics(SM_CYSCREEN) - 300) / 2,
500,
300,
NULL,
NULL,
m_hInstance,
NULL
);
}
void ScreenReader::InitRawInputDevices()
{
// Initialize Keyboard
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = m_hWnd;
// Initialize Mouse
rid[1].usUsagePage = 0x01;
rid[1].usUsage = 0x02;
rid[1].dwFlags = RIDEV_INPUTSINK;
rid[1].hwndTarget = m_hWnd;
// Register RIDs
RegisterRawInputDevices(rid, 2, sizeof(RAWINPUTDEVICE));
}
bool ScreenReader::IsMouseMove()
{
POINT pt;
GetCursorPos(&pt);
bool result = !(m_prevMouseX == pt.x && m_prevMouseY == pt.y);
m_prevMouseX = pt.x;
m_prevMouseY = pt.y;
return result;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_QUIT:
PostQuitMessage(0);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return application->MessageHandler(hWnd, message, wParam, lParam);
}
}
I wrapped ISpVoice into the SpeechSynthesis class.
speechsynthesis.h:
#ifndef _SPEECHSYNTHESIS_H_
#define _SPEECHSYNTHESIS_H_
#pragma warning(disable : 4996)
#define SPCAT_VOICES_ONECORE L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech_OneCore\\Voices"
#include <sapi.h>
#include <sphelper.h>
#include <atlbase.h>
class SpeechSynthesis
{
public:
SpeechSynthesis();
~SpeechSynthesis();
HRESULT Speak(LPCWSTR pwcs, DWORD dwFlags = SPF_PURGEBEFORESPEAK | SPF_ASYNC | SPF_IS_NOT_XML, ULONG *pulStreamNumber = NULL);
HRESULT Resume();
HRESULT Pause();
HRESULT Stop();
ISpVoice* getVoice();
private:
CComPtr<ISpObjectToken> cpVoiceToken;
CComPtr<IEnumSpObjectTokens> cpEnum;
ISpVoice* pVoice;
ULONG count;
};
#endif
speechsynthesis.cpp:
#include "speechsynthesis.h"
SpeechSynthesis::SpeechSynthesis()
{
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
if (SUCCEEDED(hr))
hr = SpEnumTokens(SPCAT_VOICES_ONECORE, NULL, NULL, &cpEnum);
if (SUCCEEDED(hr))
hr = cpEnum->GetCount(&count);
if (SUCCEEDED(hr))
{
cpEnum->Item(1, &cpVoiceToken);
pVoice->SetPriority(SPVPRIORITY::SPVPRI_ALERT);
pVoice->SetAlertBoundary(SPEI_PHONEME);
pVoice->SetOutput(NULL, TRUE);
pVoice->SetVoice(cpVoiceToken);
}
if (FAILED(hr))
{
MessageBox(NULL, "A fatal error has occured", "Error Message", MB_ABORTRETRYIGNORE);
}
}
SpeechSynthesis::~SpeechSynthesis()
{
pVoice->Release();
}
HRESULT SpeechSynthesis::Speak(LPCWSTR pwcs, DWORD dwFlags, ULONG *pulStreamNumber)
{
return pVoice->Speak(pwcs, dwFlags, pulStreamNumber);
}
HRESULT SpeechSynthesis::Resume()
{
return pVoice->Resume();
}
HRESULT SpeechSynthesis::Pause()
{
return pVoice->Pause();
}
HRESULT SpeechSynthesis::Stop()
{
return Speak(NULL);
}
ISpVoice * SpeechSynthesis::getVoice()
{
return pVoice;
}
uiautomator.h
#ifndef _UIAUTOMATOR_H_
#define _UIAUTOMATOR_H_
#include <windows.h>
#include <oleacc.h>
#include <uiautomation.h>
#pragma comment(lib, "oleacc.lib")
class UIAutomator
{
public:
UIAutomator();
~UIAutomator();
HRESULT GetItemNameAtMousePoint(BSTR *pStr);
HRESULT GetUIAutomationItemNameAtMousePoint(BSTR *pStr);
private:
HRESULT InitUIAutomation();
private:
IUIAutomation *m_automation;
};
#endif
uiautomator.cpp
#include "uiautomator.h"
UIAutomator::UIAutomator()
{
SetProcessDPIAware();
HRESULT hr = InitUIAutomation();
}
UIAutomator::~UIAutomator()
{
}
HRESULT UIAutomator::GetItemNameAtMousePoint(BSTR * pStr)
{
POINT pt;
GetPhysicalCursorPos(&pt);
VARIANT varItem;
IAccessible *pAcc;
HRESULT hr = AccessibleObjectFromPoint(pt, &pAcc, &varItem);
if (SUCCEEDED(hr))
{
hr = pAcc->get_accName(varItem, pStr);
VariantClear(&varItem);
pAcc->Release();
}
return hr;
}
HRESULT UIAutomator::GetUIAutomationItemNameAtMousePoint(BSTR * pStr)
{
CONTROLTYPEID id;
POINT pt;
IUIAutomationElement *elem;
VARIANT val;
GetCursorPos(&pt);
HRESULT hr = m_automation->ElementFromPoint(pt, &elem);
if (SUCCEEDED(hr))
{
hr = elem->get_CurrentControlType(&id);
if (SUCCEEDED(hr))
{
if (id == UIA_PaneControlTypeId)
GetItemNameAtMousePoint(pStr);
else if (id == UIA_EditControlTypeId)
{
hr = elem->GetCurrentPropertyValue(UIA_ValueValuePropertyId, &val);
if (SUCCEEDED(hr))
{
*pStr = SysAllocString(val.bstrVal);
VariantClear(&val);
}
}
else
{
hr = elem->get_CurrentName(pStr);
}
}
elem->Release();
}
return hr;
}
HRESULT UIAutomator::InitUIAutomation()
{
HRESULT hr = CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER,
__uuidof(IUIAutomation), (void**)&m_automation);
return hr;
}
main.cpp
#include "vld.h"
#include "screenreader.h"
#include <memory>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdShow)
{
std::unique_ptr<ScreenReader> app(new ScreenReader(hInstance, hPrevInstance, pScmdline, iCmdShow));
return app->Exec();
}
If you don't have time to compile here's the program.
If you launch it and hover the mouse on the program window there is a lag when highlighting minimize and close button. Also sometimes the tts doesn't stop immediately when you hover at another object.
Compare this one to NVDA Screen Reader. You will notice the big difference.
It works for me with or without setting SetAlertBoundary(SPEI_PHONEME).
The following is my test code, you can have a try.
HRESULT hr = ::CoInitialize(nullptr);
if (FAILED(hr))
{
return EXIT_FAILURE;
}
std::wstring text;
text = L"This is a sentence.";
CComPtr<ISpVoice> cpVoice;
// Create a SAPI voice
hr = cpVoice.CoCreateInstance(CLSID_SpVoice);
//cpVoice->SetAlertBoundary(SPEI_PHONEME);
// set the output to the default audio device
if (SUCCEEDED(hr))
{
hr = cpVoice->SetOutput(NULL, TRUE);
}
// Speak the text
if (SUCCEEDED(hr))
{
hr = cpVoice->Speak(text.c_str(), SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
}
text = L"The third type, a logarithm of the unsigned fold change, is undoubtedly the most tractable.";
Sleep(600);
hr = cpVoice->Pause();
hr = cpVoice->Resume();
hr = cpVoice->Speak(text.c_str(), SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
Sleep(10000);
::CoUninitialize();
if (SUCCEEDED(hr))
{
return EXIT_SUCCESS;
}
return EXIT_FAILURE;
I finally got it!
CComPtr<ISpAudio> audio;
CSpStreamFormat format;
format.AssignFormat(SPSF_11kHz8BitMono);
Initialize audio
SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOOUT, &audio);
Then, set its format and set it as output to pVoice.
audio->SetFormat(format.FormatId(), format.WaveFormatExPtr());
pVoice->SetOutput(audio, FALSE);
Now I have access to the audio stream!
Now to Immediately stop the audio, call:
audio->SetState(SPAS_STOP, 0);
Then speak again using:
audio->SetState(SPAS_RUN, 0);
pVoice->Speak(L"This is a sentence", SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
Related
I would like to know when the system language has changed in my application, even when the application is not active. So I created an implementation using ITfLanguageProfileNotifySink. But, ITfLanguageProfileNotifySink::OnLanguageChange seems to only be executing when the window is active. How can I have this execute when the window is not the top active window?
Sample Code
#include <windows.h>
#include <msctf.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
class NotifyMe : protected ITfLanguageProfileNotifySink {
public:
ITfSource *m_tfSource;
DWORD m_dwCookie;
void Init();
virtual HRESULT STDMETHODCALLTYPE OnLanguageChange(LANGID langid, __RPC__out BOOL *pfAccept);
virtual HRESULT STDMETHODCALLTYPE OnLanguageChanged();
// IUnknown implementation
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
ULONG m_ulRefCount; ///< COM object reference count
};
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if(hwnd == NULL)
{
return 0;
}
CoInitialize(nullptr);
NotifyMe notify;
notify.Init();
ShowWindow(hwnd, nCmdShow);
MSG msg = {};
while(GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
void NotifyMe::Init() {
m_tfSource = NULL;
ITfInputProcessorProfiles *pProfiles;
HRESULT hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER, IID_ITfInputProcessorProfiles, (LPVOID*)&pProfiles);
if(SUCCEEDED(hr)) {
hr = pProfiles->QueryInterface(IID_ITfSource, (LPVOID*)&m_tfSource);
if(SUCCEEDED(hr)) {
hr = m_tfSource->AdviseSink(IID_ITfLanguageProfileNotifySink, (ITfLanguageProfileNotifySink*)this, &m_dwCookie);
if(FAILED(hr) || m_dwCookie == -1) {
m_tfSource->Release();
m_tfSource = NULL;
}
}
pProfiles->Release();
}
}
HRESULT STDMETHODCALLTYPE NotifyMe::OnLanguageChange(LANGID langid, __RPC__out BOOL *pfAccept)
{
if(pfAccept) *pfAccept = TRUE;
return S_OK;
}
HRESULT STDMETHODCALLTYPE NotifyMe::OnLanguageChanged()
{
OutputDebugStringA("Language Changed");
return S_OK;
}
HRESULT STDMETHODCALLTYPE NotifyMe::QueryInterface(REFIID riid, __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject)
{
if(!ppvObject)
return E_INVALIDARG;
if(riid == IID_IUnknown)
*ppvObject = static_cast<IUnknown*>(this);
else if(riid == IID_ITfLanguageProfileNotifySink)
*ppvObject = static_cast<ITfLanguageProfileNotifySink*>(this);
else {
*ppvObject = NULL;
return E_NOINTERFACE;
}
AddRef();
return NOERROR;
}
ULONG STDMETHODCALLTYPE NotifyMe::AddRef()
{
InterlockedIncrement(&m_ulRefCount);
return m_ulRefCount;
}
ULONG STDMETHODCALLTYPE NotifyMe::Release()
{
// Decrement the object's internal counter.
ULONG ulRefCount = InterlockedDecrement(&m_ulRefCount);
if(m_ulRefCount == 0)
delete this;
return ulRefCount;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
return 0;
}
// i want to listen to these events when the window is not in focus
case WM_SETFONT:
OutputDebugStringA("Font Changed");
break;
case WM_INPUTLANGCHANGE:
OutputDebugStringA("Language Changed - WndProc");
break;
// -- along with paint
//case WM_PAINT:
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
That's not how the ITfLanguageProfileNotifySink work. It is only for YOUR application.
What you need to understand is that every application got its own language just like yours. So when you switch to an app, the language there could be English, and when you switch back to any other app, it could be French, Japanese, Russian, or whatever language you set there.
What you want to achieve is to detect that change for "every" application, right? Then ITfLanguageProfileNotifySink is not the way.
You have to use GetKeyboardLayout instead because it allows you to get the language of any thread. But looks like you only want to detect the "active" thread, right? So you'd also want to pair it with GetWindowThreadProcessId(GetForegroundWindow(), NULL) because this would get the thread of the currently active window for you.
And for that to be integrated correctly with your application, you also have to modify your loop so that it doesn't "wait" for UI messages and block your detection scheme by using PeekMessage().
MSG msg = {};
const int period_ms = 10;
while (true)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
static LANGID currentLangId = LOWORD(::GetKeyboardLayout(0));
// Get active window thred
DWORD threadId = ::GetWindowThreadProcessId(::GetForegroundWindow(), NULL);
LANGID newLangId = LOWORD(::GetKeyboardLayout(threadId));
if (newLangId != currentLangId)
{
currentLangId = newLangId;
wchar_t szLangName[256];
GetLocaleInfo(MAKELCID(newLangId, SORT_DEFAULT), LOCALE_SENGLANGUAGE, szLangName, 256);
OutputDebugString(szLangName);
OutputDebugString(L"\n");
}
Sleep(period_ms);
}
Note that if the language changed in an app from X -> Y, it'll print 'Y', then when you switch to another app, it'll mostly print 'X' first and then in the next iteration, it'd print 'Y' right after. That's because "most" apps change their language accordingly to the previous window after you open them.
And some other applications would keep their language changes only inside the application no matter what how you change it outside of it. It is different from one app to another. But with that loop, you'll be able to detect all changes.
A new problem blows my mind lately: a very simple code using the WebView2 library from Microsoft works if compiled as C++ but not as C. What could be causing the problem? I tried all sorts of fixes, like using an older version of the WebView2 library, using Edge Canary or beta or a different version of WebView2 Runtime, it simply refuses to work.
Here is the sample code in C:
#include <initguid.h>
#include <Windows.h>
#include <stdio.h>
#include <conio.h>
#include <shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
#include <Shlobj_core.h>
#include "WebView2.h"
#define APPLICATION_NAME TEXT("WebView2")
#define error_printf printf
ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* envHandler;
ICoreWebView2CreateCoreWebView2ControllerCompletedHandler* completedHandler;
HWND hWnd = NULL;
ICoreWebView2Controller* webviewController = NULL;
ICoreWebView2* webviewWindow = NULL;
BOOL bEnvCreated = FALSE;
ULONG HandlerRefCount = 0;
ULONG HandlerAddRef(IUnknown* This)
{
return ++HandlerRefCount;
}
ULONG HandlerRelease(IUnknown* This)
{
--HandlerRefCount;
if (HandlerRefCount == 0)
{
if (completedHandler)
{
free(completedHandler->lpVtbl);
free(completedHandler);
}
if (envHandler)
{
free(envHandler->lpVtbl);
free(envHandler);
}
}
return HandlerRefCount;
}
HRESULT HandlerQueryInterface(
IUnknown* This,
IID* riid,
void** ppvObject
)
{
*ppvObject = This;
HandlerAddRef(This);
return S_OK;
}
HRESULT HandlerInvoke(
IUnknown* This,
HRESULT errorCode,
void* arg
)
{
if (!bEnvCreated)
{
bEnvCreated = TRUE;
char ch;
completedHandler = malloc(sizeof(ICoreWebView2CreateCoreWebView2ControllerCompletedHandler));
if (!completedHandler)
{
error_printf(
"%s:%d: %s (0x%x).\n",
__FILE__,
__LINE__,
"Cannot allocate ICoreWebView2CreateCoreWebView2ControllerCompletedHandler",
GetLastError()
);
ch = _getch();
return GetLastError();
}
completedHandler->lpVtbl = malloc(sizeof(ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl));
if (!completedHandler->lpVtbl)
{
error_printf(
"%s:%d: %s (0x%x).\n",
__FILE__,
__LINE__,
"Cannot allocate ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl",
GetLastError()
);
ch = _getch();
return GetLastError();
}
completedHandler->lpVtbl->AddRef = HandlerAddRef;
completedHandler->lpVtbl->Release = HandlerRelease;
completedHandler->lpVtbl->QueryInterface = HandlerQueryInterface;
completedHandler->lpVtbl->Invoke = HandlerInvoke;
ICoreWebView2Environment* env = arg;
env->lpVtbl->CreateCoreWebView2Controller(
env,
hWnd,
completedHandler
);
}
else
{
ICoreWebView2Controller* controller = arg;
if (controller != NULL) {
webviewController = controller;
webviewController->lpVtbl->get_CoreWebView2(
webviewController,
&webviewWindow
);
}
ICoreWebView2Settings* Settings;
webviewWindow->lpVtbl->get_Settings(
webviewWindow,
&Settings
);
Settings->lpVtbl->put_IsScriptEnabled(
Settings,
TRUE
);
Settings->lpVtbl->put_AreDefaultScriptDialogsEnabled(
Settings,
TRUE
);
Settings->lpVtbl->put_IsWebMessageEnabled(
Settings,
TRUE
);
Settings->lpVtbl->put_AreDevToolsEnabled(
Settings,
FALSE
);
Settings->lpVtbl->put_AreDefaultContextMenusEnabled(
Settings,
TRUE
);
Settings->lpVtbl->put_IsStatusBarEnabled(
Settings,
TRUE
);
RECT bounds;
GetClientRect(hWnd, &bounds);
webviewController->lpVtbl->put_Bounds(
webviewController,
bounds
);
webviewWindow->lpVtbl->Navigate(
webviewWindow,
L"https://google.com/"
);
}
return S_OK;
}
LRESULT CALLBACK WindowProc(
_In_ HWND hWnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
switch (uMsg)
{
/*case WM_NCCALCSIZE:
{
return 0;
}*/
case WM_DPICHANGED:
{
RECT* const newWindowSize = (RECT*)(lParam);
SetWindowPos(
hWnd,
NULL,
newWindowSize->left,
newWindowSize->top,
newWindowSize->right - newWindowSize->left,
newWindowSize->bottom - newWindowSize->top,
SWP_NOZORDER | SWP_NOACTIVATE);
return TRUE;
}
case WM_SIZE:
{
if (webviewController != NULL) {
RECT bounds;
GetClientRect(hWnd, &bounds);
webviewController->lpVtbl->put_Bounds(
webviewController,
bounds
);
};
break;
}
default:
{
return DefWindowProc(
hWnd,
uMsg,
wParam,
lParam
);
}
}
return 0;
}
int WINAPI wWinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nShowCmd
)
{
int ch;
FILE* conout;
AllocConsole();
freopen_s(
&conout,
"CONOUT$",
"w",
stdout
);
HRESULT hr;
if (!SetProcessDpiAwarenessContext(
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
))
{
error_printf(
"%s:%d: %s (0x%x).\n",
__FILE__,
__LINE__,
"SetProcessDpiAwarenessContext",
GetLastError()
);
ch = _getch();
return GetLastError();
}
hr = CoInitialize(NULL);
if (FAILED(hr))
{
error_printf(
"%s:%d: %s (0x%x).\n",
__FILE__,
__LINE__,
"CoInitialize",
hr
);
ch = _getch();
return hr;
}
WNDCLASS wndClass = { 0 };
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WindowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = APPLICATION_NAME;
hWnd = CreateWindowEx(
0,
(LPCWSTR)(
MAKEINTATOM(
RegisterClass(&wndClass)
)
),
APPLICATION_NAME,
WS_OVERLAPPEDWINDOW,
100, 100, 800, 800,
NULL,
NULL,
hInstance,
NULL
);
if (!hWnd)
{
error_printf(
"%s:%d: %s (0x%x).\n",
__FILE__,
__LINE__,
"CreateWindowEx",
GetLastError()
);
ch = _getch();
return GetLastError();
}
ShowWindow(hWnd, nShowCmd);
envHandler = malloc(sizeof(ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler));
if (!envHandler)
{
error_printf(
"%s:%d: %s (0x%x).\n",
__FILE__,
__LINE__,
"Cannot allocate ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler",
GetLastError()
);
ch = _getch();
return GetLastError();
}
envHandler->lpVtbl = malloc(sizeof(ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl));
if (!envHandler->lpVtbl)
{
error_printf(
"%s:%d: %s (0x%x).\n",
__FILE__,
__LINE__,
"Cannot allocate ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl",
GetLastError()
);
ch = _getch();
return GetLastError();
}
envHandler->lpVtbl->AddRef = HandlerAddRef;
envHandler->lpVtbl->Release = HandlerRelease;
envHandler->lpVtbl->QueryInterface = HandlerQueryInterface;
envHandler->lpVtbl->Invoke = HandlerInvoke;
UpdateWindow(hWnd);
CreateCoreWebView2EnvironmentWithOptions(
NULL,
NULL,
NULL,
envHandler
);
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(
&msg,
NULL,
0,
0)) != 0)
{
// An error occured
if (bRet == -1)
{
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
As you can see, I simply used lpVtbl where appropiate and provided proper callbacks. The code is a bit more compact in C++:
#include <Windows.h>
#include <stdio.h>
#include <wrl.h>
#include <wil/com.h>
#include "WebView2.h"
#define APPLICATION_NAME TEXT("WebView2")
static wil::com_ptr<ICoreWebView2Controller> webviewController;
static wil::com_ptr<ICoreWebView2> webviewWindow;
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
switch (uMsg)
{
case WM_DPICHANGED:
{
RECT* const newWindowSize = (RECT*)(lParam);
SetWindowPos(hwnd,
NULL,
newWindowSize->left,
newWindowSize->top,
newWindowSize->right - newWindowSize->left,
newWindowSize->bottom - newWindowSize->top,
SWP_NOZORDER | SWP_NOACTIVATE);
return TRUE;
}
case WM_SIZE:
{
if (webviewController != NULL) {
RECT bounds;
GetClientRect(hwnd, &bounds);
webviewController->put_Bounds(bounds);
};
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(
hwnd,
uMsg,
wParam,
lParam
);
}
}
return 0;
}
int WINAPI wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PWSTR pCmdLine,
int nCmdShow
)
{
SetProcessDpiAwarenessContext(
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
);
WNDCLASS wndClass = { 0 };
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WindowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = APPLICATION_NAME;
RegisterClass(&wndClass);
HWND hWnd = CreateWindowEx(
0,
APPLICATION_NAME,
APPLICATION_NAME,
WS_OVERLAPPEDWINDOW,
100, 100, 800, 800,
NULL,
NULL,
hInstance,
NULL
);
ShowWindow(
hWnd,
nCmdShow
);
UpdateWindow(hWnd);
CreateCoreWebView2EnvironmentWithOptions(NULL, NULL, NULL,
Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
[hWnd](HRESULT result, ICoreWebView2Environment* env) -> HRESULT {
// Create a CoreWebView2Controller and get the associated CoreWebView2 whose parent is the main window hWnd
env->CreateCoreWebView2Controller(hWnd, Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
[hWnd](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT {
if (controller != nullptr) {
webviewController = controller;
webviewController->get_CoreWebView2(&webviewWindow);
}
// Add a few settings for the webview
// The demo step is redundant since the values are the default settings
ICoreWebView2Settings* Settings;
webviewWindow->get_Settings(&Settings);
Settings->put_IsScriptEnabled(TRUE);
Settings->put_AreDefaultScriptDialogsEnabled(TRUE);
Settings->put_IsWebMessageEnabled(TRUE);
Settings->put_AreDevToolsEnabled(FALSE);
//Settings->put_AreDefaultContextMenusEnabled(FALSE);
Settings->put_IsStatusBarEnabled(FALSE);
// Resize WebView to fit the bounds of the parent window
RECT bounds;
GetClientRect(hWnd, &bounds);
webviewController->put_Bounds(bounds);
webviewController->put_ZoomFactor(0.8);
// Schedule an async task to navigate to Bing
webviewWindow->Navigate(HOME_PAGE);
// Step 4 - Navigation events
// Step 5 - Scripting
// Step 6 - Communication between host and web content
return S_OK;
}).Get());
return S_OK;
}).Get());
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(
&msg,
NULL,
0,
0)) != 0)
{
// An error occured
if (bRet == -1)
{
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
Since I have now stumbled upon this, I am really curious what may be causing this. Why does it matter in the end? Thanks for any pointers.
It does not work means the web page is not displayed. The window is blank. The WebView2 actually is not displayed on the window.
Edit:
What are wil::com_ptrs actually? If I change this:
static wil::com_ptr<ICoreWebView2Controller> webviewController;
static wil::com_ptr<ICoreWebView2> webviewWindow;
To this:
static ICoreWebView2Controller* webviewController;
static ICoreWebView2* webviewWindow;
In C++, I break it. Why? Just why? (I replaced the callbacks Microsoft::WRL::Callback with standalone classes and, naturally, it still worked, but getting rid of the COM pointers and using regular pointers breaks it. Why...?
The solution is simple, if one actually takes a minute to look a bit on the correctness and logic of the code, rather than suggesting generics and commenting just so that the patch cables have some bytes to carry around. This section has to be modified a bit: since I use controller after the function returns, and without using smart pointers, I have to increase its reference count so that the library knows I use it and does not free it after the Invoke function body is executed. Of course, when you do the assignment in the version with smart pointers, this is automatically done in the background. Unfortunately, I overlooked this, not being that aware this happens. Things like these is the reason I write these ports in C, so that I take an in-depth look and better understand how stuff works from top to bottom. 'It can't be done' without an actual reason is not a valid answer for me.
Anyway, here is the fixed version:
if (controller != NULL) {
webviewController = controller;
webviewController->lpVtbl->get_CoreWebView2(
webviewController,
&webviewWindow
);
webviewController->lpVtbl->AddRef(webviewController); // <-- here, increase the reference count for the webviewController
}
That's it. many thanks to the guy who helped me out: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1124
I would like write an simple (only one exe) music player.
The player is work if I want play mp3, but didn't work if I want use MadFLAC Filter.
I can't connect of the MadFLAC pins. Always drop 0x80040207 error.
(I think, the MadFLAC filter is loaded correctly, I can add to the GraphBuilder without error and see in the EnumFilters.)
I do not insist on using MadFLAC. All other options is interest me with which I can play a FLAC file via DirectShow without install codec pack to client pc.
Here is my simplified code:
#include <windows.h>
#include <strmif.h>
#include <control.h>
#include <uuids.h>
#pragma comment(lib, "strmiids.lib")
IPin* GetPin(IBaseFilter *bFilter, PIN_DIRECTION pindir)
{
IEnumPins *EnumPin;
bFilter->EnumPins(&EnumPin);
unsigned long int num;
IPin *TempPin = NULL;
do {
EnumPin->Next(1, &TempPin, &num);
if (num != 0)
{
PIN_INFO PinInfo;
TempPin->QueryPinInfo(&PinInfo);
if (PinInfo.dir == pindir) break;
}
} while (num != 0);
return TempPin;
}
typedef HRESULT __stdcall DLLGETCLASSOBJECT(REFCLSID rclsid, REFIID riid, void **ppv);
HRESULT CreateFilterFromFile(HINSTANCE hLibInst, GUID TGUID, void **Filter) {
IClassFactory * ClassFactory;
HRESULT Result = S_FALSE;
FARPROC func;
func = GetProcAddress(hLibInst, "DllGetClassObject");
if (func != NULL)
{
IClassFactory *classFactory;
DLLGETCLASSOBJECT *dllGetClassObject = (DLLGETCLASSOBJECT*)func;
Result = dllGetClassObject(TGUID, IID_IClassFactory, (void**)&classFactory);
if (SUCCEEDED(Result))
{
Result = classFactory->CreateInstance(NULL, IID_IBaseFilter, Filter);
classFactory->Release();
}
}
return Result;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
void Player(bool isFlac) {
IGraphBuilder * player;
IMediaControl * mcontrol;
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&player);
player->QueryInterface(IID_IMediaControl, (void **)&mcontrol);
IBaseFilter *ARS = NULL;
HRESULT hr = CoCreateInstance(CLSID_AsyncReader, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&ARS);
IBaseFilter * AudioDecoderFilter = NULL;
if (isFlac) {
hr = player->AddSourceFilter(L"D:\\test.flac", L"Async Reader Source", &ARS);
GUID CLSID_MadFlacAudioDecoder;
CLSIDFromString(L"{6B257121-CBB6-46B3-ABFA-B14DFA98C4A6}", &CLSID_MadFlacAudioDecoder);
HINSTANCE FHMPCAudioFilterInst = CoLoadLibrary(L"d:\\_MadFlac\\madFlac.ax", true);
if (FHMPCAudioFilterInst != 0) {
hr = CreateFilterFromFile(FHMPCAudioFilterInst, CLSID_MadFlacAudioDecoder, (void **)&AudioDecoderFilter);
if (AudioDecoderFilter != NULL) {
player->AddFilter(AudioDecoderFilter, L"MadFLAC Audio decoder (internal)");
}
}
}
else {
hr = player->AddSourceFilter(L"D:\\test.mp3", L"Async Reader Source", &ARS);
hr = CoCreateInstance(CLSID_MPEG1Splitter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&AudioDecoderFilter);
hr = player->AddFilter(AudioDecoderFilter, L"MPEG1Splitter");
}
IBaseFilter *PCM = NULL;
hr = CoCreateInstance(CLSID_ACMWrapper, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&PCM);
hr = player->AddFilter(PCM, L"PCM");
IBaseFilter *DSE = NULL;
hr = CoCreateInstance(CLSID_DSoundRender, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&DSE);
hr = player->AddFilter(DSE, L"Direct Renderer");
//ARS -> AdioDecoder
hr = GetPin(ARS, PINDIR_OUTPUT)->Connect(GetPin(AudioDecoderFilter, PINDIR_INPUT), NULL);
//If I use MadFLAC filter this hresult is 0x80040207
if (FAILED(hr)) MessageBoxA(NULL, "There is no common media type between these pins.", "Error", NULL);
//AudioDecoderFilter -> PCM
hr = GetPin(AudioDecoderFilter, PINDIR_OUTPUT)->Connect(GetPin(PCM, PINDIR_INPUT), NULL);
//PCM -> DSE
hr = GetPin(PCM, PINDIR_OUTPUT)->Connect(GetPin(DSE, PINDIR_INPUT), NULL);
//ListGraph(player);
mcontrol->Run();
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = "Teszt";
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassEx(&wc)) { MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; }
hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, "Teszt", "AppName", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, 460, 240, NULL, NULL, hInstance, NULL);
if (hwnd == NULL) { MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; }
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
Player(false);
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
I use VS2015, native C++.
I would have tried to use LibFLAC as well, but I was already stuck there that I couldn’t Build because it threw hundreds of bugs, which I eventually reduced to less than 20 but I had already given up there after several hours of suffering.
https://github.com/xiph/flac
https://ftp.osuosl.org/pub/xiph/releases/oggdsf/
Windows is not shipped with FLAC decoder for DirectShow. Hence, ability to play FLAC audio via DirectShow requires a third party decoder. If you don't want to reply on a codec pack, you need to either bundle certain FLAC decoder with DirectShow interface or implement a decoder in code. https://xiph.org/dshow looks like the best third party decoder option to me, implementing a DirectShow filter over FLAC library requires some development effort, which is probably an overkill for the task in question.
I was making a program that draws two lines and a circle in the window, for this I used Direct2D.
I was doing a program that draws two lines and a circle in the window. I noticed a problem and that is that when drawing a line of coordinates (100,200), (200,200), that line is not drawn in the pixels (100,200) and ( 200,200). I checked this by reading the coordinates of the mouse in the window and seeing if the line actually has the coordinates that I mentioned.
Do a little more research, and according to Microsoft, direct2d works with dpi and not physical pixels.
So is there a way to work directly with pixels like in Processing or Win32 where the position of the controls is expressed in physical pixels of the window.
#include <d2d1_3.h>
#include <d2d1_3helper.h>
#include <d3d11_1.h>
#include <dxgi1_6.h>
#include <string>
#pragma comment(lib,"d3d11")
#pragma comment(lib,"d2d1")
#pragma comment(lib,"dxgi")
#pragma warning(disable : 4996)
using namespace D2D1;
using namespace std;
template<typename Interface> void SafeRelease(Interface**);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInst
, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) {
UNREFERENCED_PARAMETER(hPrevInst);
UNREFERENCED_PARAMETER(lpCmdLine);
WNDCLASS wc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINLOGO));
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = L"Demo_Wnd_Class";
wc.lpszMenuName = NULL;
wc.style = 0;
RegisterClass(&wc);
DWORD dwTopLevelWndStyle = WS_OVERLAPPEDWINDOW ^ (WS_THICKFRAME | WS_MAXIMIZEBOX);
RECT clientRc = { 0,0,640,480 };
AdjustWindowRect(&clientRc, dwTopLevelWndStyle, FALSE);
HWND hWnd = CreateWindow(L"Demo_Wnd_Class", L"Direct2DDemo", dwTopLevelWndStyle, CW_USEDEFAULT, CW_USEDEFAULT
, clientRc.right - clientRc.left, clientRc.bottom - clientRc.top, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, SW_SHOWDEFAULT);
MSG msg;
ZeroMemory(&msg, sizeof(MSG));
ID3D11Device* d3dDevice;
IDXGIDevice* dxgiDevice = NULL;
D3D_FEATURE_LEVEL featureLevels[] = {
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1,
};
HRESULT hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT, featureLevels
, 7, D3D11_SDK_VERSION, &d3dDevice, NULL, NULL);
if (SUCCEEDED(hr)) {
OutputDebugString(L"D3D11 Device created successful\n");
hr = d3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
}
ID2D1Device6* d2dDevice = NULL;
ID2D1DeviceContext6* d2dDeviceContext = NULL;
IDXGIFactory2* dxgiFactory = NULL;
IDXGISwapChain1* dxgiSwapChain = NULL;
IDXGISurface *dxgiBackBufferSurface = NULL; // Back buffer
ID2D1Bitmap1* bmpTarget = NULL;
ID2D1SolidColorBrush* shapeBr = NULL;
DXGI_SWAP_CHAIN_DESC1 dscd;
dscd.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
dscd.BufferCount = 2;
dscd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
dscd.Flags = 0;
dscd.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
dscd.Height = 480;
dscd.SampleDesc.Count = 1;
dscd.SampleDesc.Quality = 0;
dscd.Scaling = DXGI_SCALING_NONE;
dscd.Stereo = FALSE;
dscd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
dscd.Width = 640;
if (SUCCEEDED(hr)) {
OutputDebugString(L"DXGI Device created successful\n");
hr = D2D1CreateDevice(dxgiDevice, CreationProperties(D2D1_THREADING_MODE_SINGLE_THREADED, D2D1_DEBUG_LEVEL_NONE, D2D1_DEVICE_CONTEXT_OPTIONS_NONE), (ID2D1Device**)&d2dDevice);
}
if (SUCCEEDED(hr)) {
OutputDebugString(L"D2D Device created successful\n");
hr = d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, (ID2D1DeviceContext**)&d2dDeviceContext);
}
if (SUCCEEDED(hr)) {
OutputDebugString(L"D2D Device Context created successful\n");
hr = CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, __uuidof(IDXGIFactory2), (void**)&dxgiFactory);
}
if (SUCCEEDED(hr)) {
OutputDebugString(L"DXGI Factory created successful\n");
hr = dxgiFactory->CreateSwapChainForHwnd(d3dDevice, hWnd, &dscd, NULL, NULL, &dxgiSwapChain);
}
if (SUCCEEDED(hr)) {
OutputDebugString(L"DXGI Swap Chain created successful\n");
hr = dxgiSwapChain->GetBuffer(0, __uuidof(IDXGISurface), (void**)&dxgiBackBufferSurface);
}
if (SUCCEEDED(hr)) {
hr = d2dDeviceContext->CreateBitmapFromDxgiSurface(dxgiBackBufferSurface, BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE),0,0), &bmpTarget);
}
if (SUCCEEDED(hr)) {
OutputDebugString(L"D2D Bitmap created successful\n");
d2dDeviceContext->SetTarget(bmpTarget);
hr = d2dDeviceContext->CreateSolidColorBrush(ColorF(ColorF::White), &shapeBr);
}
if (SUCCEEDED(hr)) {
OutputDebugString(L"D2D Solid Color Brush created successful\n");
d2dDeviceContext->SetUnitMode(D2D1_UNIT_MODE_PIXELS);
d2dDeviceContext->SetTransform(Matrix3x2F::Identity());
d2dDeviceContext->SetAntialiasMode(D2D1_ANTIALIAS_MODE::D2D1_ANTIALIAS_MODE_ALIASED);
}
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
d2dDeviceContext->BeginDraw();
d2dDeviceContext->Clear(ColorF(ColorF::ForestGreen));
d2dDeviceContext->DrawLine(Point2F(400, 0), Point2F(400, 400), shapeBr);
d2dDeviceContext->DrawLine(Point2F(200.0f, 320.0f), Point2F(400.0f, 320.0f), shapeBr);
d2dDeviceContext->DrawEllipse(D2D1::Ellipse(Point2F(300.0f, 300.0f), 10.0f, 10.0f), shapeBr);
POINT cursorPos;
GetCursorPos(&cursorPos);
ScreenToClient(hWnd,&cursorPos);
wstring dbgCursorPos = wstring(L"(MouseX: " + to_wstring(cursorPos.x) + L" MouseY: " + to_wstring(cursorPos.y) + L")\n");
OutputDebugString(dbgCursorPos.c_str());
d2dDeviceContext->EndDraw();
dxgiSwapChain->Present(1, 0);
}
}
SafeRelease(&d3dDevice);
SafeRelease(&dxgiBackBufferSurface);
SafeRelease(&dxgiDevice);
SafeRelease(&dxgiFactory);
SafeRelease(&dxgiSwapChain);
SafeRelease(&d2dDevice);
SafeRelease(&d2dDeviceContext);
SafeRelease(&bmpTarget);
SafeRelease(&shapeBr);
return 0;
}
template<typename Interface> void SafeRelease(Interface** ppInterface) {
if (*ppInterface) {
(*ppInterface)->Release();
(*ppInterface) = NULL;
}
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
if (Msg == WM_DESTROY) {
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
I tried to create this window class which creates and shows a window. When I run this class, GetMessage keep on sending WM_PAINT message, and task manager shows me about 50% CPU usage just from my process.
main.cpp:
#include "Window.h"
int WINAPI WinMain(
HINSTANCE /* hInstance */,
HINSTANCE /* hPrevInstance */,
LPSTR /* lpCmdLine */,
int /* nCmdShow */
)
{
Window::GlobalInitialize();
Window window(L"abcd", 500, 500);
if (SUCCEEDED(window.Initialize()))
window.RunMessageLoop();
Window::GlobalTerminate();
return 0;
}
Window.h:
#ifndef WINDOW_HEADER
#define WINDOW_HEADER
#include <Windows.h>
#include <functional>
#pragma comment(lib, "d2d1.lib")
class Window;
typedef std::function<void(Window *window, UINT id, LPCWSTR message)> ErrorCallback;
class Window {
public:
#define ERROR_FAILED_HINSTANCE 1
#define ERROR_FAILED_HINSTANCE_STR L"Failed to retrieve hInstance"
#define ERROR_FAILED_REGISTER 2
#define ERROR_FAILED_REGISTER_STR L"Failed to register window class"
#define ERROR_FAILED_CREATION 3
#define ERROR_FAILED_CREATION_STR L"Failed to create window"
typedef std::function<HRESULT(Window *window)> WEOnCreate;
typedef std::function<HRESULT(Window *window)> WEOnDestroy;
typedef std::function<HRESULT(Window *window)> WEOnRender;
typedef std::function<HRESULT(Window *window, UINT width, UINT height)> WEOnResize;
typedef std::function<HRESULT(Window *window, UINT horizontalResolution, UINT verticalResolution)> WEOnScreenResolutionChange;
Window(LPCWSTR title, UINT width, UINT height);
~Window();
HRESULT SetSize(UINT width, UINT height);
HRESULT SetTitle(LPCWSTR title);
inline UINT GetWidth() { return _width; }
inline UINT GetHeight() { return _height; }
inline LPCWSTR GetTitle() { return _title; }
inline HWND GetHandle() { return hWnd; }
inline void SetOnCreateCallback(WEOnCreate fun) { _onCreate = fun; }
inline void SetOnDestroyCallback(WEOnDestroy fun) { _onDestroy = fun; }
inline void SetOnRenderCallback(WEOnRender fun) { _onRender = fun; }
inline void SetOnResizeCallback(WEOnResize fun) { _onResize = fun; }
inline void SetOnScreenResolutionChangeCallback(WEOnScreenResolutionChange fun) { _onResChange = fun; }
inline void SetExtraAllocatedSpace(void *ptr) { extra = ptr; }
inline void *GetExtraAllocatedSpace() { return extra; }
inline void Terminate() { if (hWnd) DestroyWindow(hWnd); }
static inline void SetErrorCallback(ErrorCallback fun) { _errorCallback = fun; }
HRESULT Initialize();
void RunMessageLoop();
static HRESULT GlobalInitialize();
static HRESULT GlobalTerminate();
private:
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static inline void throwError(Window *window, UINT id, LPCWSTR message) {
if (_errorCallback)
_errorCallback(window, id, message);
}
WEOnCreate _onCreate;
WEOnDestroy _onDestroy;
WEOnRender _onRender;
WEOnResize _onResize;
WEOnScreenResolutionChange _onResChange;
static ErrorCallback _errorCallback;
static LPCWSTR szClassName;
static HINSTANCE hInstance;
HWND hWnd;
int _width, _height;
LPCWSTR _title;
void *extra;
};
#endif
Window.cpp:
#include "Window.h"
//Initialize static variables
ErrorCallback Window::_errorCallback = nullptr;
LPCWSTR Window::szClassName = L"WindowClass";
HINSTANCE Window::hInstance;
Window::Window(LPCWSTR title = L"Window", UINT width = 640, UINT height = 480) :
_onCreate(nullptr),
_onDestroy(nullptr),
_onRender(nullptr),
_onResize(nullptr),
hWnd(NULL),
extra(NULL),
_width(width),
_height(height),
_title(title) {}
Window::~Window() {
if (hWnd) {
DestroyWindow(hWnd);
hWnd = NULL;
}
}
HRESULT Window::GlobalInitialize() {
// Retreive hInstance
hInstance = GetModuleHandle(NULL);
if (!hInstance) {
throwError(NULL, ERROR_FAILED_HINSTANCE, ERROR_FAILED_HINSTANCE_STR);
return E_FAIL;
}
// Create window class
WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = Window::WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(LONG_PTR);
wcex.hInstance = hInstance;
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.hCursor = LoadCursor(NULL, IDI_APPLICATION);
wcex.lpszClassName = szClassName;
if (!RegisterClassEx(&wcex)) {
throwError(NULL, ERROR_FAILED_REGISTER, ERROR_FAILED_REGISTER_STR);
return E_FAIL;
}
return S_OK;
}
HRESULT Window::GlobalTerminate() {
if (UnregisterClass(szClassName, hInstance))
return S_OK;
else
return E_FAIL;
}
void Window::RunMessageLoop() {
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
HRESULT Window::Initialize() {
// Create the window
hWnd = CreateWindow(
szClassName,
_title,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
_width,
_height,
NULL,
NULL,
hInstance,
this
);
if (!hWnd) {
throwError(this, ERROR_FAILED_CREATION, ERROR_FAILED_CREATION_STR);
return E_FAIL;
}
// Show and render the window
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
return S_OK;
}
LRESULT CALLBACK Window::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_CREATE) {
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
Window *window = (Window *)pcs->lpCreateParams;
::SetWindowLongPtr(hWnd, GWLP_USERDATA, PtrToUlong(window));
if (window->_onCreate != nullptr)
window->_onCreate(window);
}
Window *pWnd = reinterpret_cast<Window *>(static_cast<LONG_PTR>(GetWindowLongPtr(hWnd, GWLP_USERDATA)));
HRESULT hr = S_OK;
if (!pWnd) {
return DefWindowProc(hWnd, message, wParam, lParam);
}
switch (message) {
case WM_PAINT:
{
if (pWnd->_onRender)
hr = pWnd->_onRender(pWnd);
else
hr = S_OK;
}
break;
case WM_SIZE:
{
if (pWnd->_onResize)
hr = pWnd->_onResize(pWnd, LOWORD(lParam), HIWORD(lParam));
else
hr = S_OK;
}
break;
case WM_DISPLAYCHANGE:
{
if (pWnd->_onResChange)
hr = pWnd->_onResChange(pWnd, LOWORD(lParam), HIWORD(lParam));
else
hr = S_OK;
}
break;
case WM_DESTROY:
{
if (pWnd->_onDestroy && FAILED(pWnd->_onDestroy(pWnd)))
break;
}
PostQuitMessage(0);
hWnd = NULL;
break;
default:
hr = DefWindowProc(hWnd, message, wParam, lParam);
}
return hr;
}
HRESULT Window::SetSize(UINT width, UINT height) {
if (hWnd)
if (!::SetWindowPos(hWnd, 0, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER))
return E_FAIL;
_width = width;
_height = height;
return S_OK;
}
HRESULT Window::SetTitle(LPCWSTR title) {
if (hWnd)
if (!::SetWindowText(hWnd, title))
return E_FAIL;
_title = title;
return S_OK;
}
I hope someone can help me since everything looks OK(the window even runs fine).
Firstly, you seem to be treating the window procedure as if it was a COM method, but window procedures do not return an HRESULT - they return an LRESULT whose meaning is different for each message.
In the case of WM_PAINT it's not possible to return a value that indicates "I don't need to paint this time". The system will send WM_PAINT messages as long as a portion of your window is marked as dirty, and the way you mark the dirty area as "painted" is by calling BeginPaint and EndPaint. If you don't do this, the system will continue to consider your window as dirty and continue to send WM_PAINT messages.
You haven't shown the source code for your _onRender function but the very fact that you have made WM_PAINT handling optional (i.e. if nothing calls SetOnRenderCallback then no callback will be registered) means that you are probably not processing WM_PAINT correctly. At the very least, if you don't do the painting yourself, you should pass the message through to DefWindowProc to allow the default processing to take place.