Windows XP 和 Windows 7 中的不同程序行为
Different program behaviour in Windows XP and Windows 7
这段代码在window的两个线程中从上到下画了两条竖条纹(每个线程对应的函数是thread_func
)。第一个线程绘制左侧条纹的一部分,然后第二个线程绘制右侧条纹的一部分,然后再次绘制第一个,依此类推。使用信号量和临界区来确保此顺序。
#include <windows.h>
#include <cstdint>
HDC hDC;
HDC hDCMem;
HBITMAP hbitmap;
HWND hwnd;
int ScreenMaxX;
int ScreenMaxY;
short pattern[8]={~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF};
HBRUSH brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern));
void bar(int nLeft, int nTop, int nRight, int nBottom)
{
RECT rect;
rect.left = nLeft;
rect.right = nRight;
rect.top = nTop;
rect.bottom = nBottom;
::SetTextColor(hDCMem, 0xFF00FF);
::SetBkColor(hDCMem, 0xFF00FF);
//brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern));
::FillRect(hDCMem, &rect, brush);
}
void flush(){
::BitBlt(hDC, 0, 0, ScreenMaxX, ScreenMaxY, hDCMem, 0, 0, SRCCOPY);
}
CRITICAL_SECTION graphics_cs;
uint8_t thread_cnt=0;
uint8_t total_threads=2;
HANDLE turnstile1=CreateSemaphoreW(nullptr, 0, 2, nullptr);
void thread_func(int num){
int x,y;
if(num==0){
x=20; y=0;
} else {
x=110; y=0;
}
while(true) {
while(true) {
EnterCriticalSection(&graphics_cs);
if (thread_cnt == num) {
thread_cnt++;
bar(x, y, x+40, y+40);
y+=1;
//flush();
if(thread_cnt==total_threads){
thread_cnt = 0;
flush();
ReleaseSemaphore(turnstile1, total_threads, nullptr);
}
LeaveCriticalSection(&graphics_cs);
break;
} else {
LeaveCriticalSection(&graphics_cs);
}
}
WaitForSingleObject(turnstile1, INFINITE);
Sleep(100);
}
}
void mainx ()
{
InitializeCriticalSection(&graphics_cs);
for(int i=0; i<total_threads; i++){
CreateThread (nullptr, 0, (LPTHREAD_START_ROUTINE)thread_func, (LPVOID)i, 0, nullptr);
}
}
DWORD Th(LPVOID param)
{
(void)param;
::SetWindowPos(hwnd, HWND_TOP,
10,
10,
400,
500,
SWP_SHOWWINDOW
);
mainx();
flush();
return 0;
}
DWORD g_nMainThreadID;
//processing main window messages
long FAR PASCAL WindowProc(HWND hWnd,UINT message, WPARAM wParam,LPARAM lParam )
{
switch (message)
{
case WM_PAINT: flush();
break;
case WM_DESTROY: PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )
{
(void)hPrevInstance, (void)lpCmdLine;
WNDCLASS wc;
MSG msg;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "Menu_one";
wc.lpszClassName = "NAME";
if (!RegisterClass(&wc)) {return 0; };
//main window
hwnd = CreateWindow("NAME",
"!",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
HWND_DESKTOP,
NULL,
hInstance,
NULL
);
ScreenMaxX = ::GetSystemMetrics(SM_CXSCREEN);
ScreenMaxY = ::GetSystemMetrics(SM_CYSCREEN);
hDC = ::GetDC(hwnd);
hDCMem = ::CreateCompatibleDC(hDC);
hbitmap = ::CreateCompatibleBitmap(hDC, ScreenMaxX, ScreenMaxY );
::SelectObject(hDCMem, hbitmap);
auto hbrush = (HBRUSH)::GetStockObject(WHITE_BRUSH);
::SelectObject(hDCMem, hbrush);
::PatBlt(hDCMem, 0,0, ScreenMaxX, ScreenMaxY, PATCOPY );
::DeleteObject(hbrush);
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Th, (LPVOID)hwnd, 0,&g_nMainThreadID);
ShowWindow(hwnd, nShowCmd);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
在Windows 7中,条纹的绘制速度与预期相同。但在 Windows XP 中速度不同:
如果我取消注释 //brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern));
或 //flush();
行,在 Windows XP 中的绘图速度将是相同的。为什么这可以解决问题,为什么初始代码的行为在 Windows 的不同版本中不同?
更新
当我在 bar
和 flush
调用 thread_func
之后添加 std::cout<<"num = "<<num<<" : bar call\n";
和 std::cout<<"num = "<<num<<" : flush call\n";
时,输出是
num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
...
顺序似乎是正确的,但是在刷新调用后没有立即绘制左边的条纹。
从我的评论中提取一些信息作为答案。
请注意,GDI 批处理操作可能会导致较慢机器上的突发渲染。虽然 GDI 在 XP 和 7 上都进行批处理,但实际上在 7 上这似乎不再是一个大问题。尝试调用 GdiSetBatchLimit 将限制设置为 1。这将导致 GDI 在每次调用后刷新,这可能总体上更慢,但应该消除突发行为。
这段代码在window的两个线程中从上到下画了两条竖条纹(每个线程对应的函数是thread_func
)。第一个线程绘制左侧条纹的一部分,然后第二个线程绘制右侧条纹的一部分,然后再次绘制第一个,依此类推。使用信号量和临界区来确保此顺序。
#include <windows.h>
#include <cstdint>
HDC hDC;
HDC hDCMem;
HBITMAP hbitmap;
HWND hwnd;
int ScreenMaxX;
int ScreenMaxY;
short pattern[8]={~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF};
HBRUSH brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern));
void bar(int nLeft, int nTop, int nRight, int nBottom)
{
RECT rect;
rect.left = nLeft;
rect.right = nRight;
rect.top = nTop;
rect.bottom = nBottom;
::SetTextColor(hDCMem, 0xFF00FF);
::SetBkColor(hDCMem, 0xFF00FF);
//brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern));
::FillRect(hDCMem, &rect, brush);
}
void flush(){
::BitBlt(hDC, 0, 0, ScreenMaxX, ScreenMaxY, hDCMem, 0, 0, SRCCOPY);
}
CRITICAL_SECTION graphics_cs;
uint8_t thread_cnt=0;
uint8_t total_threads=2;
HANDLE turnstile1=CreateSemaphoreW(nullptr, 0, 2, nullptr);
void thread_func(int num){
int x,y;
if(num==0){
x=20; y=0;
} else {
x=110; y=0;
}
while(true) {
while(true) {
EnterCriticalSection(&graphics_cs);
if (thread_cnt == num) {
thread_cnt++;
bar(x, y, x+40, y+40);
y+=1;
//flush();
if(thread_cnt==total_threads){
thread_cnt = 0;
flush();
ReleaseSemaphore(turnstile1, total_threads, nullptr);
}
LeaveCriticalSection(&graphics_cs);
break;
} else {
LeaveCriticalSection(&graphics_cs);
}
}
WaitForSingleObject(turnstile1, INFINITE);
Sleep(100);
}
}
void mainx ()
{
InitializeCriticalSection(&graphics_cs);
for(int i=0; i<total_threads; i++){
CreateThread (nullptr, 0, (LPTHREAD_START_ROUTINE)thread_func, (LPVOID)i, 0, nullptr);
}
}
DWORD Th(LPVOID param)
{
(void)param;
::SetWindowPos(hwnd, HWND_TOP,
10,
10,
400,
500,
SWP_SHOWWINDOW
);
mainx();
flush();
return 0;
}
DWORD g_nMainThreadID;
//processing main window messages
long FAR PASCAL WindowProc(HWND hWnd,UINT message, WPARAM wParam,LPARAM lParam )
{
switch (message)
{
case WM_PAINT: flush();
break;
case WM_DESTROY: PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )
{
(void)hPrevInstance, (void)lpCmdLine;
WNDCLASS wc;
MSG msg;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "Menu_one";
wc.lpszClassName = "NAME";
if (!RegisterClass(&wc)) {return 0; };
//main window
hwnd = CreateWindow("NAME",
"!",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
HWND_DESKTOP,
NULL,
hInstance,
NULL
);
ScreenMaxX = ::GetSystemMetrics(SM_CXSCREEN);
ScreenMaxY = ::GetSystemMetrics(SM_CYSCREEN);
hDC = ::GetDC(hwnd);
hDCMem = ::CreateCompatibleDC(hDC);
hbitmap = ::CreateCompatibleBitmap(hDC, ScreenMaxX, ScreenMaxY );
::SelectObject(hDCMem, hbitmap);
auto hbrush = (HBRUSH)::GetStockObject(WHITE_BRUSH);
::SelectObject(hDCMem, hbrush);
::PatBlt(hDCMem, 0,0, ScreenMaxX, ScreenMaxY, PATCOPY );
::DeleteObject(hbrush);
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Th, (LPVOID)hwnd, 0,&g_nMainThreadID);
ShowWindow(hwnd, nShowCmd);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
在Windows 7中,条纹的绘制速度与预期相同。但在 Windows XP 中速度不同:
如果我取消注释 //brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern));
或 //flush();
行,在 Windows XP 中的绘图速度将是相同的。为什么这可以解决问题,为什么初始代码的行为在 Windows 的不同版本中不同?
更新
当我在 bar
和 flush
调用 thread_func
之后添加 std::cout<<"num = "<<num<<" : bar call\n";
和 std::cout<<"num = "<<num<<" : flush call\n";
时,输出是
num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
num = 0 : bar call
num = 1 : bar call
num = 1 : flush call
...
顺序似乎是正确的,但是在刷新调用后没有立即绘制左边的条纹。
从我的评论中提取一些信息作为答案。
请注意,GDI 批处理操作可能会导致较慢机器上的突发渲染。虽然 GDI 在 XP 和 7 上都进行批处理,但实际上在 7 上这似乎不再是一个大问题。尝试调用 GdiSetBatchLimit 将限制设置为 1。这将导致 GDI 在每次调用后刷新,这可能总体上更慢,但应该消除突发行为。