Created
August 5, 2016 09:57
-
-
Save phongphan/c55781933d60262cdefbfbf56e7e86d9 to your computer and use it in GitHub Desktop.
Windows automatic proxy settings resolver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Windows automatic proxy settings resolver | |
* Jiri Hruska <jirka@fud.cz>, 2012-2013 | |
* Released under the WTFPL license. */ | |
#define UNICODE | |
#define _UNICODE | |
#include <string> | |
#include <Windows.h> | |
#include <winhttp.h> | |
#pragma comment(lib, "winhttp.lib") | |
namespace detail { | |
struct WinHttpHandle { | |
HINTERNET m_hInternet; | |
WinHttpHandle(HANDLE hInternet = NULL) | |
: m_hInternet(hInternet) | |
{ | |
} | |
WinHttpHandle& operator=(HINTERNET hInternet) | |
{ | |
if (hInternet != m_hInternet) { | |
if (m_hInternet) | |
WinHttpCloseHandle(m_hInternet); | |
m_hInternet = hInternet; | |
} | |
return *this; | |
} | |
operator HINTERNET() | |
{ | |
return m_hInternet; | |
} | |
~WinHttpHandle() | |
{ | |
if (m_hInternet) | |
WinHttpCloseHandle(m_hInternet); | |
} | |
}; | |
struct WinHttpCurrentUserIeProxyConfig : public WINHTTP_CURRENT_USER_IE_PROXY_CONFIG { | |
WinHttpCurrentUserIeProxyConfig() | |
{ | |
memset(this, 0, sizeof(*this)); | |
} | |
~WinHttpCurrentUserIeProxyConfig() | |
{ | |
if (lpszAutoConfigUrl) | |
GlobalFree(lpszAutoConfigUrl); | |
if (lpszProxy) | |
GlobalFree(lpszProxy); | |
if (lpszProxyBypass) | |
GlobalFree(lpszProxyBypass); | |
} | |
}; | |
struct WinHttpProxyInfo : public WINHTTP_PROXY_INFO { | |
WinHttpProxyInfo() | |
{ | |
memset(this, 0, sizeof(*this)); | |
} | |
~WinHttpProxyInfo() | |
{ | |
if (lpszProxy) | |
GlobalFree(lpszProxy); | |
if (lpszProxyBypass) | |
GlobalFree(lpszProxyBypass); | |
} | |
}; | |
class CrackedUrl { | |
public: | |
CrackedUrl() | |
{ | |
m_scheme[0] = 0; | |
m_hostname[0] = 0; | |
} | |
bool crack(LPCWSTR url) | |
{ | |
// Loosely based on InternetCrackUrl(), which needs another .dll dependency and | |
// including both <WinHttp.h> and <WinInet.h> is broken in Windows SDK (...). | |
LPCWSTR ptr = wcschr(url, L':'); | |
if (!ptr || ptr - url >= _countof(m_scheme)) | |
return false; | |
wcsncpy_s(m_scheme, url, ptr - url); | |
CharLower(m_scheme); | |
if (wcsncmp(ptr, L"://", 3) == 0) | |
url = ptr + 3; | |
else | |
url = ptr + 1; | |
ptr = wcschr(url, L'/'); | |
LPCWSTR ptr2 = wcschr(url, L'@'); | |
if (ptr2 && ptr2 < ptr) | |
url = ptr2 + 1; | |
ptr2 = wcschr(url, L':'); | |
if (ptr2 && ptr2 < ptr) | |
ptr = ptr2; | |
wcsncpy_s(m_hostname, url, ptr - url); | |
CharLower(m_hostname); | |
return true; | |
} | |
LPCWSTR scheme() const | |
{ | |
return m_scheme; | |
} | |
LPCWSTR hostname() const | |
{ | |
return m_hostname; | |
} | |
protected: | |
WCHAR m_scheme[32]; | |
WCHAR m_hostname[256]; | |
}; | |
class ProxyChecker | |
{ | |
protected: | |
struct StaticContext { | |
CRITICAL_SECTION cs; | |
WinHttpHandle hInternet; | |
bool fullAutodetectionFailed; | |
StaticContext() | |
: fullAutodetectionFailed(false) | |
{ | |
InitializeCriticalSection(&cs); | |
} | |
~StaticContext() | |
{ | |
DeleteCriticalSection(&cs); | |
} | |
}; | |
protected: | |
static StaticContext s_context; | |
LPCWSTR m_url; | |
CrackedUrl m_urlInfo; | |
std::wstring m_out; | |
WINHTTP_AUTOPROXY_OPTIONS m_apo; | |
protected: | |
bool checkProxyBypassList(LPWSTR list) | |
{ | |
// The proxy bypass list contains one or more server names separated by semicolons | |
// or whitespace. The proxy bypass list can also contain the string "<local>" to | |
// indicate that all local intranet sites are bypassed. Local intranet sites are | |
// considered to be all servers that do not contain a period in their name. | |
static const wchar_t separators[] = L"\t\r\n ;"; | |
wchar_t* context; | |
for (wchar_t* tok = wcstok_s(list, separators, &context); tok; tok = wcstok_s(NULL, separators, &context)) { | |
CharLower(tok); | |
if (wcscmp(tok, L"<local>") == 0) { | |
if (wcschr(m_urlInfo.hostname(), L'.') == NULL || | |
wcscmp(m_urlInfo.hostname(), L"127.0.0.1") == 0) | |
return true; | |
continue; | |
} | |
const wchar_t* mask = tok; | |
const wchar_t* hostname = m_urlInfo.hostname(); | |
const wchar_t* backtrack = NULL; | |
while (*mask && *hostname) { | |
if (*mask == *hostname) { | |
mask++; | |
hostname++; | |
} else if (*mask == L'*') { | |
while (mask[1] == L'*') | |
mask++; | |
backtrack = mask++; | |
while (*hostname && *mask != *hostname) | |
hostname++; | |
} else if (backtrack) { | |
mask = backtrack; | |
} else { | |
break; | |
} | |
if (!*mask && *hostname && backtrack) | |
mask = backtrack; | |
} | |
while(*mask == L'*') | |
mask++; | |
if (!*mask && !*hostname) | |
return true; | |
} | |
return false; | |
} | |
bool getProxyFromList(LPWSTR list) | |
{ | |
// The proxy server list contains one or more of the following strings | |
// separated by semicolons or whitespace: | |
// ([<scheme>=][<scheme>"://"]<server>[":"<port>]) | |
static const wchar_t separators[] = L"\t\r\n ;"; | |
wchar_t* context; | |
for (wchar_t* tok = wcstok_s(list, separators, &context); tok; tok = wcstok_s(NULL, separators, &context)) { | |
// If there are multiple entries for different protocols, accept only the requested one. | |
wchar_t* pos = wcschr(tok, L'='); | |
if (pos) { | |
*pos = L'\0'; | |
if (wcscmp(tok, m_urlInfo.scheme()) != 0) | |
continue; | |
tok = pos + 1; | |
} | |
// If there is protocol included in the proxy address, accept only "http://". | |
pos = wcsstr(tok, L"://"); | |
if (pos) { | |
if (wcsncmp(tok, L"http:", pos - tok + 1) != 0) | |
continue; | |
tok = pos + 3; | |
} | |
m_out = tok; | |
return true; | |
} | |
return false; | |
} | |
bool checkBypassAndGetProxy(LPWSTR proxyBypassList, LPWSTR proxyList) | |
{ | |
if (proxyBypassList && checkProxyBypassList(proxyBypassList)) | |
return true; | |
if (proxyList && getProxyFromList(proxyList)) | |
return true; | |
return false; | |
} | |
bool tryGetProxyForUrl() | |
{ | |
EnterCriticalSection(&s_context.cs); | |
if ((m_apo.dwFlags & WINHTTP_AUTOPROXY_AUTO_DETECT) && s_context.fullAutodetectionFailed) { | |
LeaveCriticalSection(&s_context.cs); | |
return false; | |
} | |
if (!s_context.hInternet) | |
s_context.hInternet = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC); | |
if (!s_context.hInternet) { | |
LeaveCriticalSection(&s_context.cs); | |
return false; | |
} | |
m_apo.lpvReserved = NULL; | |
m_apo.dwReserved = 0; | |
m_apo.fAutoLogonIfChallenged = FALSE; | |
WinHttpProxyInfo pi; | |
BOOL bResult = WinHttpGetProxyForUrl(s_context.hInternet, m_url, &m_apo, &pi); | |
if (!bResult) { | |
if (GetLastError() == ERROR_WINHTTP_AUTODETECTION_FAILED) { | |
s_context.fullAutodetectionFailed = true; | |
} else if (GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE) { | |
m_apo.fAutoLogonIfChallenged = TRUE; | |
bResult = WinHttpGetProxyForUrl(s_context.hInternet, m_url, &m_apo, &pi); | |
} | |
} | |
LeaveCriticalSection(&s_context.cs); | |
if (!bResult) | |
return false; | |
if (pi.dwAccessType == WINHTTP_ACCESS_TYPE_NO_PROXY) | |
return true; | |
if (pi.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY) { | |
if (checkBypassAndGetProxy(pi.lpszProxyBypass, pi.lpszProxy)) | |
return true; | |
} | |
return false; | |
} | |
public: | |
bool process(LPCWSTR url) | |
{ | |
m_out.clear(); | |
m_url = url; | |
if (!m_urlInfo.crack(url)) | |
return false; | |
WinHttpCurrentUserIeProxyConfig iecfg; | |
if (!WinHttpGetIEProxyConfigForCurrentUser(&iecfg)) | |
return false; | |
bool triedSomething = false; | |
// If auto-config script is provided, try to use it. | |
if (iecfg.lpszAutoConfigUrl) { | |
triedSomething = true; | |
m_apo.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; | |
m_apo.dwAutoDetectFlags = 0; | |
m_apo.lpszAutoConfigUrl = iecfg.lpszAutoConfigUrl; | |
if (tryGetProxyForUrl()) | |
return true; | |
} | |
// If automatic detection is enabled, try to use it. | |
if (iecfg.fAutoDetect) { | |
triedSomething = true; | |
m_apo.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT; | |
m_apo.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; | |
m_apo.lpszAutoConfigUrl = NULL; | |
WinHttpProxyInfo pi; | |
if (tryGetProxyForUrl()) | |
return true; | |
} | |
// If custom proxy servers are configured, try to use them. | |
if (iecfg.lpszProxy) { | |
triedSomething = true; | |
if (checkBypassAndGetProxy(iecfg.lpszProxyBypass, iecfg.lpszProxy)) | |
return true; | |
} | |
// If nothing was enabled, direct mode should be used. | |
if (!triedSomething) | |
return true; | |
// Something was tried but failed. Perhaps use direct mode, but only as fallback. | |
return false; | |
} | |
LPCWSTR proxyAddr() const | |
{ | |
return (m_out.empty() ? NULL : m_out.c_str()); | |
} | |
}; | |
ProxyChecker::StaticContext ProxyChecker::s_context; | |
} | |
/** | |
* Pass in an URL, get HTTP proxy "hostname:port" back, or false return value to not use any proxy. | |
*/ | |
bool getProxyForUrl(LPCWSTR url, std::wstring& out) | |
{ | |
detail::ProxyChecker pc; | |
if (!pc.process(url) || !pc.proxyAddr()) | |
return false; | |
out = pc.proxyAddr(); | |
return true; | |
} | |
//============================================================================= | |
#include <vector> | |
#include <CommCtrl.h> | |
#pragma comment(lib, "comctl32.lib") | |
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") | |
enum { | |
IDC_CONFIG_REFRESH = 100, | |
IDC_CONFIG_AUTO, | |
IDC_CONFIG_SCRIPT, | |
IDC_CONFIG_SCRIPT_ADDR, | |
IDC_CONFIG_CUSTOM, | |
IDC_CONFIG_CUSTOM_ADDR, | |
IDC_CONFIG_CUSTOM_BYPASS, | |
IDC_INPUT, | |
IDC_RESOLVE, | |
IDC_OUTPUT, | |
IDC_OUTPUT_CLEAR | |
}; | |
INT_PTR WINAPI dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) | |
{ | |
switch(uMsg) { | |
case WM_INITDIALOG: | |
SetFocus(GetDlgItem(hwndDlg, IDC_INPUT)); | |
SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_CONFIG_REFRESH, BN_CLICKED), 0); | |
return FALSE; | |
case WM_DESTROY: | |
PostQuitMessage(0); | |
return TRUE; | |
case WM_CLOSE: | |
DestroyWindow(hwndDlg); | |
return TRUE; | |
case WM_COMMAND: | |
switch(LOWORD(wParam)) { | |
case IDC_CONFIG_REFRESH: { | |
detail::WinHttpCurrentUserIeProxyConfig iecfg; | |
if (!WinHttpGetIEProxyConfigForCurrentUser(&iecfg)) { | |
MessageBox(hwndDlg, L"WinHttpGetIEProxyConfigForCurrentUser() failed.", L"Error", MB_ICONERROR | MB_OK); | |
} else { | |
CheckDlgButton(hwndDlg, IDC_CONFIG_AUTO, iecfg.fAutoDetect ? BST_CHECKED : BST_UNCHECKED); | |
CheckDlgButton(hwndDlg, IDC_CONFIG_SCRIPT, iecfg.lpszAutoConfigUrl ? BST_CHECKED : BST_UNCHECKED); | |
SetDlgItemText(hwndDlg, IDC_CONFIG_SCRIPT_ADDR, iecfg.lpszAutoConfigUrl ? iecfg.lpszAutoConfigUrl : L"(null)"); | |
CheckDlgButton(hwndDlg, IDC_CONFIG_CUSTOM, iecfg.lpszProxy ? BST_CHECKED : BST_UNCHECKED); | |
SetDlgItemText(hwndDlg, IDC_CONFIG_CUSTOM_ADDR, iecfg.lpszProxy ? iecfg.lpszProxy : L"(null)"); | |
SetDlgItemText(hwndDlg, IDC_CONFIG_CUSTOM_BYPASS, iecfg.lpszProxyBypass ? iecfg.lpszProxyBypass : L"(null)"); | |
} | |
break; | |
} | |
case IDC_RESOLVE: { | |
SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_CONFIG_REFRESH, BN_CLICKED), 0); | |
WCHAR url[1024]; | |
GetDlgItemText(hwndDlg, IDC_INPUT, url, _countof(url)); | |
wchar_t* context; | |
for (wchar_t* tok = wcstok_s(url, L" \t\r\n", &context); tok; tok = wcstok_s(NULL, L" \t\r\n", &context)) { | |
std::wstring proxy; | |
bool success; | |
{ | |
detail::ProxyChecker pc; | |
success = pc.process(tok); | |
if (success && pc.proxyAddr()) | |
proxy = pc.proxyAddr(); | |
} | |
WCHAR tmp[1024]; | |
swprintf_s(tmp, L"%s -> %s\n", tok, success ? (proxy.empty() ? L"<direct>" : proxy.c_str()) : L"<failed>"); | |
HWND hwndOutput = GetDlgItem(hwndDlg, IDC_OUTPUT); | |
int pos = GetWindowTextLength(hwndOutput); | |
SendMessage(hwndOutput, EM_SETSEL, pos, pos); | |
SendMessage(hwndOutput, EM_REPLACESEL, FALSE, reinterpret_cast<LPARAM>(tmp)); | |
} | |
break; | |
} | |
case IDC_OUTPUT_CLEAR: | |
SetDlgItemText(hwndDlg, IDC_OUTPUT, L""); | |
break; | |
} | |
return TRUE; | |
} | |
return FALSE; | |
} | |
class DialogTemplate { | |
public: | |
enum Classes { | |
BUTTON = 0x0080, | |
EDIT = 0x0081, | |
STATIC = 0x0082, | |
LISTBOX = 0x0083, | |
SCROLLBAR = 0x0084, | |
COMBOBOX = 0x0085 | |
}; | |
public: | |
DialogTemplate(DWORD dwStyle, DWORD dwExtendedStyle, short x, short y, short cx, short cy, LPCWSTR lpszTitle) | |
{ | |
size_t len = wcslen(lpszTitle) + 1; | |
m_buffer.resize(sizeof(DLGTEMPLATE) + 2 + 2 + 2 * len); | |
DLGTEMPLATE* dlg = reinterpret_cast<DLGTEMPLATE*>(&m_buffer[0]); | |
dlg->style = dwStyle & ~DS_SETFONT; | |
dlg->dwExtendedStyle = dwExtendedStyle; | |
dlg->cdit = 0; | |
dlg->x = x; | |
dlg->y = y; | |
dlg->cx = cx; | |
dlg->cy = cy; | |
WORD* meta = reinterpret_cast<WORD*>(&m_buffer[sizeof(DLGTEMPLATE)]); | |
meta[0] = 0x0000; | |
meta[1] = 0x0000; | |
memcpy(meta + 2, lpszTitle, 2 * len); | |
} | |
void setFont(WORD wFontSize, LPCWSTR lpszFontFace) | |
{ | |
size_t ofs = sizeof(DLGTEMPLATE) + 2 + 2 + 2 * (wcslen(reinterpret_cast<WCHAR*>(&m_buffer[sizeof(DLGTEMPLATE) + 2 + 2])) + 1); | |
size_t len = wcslen(lpszFontFace) + 1; | |
m_buffer.resize(ofs + 2 + 2 * len); | |
DLGTEMPLATE* dlg = reinterpret_cast<DLGTEMPLATE*>(&m_buffer[0]); | |
dlg->style |= DS_SETFONT; | |
dlg->cdit = 0; | |
WORD* meta = reinterpret_cast<WORD*>(&m_buffer[ofs]); | |
meta[0] = wFontSize; | |
memcpy(meta + 1, lpszFontFace, 2 * len); | |
} | |
void addControl(WORD wId, WORD wClassId, short x, short y, short cx, short cy, DWORD dwStyle, DWORD dwExtendedStyle, LPCWSTR lpszTitle) | |
{ | |
size_t pad = (m_buffer.size() & 3) ? 2 : 0; | |
size_t ofs = m_buffer.size() + pad; | |
size_t len = wcslen(lpszTitle) + 1; | |
m_buffer.resize(ofs + sizeof(DLGITEMTEMPLATE) + 2 + 2 + 2 * len + 2); | |
DLGTEMPLATE* dlg = reinterpret_cast<DLGTEMPLATE*>(&m_buffer[0]); | |
dlg->cdit++; | |
DLGITEMTEMPLATE* item = reinterpret_cast<DLGITEMTEMPLATE*>(&m_buffer[ofs]); | |
item->style = dwStyle; | |
item->dwExtendedStyle = dwExtendedStyle; | |
item->x = x; | |
item->y = y; | |
item->cx = cx; | |
item->cy = cy; | |
item->id = wId; | |
WORD* meta = reinterpret_cast<WORD*>(&m_buffer[ofs + sizeof(DLGITEMTEMPLATE)]); | |
meta[0] = 0xFFFF; | |
meta[1] = wClassId; | |
memcpy(meta + 2, lpszTitle, 2 * len); | |
} | |
void addStatic(WORD wId, short x, short y, short cx, short cy, DWORD dwStyle, LPCWSTR lpszTitle) | |
{ | |
addControl(wId, STATIC, x, y, cx, cy, dwStyle | WS_GROUP | WS_VISIBLE, 0, lpszTitle); | |
} | |
void addButton(WORD wId, short x, short y, short cx, short cy, DWORD dwStyle, LPCWSTR lpszTitle) | |
{ | |
addControl(wId, BUTTON, x, y, cx, cy, dwStyle | WS_TABSTOP | WS_VISIBLE, 0, lpszTitle); | |
} | |
void addEdit(WORD wId, short x, short y, short cx, short cy, DWORD dwStyle, LPCWSTR lpszTitle) | |
{ | |
addControl(wId, EDIT, x, y, cx, cy, dwStyle | WS_BORDER | WS_TABSTOP | WS_VISIBLE, 0, lpszTitle); | |
} | |
operator DLGTEMPLATE*() | |
{ | |
return reinterpret_cast<DLGTEMPLATE*>(&m_buffer[0]); | |
} | |
protected: | |
std::vector<BYTE> m_buffer; | |
}; | |
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) | |
{ | |
INITCOMMONCONTROLSEX icc; | |
icc.dwSize = sizeof(icc); | |
icc.dwICC = ICC_STANDARD_CLASSES; | |
if (!InitCommonControlsEx(&icc)) | |
return -1; | |
HWND hMainWnd; | |
{ | |
DialogTemplate dlg(DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_SYSMENU, WS_EX_WINDOWEDGE, 0, 0, 304, 319, L"Proxy Test"); | |
dlg.setFont(8, L"MS Shell Dlg"); | |
dlg.addControl(-1, DialogTemplate::BUTTON, 7, 7, 290, 115, BS_GROUPBOX | WS_VISIBLE, 0, L"Proxy settings for the current user"); | |
dlg.addButton (IDC_CONFIG_REFRESH, 238, 4, 50, 14, BS_PUSHBUTTON, L"Refresh"); | |
dlg.addButton (IDC_CONFIG_AUTO, 17, 21, 270, 8, BS_CHECKBOX, L"Automatically detect settings"); | |
dlg.addButton (IDC_CONFIG_SCRIPT, 17, 35, 270, 8, BS_CHECKBOX, L"Use automatic configuration script:"); | |
dlg.addEdit (IDC_CONFIG_SCRIPT_ADDR, 27, 45, 260, 13, ES_AUTOHSCROLL | ES_READONLY, L""); | |
dlg.addButton (IDC_CONFIG_CUSTOM, 17, 65, 270, 8, BS_CHECKBOX, L"Use custom proxy server(s):"); | |
dlg.addEdit (IDC_CONFIG_CUSTOM_ADDR, 27, 75, 260, 13, ES_AUTOHSCROLL | ES_READONLY, L""); | |
dlg.addStatic (-1, 27, 90, 260, 8, SS_LEFT, L"Exceptions:"); | |
dlg.addEdit (IDC_CONFIG_CUSTOM_BYPASS, 27, 100, 260, 13, ES_AUTOHSCROLL | ES_READONLY, L""); | |
dlg.addStatic (-1, 7, 132, 280, 8, SS_LEFT, L"URLs to test (separated by whitespace):"); | |
dlg.addEdit (IDC_INPUT, 7, 142, 225, 70, WS_VSCROLL | ES_MULTILINE | ES_WANTRETURN, | |
L"http://localhost/\r\n" | |
L"http://127.0.0.1/\r\n" | |
L"http://fud.cz/\r\n" | |
L"http://pluto/something.mp3\r\n" | |
L"http://foo.bar:secret.pass@long.domain.fud.cz/blah/blah/blah?meh=wtf@url&from:hell\r\n"); | |
dlg.addButton (IDC_RESOLVE, 237, 142, 50, 14, BS_DEFPUSHBUTTON, L"Resolve"); | |
dlg.addStatic (-1, 7, 216, 225, 8, SS_LEFT, L"Output:"); | |
dlg.addEdit (IDC_OUTPUT, 7, 227, 225, 85, WS_VSCROLL | ES_MULTILINE | ES_READONLY, L""); | |
dlg.addButton (IDC_OUTPUT_CLEAR, 238, 227, 50, 14, BS_PUSHBUTTON, L"Clear"); | |
hMainWnd = CreateDialogIndirect(GetModuleHandle(NULL), dlg, NULL, &dialogProc); | |
} | |
if (!hMainWnd) | |
return -1; | |
ShowWindow(hMainWnd, nCmdShow); | |
MSG msg; | |
while (GetMessage(&msg, NULL, 0, 0) > 0) { | |
if (IsDialogMessage(hMainWnd, &msg)) | |
continue; | |
TranslateMessage(&msg); | |
DispatchMessage(&msg); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment