EDN Admin
Well-known member
The basics of the application are this. Sharepoint Webpart calls webreference, remote dll (web service), which then calls the internal application dlls. We have a web part that produces a chart image. The chart is built and then turned into an image
all in the internal application dlls and then returned to the web part for display. Using LoadRunner and multi-user testing the chart started to break.
Here is the basic code flow.
First the Metafile is created:
<div style="color:Black;background-color:White; <pre>
HDC hdcRef = GetDC(NULL);
<span style="color:Blue; int iWidthMM = GetDeviceCaps(hdcRef, HORZSIZE);
<span style="color:Blue; int iHeightMM = GetDeviceCaps(hdcRef, VERTSIZE);
<span style="color:Blue; int iWidthPels = GetDeviceCaps(hdcRef, HORZRES);
<span style="color:Blue; int iHeightPels = GetDeviceCaps(hdcRef, VERTRES);
RECT rect = { 0 };
rect.right = (m_lWidth * iWidthMM * 100)/iWidthPels;
rect.bottom = (m_lHeight * iHeightMM * 100)/iHeightPels;
HDC hDC = CreateEnhMetaFile(hdcRef, NULL, &rect, NULL);
<span style="color:Blue; if( hDC == NULL )
{
ReleaseDC(NULL, hdcRef);
<span style="color:Blue; return E_FAIL;
}
[/code]
Then there is a case statement based on what chart they are creating. For example:
<div style="color:Black;background-color:White; <pre>
hr = GenerateContPerfChart( session, hDC );
[/code]
Then the methods ends with this block:
<div style="color:Black;background-color:White; <pre>
HENHMETAFILE hemf = CloseEnhMetaFile(hDC);
<span style="color:Blue; if( hr == S_OK )
{
RGBQUAD* pPalette = 0;
<span style="color:Blue; if( iFormat != imgFormatJPG )
pPalette = (id != CH_VAC) ? pal1 : pal2;
hr = GetEMFType(hemf, m_lWidth, m_lHeight, COLE2T(bstrFile), iFormat, pPalette );
}
DeleteEnhMetaFile(hemf);
ReleaseDC(NULL, hdcRef);
[/code]
The body of GetEMFType is:
<div style="color:Black;background-color:White; <pre>
<span style="color:Blue; if (FAILED(InitializeGdiPlus()))
<span style="color:Blue; return hr;
GUID ImageType[5] = {
Gdiplus::ImageFormatEMF,
Gdiplus::ImageFormatBMP,
Gdiplus::ImageFormatJPEG,
Gdiplus::ImageFormatPNG,
Gdiplus::ImageFormatGIF
};
Gdiplus::Status status;
CLSID clsidEncoder = CLSID_NULL;
hr = GetGdiPlusImageCodec(ImageType[iFormat], clsidEncoder);
<span style="color:Blue; if (SUCCEEDED(hr))
{
USES_CONVERSION_EX;
LPCWSTR pwszFileName = T2CW_EX( lpszFile, _ATL_SAFE_ALLOCA_DEF_THRESHOLD );
<span style="color:Blue; if ( NULL != pwszFileName )
{
Gdiplus::Metafile metafile(hemf, FALSE);
status = metafile.Save(pwszFileName, &clsidEncoder, NULL);
<span style="color:Blue; if( status != Gdiplus::Ok )
{
hr = E_FAIL;
}
}
<span style="color:Blue; else
hr = E_OUTOFMEMORY;
}
<span style="color:Blue; return hr;
[/code]
Originally we called GdiplusStartup in this routine, but when started getting errors on the call when in multi-user. I read up on GdiplusStartup and discovered that it should only be called once and we were calling it a ton. So, we added the InitilizeGdiPlus
that creates an event that we basically use as a flag to know whether or not to call GdiplusStartup. If the event doesnt exist we call startup and create the event, if it does exist we skip the startup. Here is the internals of the InitializeGdiPlus
method:
<div style="color:Black;background-color:White; <pre>
HRESULT InitializeGdiPlus()
{
HRESULT hr = S_OK;
<span style="color:Green; //
<span style="color:Green; // Create an Event object to indicate if GDI+ has been initialized in this process.
<span style="color:Green; //
<span style="color:Green; // NOTE: We have no way of knowing when the process is finished using GDI+, so
<span style="color:Green; // the GdiplusShutdown method will never get called. Hopefully it will get
<span style="color:Green; // cleaned up properly when the process exits...
<span style="color:Green; //
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, <span style="color:#A31515; "WSINITGDIPLUS");
<span style="color:Blue; if (NULL != hEvent)
{
<span style="color:Green; //
<span style="color:Green; // If the event does not already exist, then initialize GDI+
<span style="color:Green; //
<span style="color:Blue; if (ERROR_ALREADY_EXISTS != GetLastError())
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
gdiplusStartupInput.GdiplusVersion = 1;
gdiplusStartupInput.DebugEventCallback = NULL;
ULONG_PTR gdiplusToken;
<span style="color:Blue; if (Gdiplus::Ok != GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL))
<span style="color:Blue; return E_FAIL;
}
}
<span style="color:Blue; else
{
<span style="color:Green; // Failed to create the event
DWORD dwError = GetLastError();
hr = E_FAIL;
}
<span style="color:Blue; return hr;
}
[/code]
Now the error we get is on the call to HENHMETAFILE hemf = CloseEnhMetaFile(hDC); in the third code block I included. hemf comes back as NULL and GetLastError tells me ERROR_NOT_ENOUGH_MEMORY.
I am searching every resource I can but havent really found a good starting point to debug the issue. Any help would be greatly appreciated. I am new to the metafile and GdiPlus code.
As for environment, all these dlls are running in Windows Server 2003 R2 with IIS 6.
View the full article
all in the internal application dlls and then returned to the web part for display. Using LoadRunner and multi-user testing the chart started to break.
Here is the basic code flow.
First the Metafile is created:
<div style="color:Black;background-color:White; <pre>
HDC hdcRef = GetDC(NULL);
<span style="color:Blue; int iWidthMM = GetDeviceCaps(hdcRef, HORZSIZE);
<span style="color:Blue; int iHeightMM = GetDeviceCaps(hdcRef, VERTSIZE);
<span style="color:Blue; int iWidthPels = GetDeviceCaps(hdcRef, HORZRES);
<span style="color:Blue; int iHeightPels = GetDeviceCaps(hdcRef, VERTRES);
RECT rect = { 0 };
rect.right = (m_lWidth * iWidthMM * 100)/iWidthPels;
rect.bottom = (m_lHeight * iHeightMM * 100)/iHeightPels;
HDC hDC = CreateEnhMetaFile(hdcRef, NULL, &rect, NULL);
<span style="color:Blue; if( hDC == NULL )
{
ReleaseDC(NULL, hdcRef);
<span style="color:Blue; return E_FAIL;
}
[/code]
Then there is a case statement based on what chart they are creating. For example:
<div style="color:Black;background-color:White; <pre>
hr = GenerateContPerfChart( session, hDC );
[/code]
Then the methods ends with this block:
<div style="color:Black;background-color:White; <pre>
HENHMETAFILE hemf = CloseEnhMetaFile(hDC);
<span style="color:Blue; if( hr == S_OK )
{
RGBQUAD* pPalette = 0;
<span style="color:Blue; if( iFormat != imgFormatJPG )
pPalette = (id != CH_VAC) ? pal1 : pal2;
hr = GetEMFType(hemf, m_lWidth, m_lHeight, COLE2T(bstrFile), iFormat, pPalette );
}
DeleteEnhMetaFile(hemf);
ReleaseDC(NULL, hdcRef);
[/code]
The body of GetEMFType is:
<div style="color:Black;background-color:White; <pre>
<span style="color:Blue; if (FAILED(InitializeGdiPlus()))
<span style="color:Blue; return hr;
GUID ImageType[5] = {
Gdiplus::ImageFormatEMF,
Gdiplus::ImageFormatBMP,
Gdiplus::ImageFormatJPEG,
Gdiplus::ImageFormatPNG,
Gdiplus::ImageFormatGIF
};
Gdiplus::Status status;
CLSID clsidEncoder = CLSID_NULL;
hr = GetGdiPlusImageCodec(ImageType[iFormat], clsidEncoder);
<span style="color:Blue; if (SUCCEEDED(hr))
{
USES_CONVERSION_EX;
LPCWSTR pwszFileName = T2CW_EX( lpszFile, _ATL_SAFE_ALLOCA_DEF_THRESHOLD );
<span style="color:Blue; if ( NULL != pwszFileName )
{
Gdiplus::Metafile metafile(hemf, FALSE);
status = metafile.Save(pwszFileName, &clsidEncoder, NULL);
<span style="color:Blue; if( status != Gdiplus::Ok )
{
hr = E_FAIL;
}
}
<span style="color:Blue; else
hr = E_OUTOFMEMORY;
}
<span style="color:Blue; return hr;
[/code]
Originally we called GdiplusStartup in this routine, but when started getting errors on the call when in multi-user. I read up on GdiplusStartup and discovered that it should only be called once and we were calling it a ton. So, we added the InitilizeGdiPlus
that creates an event that we basically use as a flag to know whether or not to call GdiplusStartup. If the event doesnt exist we call startup and create the event, if it does exist we skip the startup. Here is the internals of the InitializeGdiPlus
method:
<div style="color:Black;background-color:White; <pre>
HRESULT InitializeGdiPlus()
{
HRESULT hr = S_OK;
<span style="color:Green; //
<span style="color:Green; // Create an Event object to indicate if GDI+ has been initialized in this process.
<span style="color:Green; //
<span style="color:Green; // NOTE: We have no way of knowing when the process is finished using GDI+, so
<span style="color:Green; // the GdiplusShutdown method will never get called. Hopefully it will get
<span style="color:Green; // cleaned up properly when the process exits...
<span style="color:Green; //
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, <span style="color:#A31515; "WSINITGDIPLUS");
<span style="color:Blue; if (NULL != hEvent)
{
<span style="color:Green; //
<span style="color:Green; // If the event does not already exist, then initialize GDI+
<span style="color:Green; //
<span style="color:Blue; if (ERROR_ALREADY_EXISTS != GetLastError())
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
gdiplusStartupInput.GdiplusVersion = 1;
gdiplusStartupInput.DebugEventCallback = NULL;
ULONG_PTR gdiplusToken;
<span style="color:Blue; if (Gdiplus::Ok != GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL))
<span style="color:Blue; return E_FAIL;
}
}
<span style="color:Blue; else
{
<span style="color:Green; // Failed to create the event
DWORD dwError = GetLastError();
hr = E_FAIL;
}
<span style="color:Blue; return hr;
}
[/code]
Now the error we get is on the call to HENHMETAFILE hemf = CloseEnhMetaFile(hDC); in the third code block I included. hemf comes back as NULL and GetLastError tells me ERROR_NOT_ENOUGH_MEMORY.
I am searching every resource I can but havent really found a good starting point to debug the issue. Any help would be greatly appreciated. I am new to the metafile and GdiPlus code.
As for environment, all these dlls are running in Windows Server 2003 R2 with IIS 6.
View the full article