Discussion:
Serious GDI Multithreading bug while printing to metafiles
(too old to reply)
Christian Kaiser
2008-04-10 13:24:20 UTC
Permalink
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);
Mark Salsbery [MVP]
2008-04-10 17:51:27 UTC
Permalink
There's nothing thread safe about GDI calls.

Is there a thread synchronization issue there?

Mark
--
Mark Salsbery
Microsoft MVP - Visual C++
Post by Christian Kaiser
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
...
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
...
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);
Christian Kaiser
2008-04-11 06:13:52 UTC
Permalink
I would expect that GDI calls done to DIFFERENT DCs are thread-safe.
There's nothing the threads have in common.

The problem appears only when recording into metafiles in parallel. Of
course it's a threading issue - inside GDI.

Christian
Post by Mark Salsbery [MVP]
There's nothing thread safe about GDI calls.
Is there a thread synchronization issue there?
Mark
--
Mark Salsbery
Microsoft MVP - Visual C++
Post by Christian Kaiser
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
...
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
...
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);
Christian Kaiser
2008-04-15 10:27:45 UTC
Permalink
For the record (google et al):

MS support just told me that this is in fact so, and that this is a
"by design-issue", and that it is unlikely to be fixed.

Christian
Post by Christian Kaiser
I would expect that GDI calls done to DIFFERENT DCs are thread-safe.
There's nothing the threads have in common.
The problem appears only when recording into metafiles in parallel. Of
course it's a threading issue - inside GDI.
Christian
Post by Mark Salsbery [MVP]
There's nothing thread safe about GDI calls.
Is there a thread synchronization issue there?
Mark
--
Mark Salsbery
Microsoft MVP - Visual C++
Post by Christian Kaiser
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
...
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
...
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);
Richard Russell
2008-04-15 13:20:25 UTC
Permalink
Post by Christian Kaiser
MS support just told me that this is in fact so, and that this
is a "by design-issue", and that it is unlikely to be fixed.
So, is the practical solution (rather than serialising the whole
sequence) to use multiple *processes* rather than multiple *threads*?

Richard.
http://www.rtrussell.co.uk/
To reply by email change 'news' to my forename.
Stefan Lechner
2008-05-06 11:12:02 UTC
Permalink
Post by Christian Kaiser
MS support just told me that this is in fact so, and that this is a
"by design-issue", and that it is unlikely to be fixed.
Christian
I've to pickup this thread again, this "by design-issue" seams to be a
rather new design issue.

We have our application running for several years, without having this
problem. Now on two brand new w2k3 sp2 server it happens.
Btw. in my case the metafile looses, if any, always the sequence
EXTCREATEFONTINDIRECT and the following SELECTOBJECT . Every thing else is
recorded correctly by now.

Next, the same issue exists when playing a metafile to a printer. In the
resulting PCL there are also font selections missed, in the same manner as
found when recording metafiles.
Afaik the spooler also creates metafiles for first.

To split up the appilcation into several processes is not practicable, since
we have up to 50 or more worker threads and multiple instances of the
application running. The resulting IPC will also slow down the application.

In our own test environment I can't reproduce this problem at all, W2k3 sp1
and w2k3 sp2, both dual processor 2GB ram, more or less the same boxes as
those having the problem.

Since we are producing unreadable output, I would be glad to have a more
detailed explanation how this "design-issue" came in, why it's found only on
some newer machines and what I really have to synchronize to get back to
correctly printed output.


regards

Stefan
Christian Kaiser
2008-05-13 14:00:32 UTC
Permalink
Well you need to synchronize (serialize) all GDI commands that may end
up in the metafile.

That's basically it. The only reliable solution.

MS states: "The recommendation is for the caller to handle all
synchronization when writing Metafiles. As this is by design, it is
unlikely for any change to be implemented.".

Perhaps they removed even more synchronization objects in the Win2K3
server... I tested on both XP and Vista, and both OSes show that
behaviour. If you like, I can send you example code that demonstrates
the problem very easily and reproducable (Visual Studio 8) -> mail to
"chk at combit.net". But it will not help you more than being able to
demonstrate the shortcomings of Windows ;-(

Christian
Post by Stefan Lechner
Post by Christian Kaiser
MS support just told me that this is in fact so, and that this is a
"by design-issue", and that it is unlikely to be fixed.
Christian
I've to pickup this thread again, this "by design-issue" seams to be a
rather new design issue.
We have our application running for several years, without having this
problem. Now on two brand new w2k3 sp2 server it happens.
Btw. in my case the metafile looses, if any, always the sequence
EXTCREATEFONTINDIRECT and the following SELECTOBJECT . Every thing else is
recorded correctly by now.
Next, the same issue exists when playing a metafile to a printer. In the
resulting PCL there are also font selections missed, in the same manner as
found when recording metafiles.
Afaik the spooler also creates metafiles for first.
To split up the appilcation into several processes is not
practicable, since
we have up to 50 or more worker threads and multiple instances of the
application running. The resulting IPC will also slow down the
application.
In our own test environment I can't reproduce this problem at all, W2k3 sp1
and w2k3 sp2, both dual processor 2GB ram, more or less the same boxes as
those having the problem.
Since we are producing unreadable output, I would be glad to have a more
detailed explanation how this "design-issue" came in, why it's found only on
some newer machines and what I really have to synchronize to get back to
correctly printed output.
regards
Stefan
RMurdock
2008-04-10 18:04:23 UTC
Permalink
I may not be fully understanding the problem here, but what I am getting=
=

is that the GDI calls are made, and then the DC are deleted, but not all=
=

GDI calls end up in the EMF. This would seem to indicate that the DC is=
=

being closed before the queued call get written.

Have you tried calling GdiFlush to make sure that the GDI queue is flush=
ed =

before calling the DeleteDC?
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
...
hObj[1]=3DCreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_5[]=3D{ 5 };
ExtTextOutW(hDC, 4,4,0,NULL,L"x",1,Dx_5);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
DeleteObject(hObj[1]);
hObj[1]=3DCreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_6[]=3D{ 5 };
ExtTextOutW(hDC, 5,5,0,NULL,L"x",1,Dx_6);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
DeleteObject(hObj[1]);
hObj[1]=3DCreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_7[]=3D{ 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
...
hObj[1]=3DCreateFont(-10,0,0,0,0,0,0,0,0,0,0,0,0,"Arial");
SelectObject(hDC, hObj[1]);
const int Dx_62[]=3D{ 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[]=3D{ 5 };
ExtTextOutW(hDC, 62,62,0,NULL,L"x",1,Dx_63);
const int Dx_64[]=3D{ 5 };
ExtTextOutW(hDC, 63,63,0,NULL,L"x",1,Dx_64);
SelectObject(hDC, GetStockObject(DEVICE_DEFAULT_FONT));
const int Dx_65[]=3D{ 5 };
ExtTextOutW(hDC, 64,64,0,NULL,L"x",1,Dx_65);
const int Dx_66[]=3D{ 5 };
ExtTextOutW(hDC, 65,65,0,NULL,L"x",1,Dx_66);
const int Dx_67[]=3D{ 5 };
ExtTextOutW(hDC, 66,66,0,NULL,L"x",1,Dx_67);
const int Dx_68[]=3D{ 5 };
ExtTextOutW(hDC, 67,67,0,NULL,L"x",1,Dx_68);
const int Dx_69[]=3D{ 5 };
ExtTextOutW(hDC, 68,68,0,NULL,L"x",1,Dx_69);
const int Dx_70[]=3D{ 5 };
ExtTextOutW(hDC, 69,69,0,NULL,L"x",1,Dx_70);
const int Dx_71[]=3D{ 5 };
ExtTextOutW(hDC, 70,70,0,NULL,L"x",1,Dx_71);
const int Dx_72[]=3D{ 5 };
ExtTextOutW(hDC, 71,71,0,NULL,L"x",1,Dx_72);
const int Dx_73[]=3D{ 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 =3D {0};
TCHAR szFile[MAX_PATH];
_stprintf(szFile,"test_%08x.emf",::GetCurrentThreadId());
pd.lStructSize =3D sizeof(pd);
pd.Flags =3D PD_RETURNDC | PD_RETURNDEFAULT;
::PrintDlg(&pd);
RECT rcPage =3D {0,0,10000,10000};
HDC hMetaDC =3D ::CreateEnhMetaFile(pd.hDC,szFile,&rcPage,"Hallo");
for (int i =3D 0; i < 2000; ++i)
{
HFONT hFont =3D ::CreateFont(
-10,
0,
0,
0,
0,
0,
0,
0,
ANSI_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
0,
"Arial");
HFONT hOldFont =3D SelectFont(hMetaDC,hFont);
::ExtTextOut(hMetaDC,
i,
i,
0,
NULL,
"x",
1,
NULL);
SelectFont(hMetaDC,hOldFont);
DeleteFont(hFont);
}
HENHMETAFILE hEMF =3D ::CloseEnhMetaFile(hMetaDC);
::DeleteObject(hEMF);
::DeleteDC(pd.hDC);
-- =

Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
Christian Kaiser
2008-04-11 06:12:21 UTC
Permalink
Thank you, but even GdiFlush() does not help (neither before clising
the EMF nor before deleting the DC).

Random records IN THE MIDDLE are lost.

Christian

"RMurdock" <***@rocketmail.com> wrote in message news:***@amandus.sdicgm.com...
I may not be fully understanding the problem here, but what I am
getting
is that the GDI calls are made, and then the DC are deleted, but not
all
GDI calls end up in the EMF. This would seem to indicate that the DC
is
being closed before the queued call get written.

Have you tried calling GdiFlush to make sure that the GDI queue is
flushed
before calling the DeleteDC?
Post by Christian Kaiser
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
...
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
...
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);
--
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
Loading...