Christian Kaiser
2008-04-10 13:24:20 UTC
Customers of ours reported a bug in our software that causes metafiles
to be incorrect.
We now reduced the bug using the appended code, which is run by
multiple threads simultaneously. The code opens a printer DC, uses
that as reference DC for a new metafile DC, creates/selects a font,
draws a text, deselects and destroys the font, then destroys the
metafile DC and the printer DC. Trivial code in my eyes.
This should result in repeated calls like (decompiled to C code using
EMF decoder, special thanks to Feng Yuan who makes that very easy):
...
hObj[1]=CreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_5[]={ 5 };
ExtTextOutW(hDC, 4,4,0,NULL,L"x",1,Dx_5);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
DeleteObject(hObj[1]);
hObj[1]=CreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_6[]={ 5 };
ExtTextOutW(hDC, 5,5,0,NULL,L"x",1,Dx_6);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
DeleteObject(hObj[1]);
hObj[1]=CreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_7[]={ 5 };
ExtTextOutW(hDC, 6,6,0,NULL,L"x",1,Dx_7);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
DeleteObject(hObj[1]);
...
and most of the times it does.
When run in multiple concurrent threads, however, some of the
repetitions miss some GDI calls:
...
hObj[1]=CreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_62[]={ 5 };
ExtTextOutW(hDC, 61,61,0,NULL,L"x",1,Dx_62);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
DeleteObject(hObj[1]);
const int Dx_63[]={ 5 };
ExtTextOutW(hDC, 62,62,0,NULL,L"x",1,Dx_63);
const int Dx_64[]={ 5 };
ExtTextOutW(hDC, 63,63,0,NULL,L"x",1,Dx_64);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
const int Dx_65[]={ 5 };
ExtTextOutW(hDC, 64,64,0,NULL,L"x",1,Dx_65);
const int Dx_66[]={ 5 };
ExtTextOutW(hDC, 65,65,0,NULL,L"x",1,Dx_66);
const int Dx_67[]={ 5 };
ExtTextOutW(hDC, 66,66,0,NULL,L"x",1,Dx_67);
const int Dx_68[]={ 5 };
ExtTextOutW(hDC, 67,67,0,NULL,L"x",1,Dx_68);
const int Dx_69[]={ 5 };
ExtTextOutW(hDC, 68,68,0,NULL,L"x",1,Dx_69);
const int Dx_70[]={ 5 };
ExtTextOutW(hDC, 69,69,0,NULL,L"x",1,Dx_70);
const int Dx_71[]={ 5 };
ExtTextOutW(hDC, 70,70,0,NULL,L"x",1,Dx_71);
const int Dx_72[]={ 5 };
ExtTextOutW(hDC, 71,71,0,NULL,L"x",1,Dx_72);
const int Dx_73[]={ 5 };
ExtTextOutW(hDC, 72,72,0,NULL,L"x",1,Dx_73);
...
This happens at least in Windows XP and in Vista.
Strange is, the CreateFont(), SelectFont(), ..., DeleteFont() is being
executed in the DC correctly, and the DC state reflects this (we can
see this in our application, which uses the selected font for some
layouting - these calculations are correct). The GDI calls are just
not stored in the metafile. There is no failure in CreateFont or
SelectFont (I left out of the checking code in the demonstration code
below, for clarity reasons).
Next astonishing thing is that setting the ProcessAffinityMask to one
processor does not help. It still happens - this, for me, was really
unexpected.
When the code below is executed multiple times, it should create
similar EMF files - but they all vary in size as indication there's
something going wrong. Looking into them, it can be seen that there
are a lot of missing records.
Serializing the whole sequence is a possible solution (a part, for
example, only CreateFont/SelectFont does not help), but scales
extremely bad with multiple processors ;-(. Especially when the GDI
calls are not so easily locateable, but all over thousands of code
lines.
Is a workaround known? This is a serious problem in multithreaded
printing (using metafiles).
Christian
------------------------------
PRINTDLG pd = {0};
TCHAR szFile[MAX_PATH];
_stprintf(szFile,"test_%08x.emf",::GetCurrentThreadId());
pd.lStructSize = sizeof(pd);
pd.Flags = PD_RETURNDC | PD_RETURNDEFAULT;
::PrintDlg(&pd);
RECT rcPage = {0,0,10000,10000};
HDC hMetaDC = ::CreateEnhMetaFile(pd.hDC,szFile,&rcPage,"Hallo");
for (int i = 0; i < 2000; ++i)
{
HFONT hFont = ::CreateFont(
-10,
0,
0,
0,
0,
0,
0,
0,
ANSI_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
0,
"Arial");
HFONT hOldFont = SelectFont(hMetaDC,hFont);
::ExtTextOut(hMetaDC,
i,
i,
0,
NULL,
"x",
1,
NULL);
SelectFont(hMetaDC,hOldFont);
DeleteFont(hFont);
}
HENHMETAFILE hEMF = ::CloseEnhMetaFile(hMetaDC);
::DeleteObject(hEMF);
::DeleteDC(pd.hDC);
to be incorrect.
We now reduced the bug using the appended code, which is run by
multiple threads simultaneously. The code opens a printer DC, uses
that as reference DC for a new metafile DC, creates/selects a font,
draws a text, deselects and destroys the font, then destroys the
metafile DC and the printer DC. Trivial code in my eyes.
This should result in repeated calls like (decompiled to C code using
EMF decoder, special thanks to Feng Yuan who makes that very easy):
...
hObj[1]=CreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_5[]={ 5 };
ExtTextOutW(hDC, 4,4,0,NULL,L"x",1,Dx_5);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
DeleteObject(hObj[1]);
hObj[1]=CreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_6[]={ 5 };
ExtTextOutW(hDC, 5,5,0,NULL,L"x",1,Dx_6);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
DeleteObject(hObj[1]);
hObj[1]=CreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_7[]={ 5 };
ExtTextOutW(hDC, 6,6,0,NULL,L"x",1,Dx_7);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
DeleteObject(hObj[1]);
...
and most of the times it does.
When run in multiple concurrent threads, however, some of the
repetitions miss some GDI calls:
...
hObj[1]=CreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_62[]={ 5 };
ExtTextOutW(hDC, 61,61,0,NULL,L"x",1,Dx_62);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
DeleteObject(hObj[1]);
const int Dx_63[]={ 5 };
ExtTextOutW(hDC, 62,62,0,NULL,L"x",1,Dx_63);
const int Dx_64[]={ 5 };
ExtTextOutW(hDC, 63,63,0,NULL,L"x",1,Dx_64);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
const int Dx_65[]={ 5 };
ExtTextOutW(hDC, 64,64,0,NULL,L"x",1,Dx_65);
const int Dx_66[]={ 5 };
ExtTextOutW(hDC, 65,65,0,NULL,L"x",1,Dx_66);
const int Dx_67[]={ 5 };
ExtTextOutW(hDC, 66,66,0,NULL,L"x",1,Dx_67);
const int Dx_68[]={ 5 };
ExtTextOutW(hDC, 67,67,0,NULL,L"x",1,Dx_68);
const int Dx_69[]={ 5 };
ExtTextOutW(hDC, 68,68,0,NULL,L"x",1,Dx_69);
const int Dx_70[]={ 5 };
ExtTextOutW(hDC, 69,69,0,NULL,L"x",1,Dx_70);
const int Dx_71[]={ 5 };
ExtTextOutW(hDC, 70,70,0,NULL,L"x",1,Dx_71);
const int Dx_72[]={ 5 };
ExtTextOutW(hDC, 71,71,0,NULL,L"x",1,Dx_72);
const int Dx_73[]={ 5 };
ExtTextOutW(hDC, 72,72,0,NULL,L"x",1,Dx_73);
...
This happens at least in Windows XP and in Vista.
Strange is, the CreateFont(), SelectFont(), ..., DeleteFont() is being
executed in the DC correctly, and the DC state reflects this (we can
see this in our application, which uses the selected font for some
layouting - these calculations are correct). The GDI calls are just
not stored in the metafile. There is no failure in CreateFont or
SelectFont (I left out of the checking code in the demonstration code
below, for clarity reasons).
Next astonishing thing is that setting the ProcessAffinityMask to one
processor does not help. It still happens - this, for me, was really
unexpected.
When the code below is executed multiple times, it should create
similar EMF files - but they all vary in size as indication there's
something going wrong. Looking into them, it can be seen that there
are a lot of missing records.
Serializing the whole sequence is a possible solution (a part, for
example, only CreateFont/SelectFont does not help), but scales
extremely bad with multiple processors ;-(. Especially when the GDI
calls are not so easily locateable, but all over thousands of code
lines.
Is a workaround known? This is a serious problem in multithreaded
printing (using metafiles).
Christian
------------------------------
PRINTDLG pd = {0};
TCHAR szFile[MAX_PATH];
_stprintf(szFile,"test_%08x.emf",::GetCurrentThreadId());
pd.lStructSize = sizeof(pd);
pd.Flags = PD_RETURNDC | PD_RETURNDEFAULT;
::PrintDlg(&pd);
RECT rcPage = {0,0,10000,10000};
HDC hMetaDC = ::CreateEnhMetaFile(pd.hDC,szFile,&rcPage,"Hallo");
for (int i = 0; i < 2000; ++i)
{
HFONT hFont = ::CreateFont(
-10,
0,
0,
0,
0,
0,
0,
0,
ANSI_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
0,
"Arial");
HFONT hOldFont = SelectFont(hMetaDC,hFont);
::ExtTextOut(hMetaDC,
i,
i,
0,
NULL,
"x",
1,
NULL);
SelectFont(hMetaDC,hOldFont);
DeleteFont(hFont);
}
HENHMETAFILE hEMF = ::CloseEnhMetaFile(hMetaDC);
::DeleteObject(hEMF);
::DeleteDC(pd.hDC);