[ATL] Make CImage thread-safe (#5553)

- Add CInitGDIPlus subclass in CImage.
- Lock the CInitGDIPlus data by using a critical section.
- Add CImage::InitGDIPlus and use it.
CORE-19008
This commit is contained in:
Katayama Hirofumi MZ 2023-08-14 20:45:37 +09:00 committed by GitHub
parent fa7d5dbf06
commit 485c03ad03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -12,9 +12,6 @@
// See rostest/apitests/atl/CImage_WIP.txt for test results. // See rostest/apitests/atl/CImage_WIP.txt for test results.
// !!!! // !!!!
// TODO: CImage::Load, CImage::Save
// TODO: make CImage thread-safe
#pragma once #pragma once
#include <atlcore.h> // for ATL Core #include <atlcore.h> // for ATL Core
@ -58,25 +55,26 @@ public:
m_rgbTransColor = CLR_INVALID; m_rgbTransColor = CLR_INVALID;
ZeroMemory(&m_ds, sizeof(m_ds)); ZeroMemory(&m_ds, sizeof(m_ds));
if (GetCommon().AddRef() == 1) s_gdiplus.IncreaseCImageCount();
{
GetCommon().LoadLib();
}
} }
~CImage() virtual ~CImage() throw()
{ {
Destroy(); Destroy();
ReleaseGDIPlus(); s_gdiplus.DecreaseCImageCount();
} }
operator HBITMAP() operator HBITMAP() throw()
{ {
return m_hbm; return m_hbm;
} }
public: static void ReleaseGDIPlus()
void Attach(HBITMAP hBitmap, DIBOrientation eOrientation = DIBOR_DEFAULT) {
s_gdiplus.ReleaseGDIPlus();
}
void Attach(HBITMAP hBitmap, DIBOrientation eOrientation = DIBOR_DEFAULT) throw()
{ {
AttachInternal(hBitmap, eOrientation, -1); AttachInternal(hBitmap, eOrientation, -1);
} }
@ -119,7 +117,6 @@ public:
m_hDC = NULL; m_hDC = NULL;
} }
public:
BOOL AlphaBlend(HDC hDestDC, BOOL AlphaBlend(HDC hDestDC,
int xDest, int yDest, int nDestWidth, int nDestHeight, int xDest, int yDest, int nDestWidth, int nDestHeight,
int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight,
@ -376,13 +373,16 @@ public:
HRESULT Load(LPCTSTR pszFileName) throw() HRESULT Load(LPCTSTR pszFileName) throw()
{ {
if (!InitGDIPlus())
return E_FAIL;
// convert the file name string into Unicode // convert the file name string into Unicode
CStringW pszNameW(pszFileName); CStringW pszNameW(pszFileName);
// create a GpBitmap object from file // create a GpBitmap object from file
using namespace Gdiplus; using namespace Gdiplus;
GpBitmap *pBitmap = NULL; GpBitmap *pBitmap = NULL;
if (GetCommon().CreateBitmapFromFile(pszNameW, &pBitmap) != Ok) if (s_gdiplus.CreateBitmapFromFile(pszNameW, &pBitmap) != Ok)
{ {
return E_FAIL; return E_FAIL;
} }
@ -390,10 +390,10 @@ public:
// get bitmap handle // get bitmap handle
HBITMAP hbm = NULL; HBITMAP hbm = NULL;
Color color(0xFF, 0xFF, 0xFF); Color color(0xFF, 0xFF, 0xFF);
Status status = GetCommon().CreateHBITMAPFromBitmap(pBitmap, &hbm, color.GetValue()); Status status = s_gdiplus.CreateHBITMAPFromBitmap(pBitmap, &hbm, color.GetValue());
// delete GpBitmap // delete GpBitmap
GetCommon().DisposeImage(pBitmap); s_gdiplus.DisposeImage(pBitmap);
// attach it // attach it
if (status == Ok) if (status == Ok)
@ -402,10 +402,13 @@ public:
} }
HRESULT Load(IStream* pStream) throw() HRESULT Load(IStream* pStream) throw()
{ {
if (!InitGDIPlus())
return E_FAIL;
// create GpBitmap from stream // create GpBitmap from stream
using namespace Gdiplus; using namespace Gdiplus;
GpBitmap *pBitmap = NULL; GpBitmap *pBitmap = NULL;
if (GetCommon().CreateBitmapFromStream(pStream, &pBitmap) != Ok) if (s_gdiplus.CreateBitmapFromStream(pStream, &pBitmap) != Ok)
{ {
return E_FAIL; return E_FAIL;
} }
@ -413,10 +416,10 @@ public:
// get bitmap handle // get bitmap handle
HBITMAP hbm = NULL; HBITMAP hbm = NULL;
Color color(0xFF, 0xFF, 0xFF); Color color(0xFF, 0xFF, 0xFF);
Status status = GetCommon().CreateHBITMAPFromBitmap(pBitmap, &hbm, color.GetValue()); Status status = s_gdiplus.CreateHBITMAPFromBitmap(pBitmap, &hbm, color.GetValue());
// delete Bitmap // delete Bitmap
GetCommon().DisposeImage(pBitmap); s_gdiplus.DisposeImage(pBitmap);
// attach it // attach it
if (status == Ok) if (status == Ok)
@ -502,18 +505,11 @@ public:
return PlgBlt(hDestDC, pPoints, rectSrc, hbmMask, pointMask); return PlgBlt(hDestDC, pPoints, rectSrc, hbmMask, pointMask);
} }
void ReleaseGDIPlus() throw()
{
COMMON*& pCommon = GetCommonPtr();
if (pCommon && pCommon->Release() == 0)
{
delete pCommon;
pCommon = NULL;
}
}
HRESULT Save(IStream* pStream, GUID *guidFileType) const throw() HRESULT Save(IStream* pStream, GUID *guidFileType) const throw()
{ {
if (!InitGDIPlus())
return E_FAIL;
using namespace Gdiplus; using namespace Gdiplus;
ATLASSERT(m_hbm); ATLASSERT(m_hbm);
@ -527,14 +523,14 @@ public:
// create a GpBitmap from HBITMAP // create a GpBitmap from HBITMAP
GpBitmap *pBitmap = NULL; GpBitmap *pBitmap = NULL;
GetCommon().CreateBitmapFromHBITMAP(m_hbm, NULL, &pBitmap); s_gdiplus.CreateBitmapFromHBITMAP(m_hbm, NULL, &pBitmap);
// save to stream // save to stream
Status status; Status status;
status = GetCommon().SaveImageToStream(pBitmap, pStream, &clsid, NULL); status = s_gdiplus.SaveImageToStream(pBitmap, pStream, &clsid, NULL);
// destroy GpBitmap // destroy GpBitmap
GetCommon().DisposeImage(pBitmap); s_gdiplus.DisposeImage(pBitmap);
return (status == Ok ? S_OK : E_FAIL); return (status == Ok ? S_OK : E_FAIL);
} }
@ -542,6 +538,9 @@ public:
HRESULT Save(LPCTSTR pszFileName, HRESULT Save(LPCTSTR pszFileName,
REFGUID guidFileType = GUID_NULL) const throw() REFGUID guidFileType = GUID_NULL) const throw()
{ {
if (!InitGDIPlus())
return E_FAIL;
using namespace Gdiplus; using namespace Gdiplus;
ATLASSERT(m_hbm); ATLASSERT(m_hbm);
@ -567,13 +566,13 @@ public:
// create a GpBitmap from HBITMAP // create a GpBitmap from HBITMAP
GpBitmap *pBitmap = NULL; GpBitmap *pBitmap = NULL;
GetCommon().CreateBitmapFromHBITMAP(m_hbm, NULL, &pBitmap); s_gdiplus.CreateBitmapFromHBITMAP(m_hbm, NULL, &pBitmap);
// save to file // save to file
Status status = GetCommon().SaveImageToFile(pBitmap, pszNameW, &clsid, NULL); Status status = s_gdiplus.SaveImageToFile(pBitmap, pszNameW, &clsid, NULL);
// destroy GpBitmap // destroy GpBitmap
GetCommon().DisposeImage(pBitmap); s_gdiplus.DisposeImage(pBitmap);
return (status == Ok ? S_OK : E_FAIL); return (status == Ok ? S_OK : E_FAIL);
} }
@ -689,7 +688,6 @@ public:
rectSrc.bottom - rectSrc.top, crTransparent); rectSrc.bottom - rectSrc.top, crTransparent);
} }
public:
static BOOL IsTransparencySupported() throw() static BOOL IsTransparencySupported() throw()
{ {
return TRUE; return TRUE;
@ -710,7 +708,7 @@ public:
excludeDefaultSave = excludeIcon | excludeEMF | excludeWMF excludeDefaultSave = excludeIcon | excludeEMF | excludeWMF
}; };
protected: private:
static bool ShouldExcludeFormat(REFGUID guidFileType, DWORD dwExclude) static bool ShouldExcludeFormat(REFGUID guidFileType, DWORD dwExclude)
{ {
if (::IsEqualGUID(guidFileType, Gdiplus::ImageFormatGIF)) if (::IsEqualGUID(guidFileType, Gdiplus::ImageFormatGIF))
@ -811,7 +809,9 @@ public:
DWORD dwExclude = excludeDefaultLoad, DWORD dwExclude = excludeDefaultLoad,
TCHAR chSeparator = TEXT('|')) TCHAR chSeparator = TEXT('|'))
{ {
CImage dummy; // HACK: Initialize common if (!InitGDIPlus())
return E_FAIL;
UINT cDecoders = 0; UINT cDecoders = 0;
Gdiplus::ImageCodecInfo* pDecoders = _getAllDecoders(cDecoders); Gdiplus::ImageCodecInfo* pDecoders = _getAllDecoders(cDecoders);
HRESULT hr = BuildCodecFilterString(pDecoders, HRESULT hr = BuildCodecFilterString(pDecoders,
@ -832,7 +832,9 @@ public:
DWORD dwExclude = excludeDefaultSave, DWORD dwExclude = excludeDefaultSave,
TCHAR chSeparator = TEXT('|')) TCHAR chSeparator = TEXT('|'))
{ {
CImage dummy; // HACK: Initialize common if (!InitGDIPlus())
return E_FAIL;
UINT cEncoders = 0; UINT cEncoders = 0;
Gdiplus::ImageCodecInfo* pEncoders = _getAllEncoders(cEncoders); Gdiplus::ImageCodecInfo* pEncoders = _getAllEncoders(cEncoders);
HRESULT hr = BuildCodecFilterString(pEncoders, HRESULT hr = BuildCodecFilterString(pEncoders,
@ -846,153 +848,177 @@ public:
return hr; return hr;
} }
protected: private:
// an extension of BITMAPINFO // an extension of BITMAPINFO
struct MYBITMAPINFOEX : BITMAPINFO struct MYBITMAPINFOEX : BITMAPINFO
{ {
RGBQUAD bmiColorsExtra[256 - 1]; RGBQUAD bmiColorsExtra[256 - 1];
}; };
// abbreviations of GDI+ basic types. FIXME: Delete us class CInitGDIPlus
typedef Gdiplus::GpStatus St;
typedef Gdiplus::GpBitmap Bm;
typedef Gdiplus::GpImage Im;
// The common data of atlimage
struct COMMON
{ {
private:
// GDI+ function types // GDI+ function types
using FUN_Startup = decltype(&Gdiplus::GdiplusStartup); using FUN_Startup = decltype(&Gdiplus::GdiplusStartup);
using FUN_Shutdown = decltype(&Gdiplus::GdiplusShutdown); using FUN_Shutdown = decltype(&Gdiplus::GdiplusShutdown);
using FUN_GetImageEncoderSize = decltype(&Gdiplus::DllExports::GdipGetImageEncodersSize);
using FUN_GetImageEncoder = decltype(&Gdiplus::DllExports::GdipGetImageEncoders);
using FUN_GetImageDecoderSize = decltype(&Gdiplus::DllExports::GdipGetImageDecodersSize);
using FUN_GetImageDecoder = decltype(&Gdiplus::DllExports::GdipGetImageDecoders);
using FUN_CreateBitmapFromFile = decltype(&Gdiplus::DllExports::GdipCreateBitmapFromFile); using FUN_CreateBitmapFromFile = decltype(&Gdiplus::DllExports::GdipCreateBitmapFromFile);
using FUN_CreateHBITMAPFromBitmap = decltype(&Gdiplus::DllExports::GdipCreateHBITMAPFromBitmap);
using FUN_CreateBitmapFromStream = decltype(&Gdiplus::DllExports::GdipCreateBitmapFromStream);
using FUN_CreateBitmapFromHBITMAP = decltype(&Gdiplus::DllExports::GdipCreateBitmapFromHBITMAP); using FUN_CreateBitmapFromHBITMAP = decltype(&Gdiplus::DllExports::GdipCreateBitmapFromHBITMAP);
using FUN_SaveImageToStream = decltype(&Gdiplus::DllExports::GdipSaveImageToStream); using FUN_CreateBitmapFromStream = decltype(&Gdiplus::DllExports::GdipCreateBitmapFromStream);
using FUN_SaveImageToFile = decltype(&Gdiplus::DllExports::GdipSaveImageToFile); using FUN_CreateHBITMAPFromBitmap = decltype(&Gdiplus::DllExports::GdipCreateHBITMAPFromBitmap);
using FUN_DisposeImage = decltype(&Gdiplus::DllExports::GdipDisposeImage); using FUN_DisposeImage = decltype(&Gdiplus::DllExports::GdipDisposeImage);
using FUN_GetImageDecoder = decltype(&Gdiplus::DllExports::GdipGetImageDecoders);
using FUN_GetImageDecoderSize = decltype(&Gdiplus::DllExports::GdipGetImageDecodersSize);
using FUN_GetImageEncoder = decltype(&Gdiplus::DllExports::GdipGetImageEncoders);
using FUN_GetImageEncoderSize = decltype(&Gdiplus::DllExports::GdipGetImageEncodersSize);
using FUN_SaveImageToFile = decltype(&Gdiplus::DllExports::GdipSaveImageToFile);
using FUN_SaveImageToStream = decltype(&Gdiplus::DllExports::GdipSaveImageToStream);
// members // members
int count = 0; HINSTANCE m_hInst;
HINSTANCE hinstGdiPlus = NULL; ULONG_PTR m_dwToken;
ULONG_PTR gdiplusToken = 0; CRITICAL_SECTION m_sect;
LONG m_nCImageObjects;
DWORD m_dwLastError;
// GDI+ functions void _clear_funs() throw()
FUN_Startup Startup = NULL;
FUN_Shutdown Shutdown = NULL;
FUN_GetImageEncoderSize GetImageEncodersSize = NULL;
FUN_GetImageEncoder GetImageEncoders = NULL;
FUN_GetImageDecoderSize GetImageDecodersSize = NULL;
FUN_GetImageDecoder GetImageDecoders = NULL;
FUN_CreateBitmapFromFile CreateBitmapFromFile = NULL;
FUN_CreateHBITMAPFromBitmap CreateHBITMAPFromBitmap = NULL;
FUN_CreateBitmapFromStream CreateBitmapFromStream = NULL;
FUN_CreateBitmapFromHBITMAP CreateBitmapFromHBITMAP = NULL;
FUN_SaveImageToStream SaveImageToStream = NULL;
FUN_SaveImageToFile SaveImageToFile = NULL;
FUN_DisposeImage DisposeImage = NULL;
COMMON()
{ {
Startup = NULL;
Shutdown = NULL;
CreateBitmapFromFile = NULL;
CreateBitmapFromHBITMAP = NULL;
CreateBitmapFromStream = NULL;
CreateHBITMAPFromBitmap = NULL;
DisposeImage = NULL;
GetImageDecoders = NULL;
GetImageDecodersSize = NULL;
GetImageEncoders = NULL;
GetImageEncodersSize = NULL;
SaveImageToFile = NULL;
SaveImageToStream = NULL;
} }
~COMMON() template <typename T_FUN>
T_FUN _get_fun(T_FUN& fun, LPCSTR name) throw()
{ {
FreeLib(); if (!fun)
} fun = reinterpret_cast<T_FUN>(::GetProcAddress(m_hInst, name));
ULONG AddRef()
{
return ++count;
}
ULONG Release()
{
return --count;
}
// get procedure address of the DLL
template <typename FUN_T>
FUN_T _getFUN(FUN_T& fun, const char *name)
{
if (fun)
return fun;
fun = reinterpret_cast<FUN_T>(::GetProcAddress(hinstGdiPlus, name));
return fun; return fun;
} }
HINSTANCE LoadLib() public:
// GDI+ functions
FUN_Startup Startup;
FUN_Shutdown Shutdown;
FUN_CreateBitmapFromFile CreateBitmapFromFile;
FUN_CreateBitmapFromHBITMAP CreateBitmapFromHBITMAP;
FUN_CreateBitmapFromStream CreateBitmapFromStream;
FUN_CreateHBITMAPFromBitmap CreateHBITMAPFromBitmap;
FUN_DisposeImage DisposeImage;
FUN_GetImageDecoder GetImageDecoders;
FUN_GetImageDecoderSize GetImageDecodersSize;
FUN_GetImageEncoder GetImageEncoders;
FUN_GetImageEncoderSize GetImageEncodersSize;
FUN_SaveImageToFile SaveImageToFile;
FUN_SaveImageToStream SaveImageToStream;
CInitGDIPlus() throw()
: m_hInst(NULL)
, m_dwToken(0)
, m_nCImageObjects(0)
, m_dwLastError(ERROR_SUCCESS)
{ {
if (hinstGdiPlus) _clear_funs();
return hinstGdiPlus; ::InitializeCriticalSection(&m_sect);
}
hinstGdiPlus = ::LoadLibraryA("gdiplus.dll"); ~CInitGDIPlus() throw()
{
ReleaseGDIPlus();
::DeleteCriticalSection(&m_sect);
}
_getFUN(Startup, "GdiplusStartup"); bool Init() throw()
_getFUN(Shutdown, "GdiplusShutdown"); {
_getFUN(GetImageEncodersSize, "GdipGetImageEncodersSize"); ::EnterCriticalSection(&m_sect);
_getFUN(GetImageEncoders, "GdipGetImageEncoders");
_getFUN(GetImageDecodersSize, "GdipGetImageDecodersSize");
_getFUN(GetImageDecoders, "GdipGetImageDecoders");
_getFUN(CreateBitmapFromFile, "GdipCreateBitmapFromFile");
_getFUN(CreateHBITMAPFromBitmap, "GdipCreateHBITMAPFromBitmap");
_getFUN(CreateBitmapFromStream, "GdipCreateBitmapFromStream");
_getFUN(CreateBitmapFromHBITMAP, "GdipCreateBitmapFromHBITMAP");
_getFUN(SaveImageToStream, "GdipSaveImageToStream");
_getFUN(SaveImageToFile, "GdipSaveImageToFile");
_getFUN(DisposeImage, "GdipDisposeImage");
if (hinstGdiPlus && Startup) if (m_dwToken == 0)
{ {
Gdiplus::GdiplusStartupInput gdiplusStartupInput; if (!m_hInst)
Startup(&gdiplusToken, &gdiplusStartupInput, NULL); m_hInst = ::LoadLibrary(TEXT("gdiplus.dll"));
if (m_hInst &&
_get_fun(Startup, "GdiplusStartup") &&
_get_fun(Shutdown, "GdiplusShutdown") &&
_get_fun(CreateBitmapFromFile, "GdipCreateBitmapFromFile") &&
_get_fun(CreateBitmapFromHBITMAP, "GdipCreateBitmapFromHBITMAP") &&
_get_fun(CreateBitmapFromStream, "GdipCreateBitmapFromStream") &&
_get_fun(CreateHBITMAPFromBitmap, "GdipCreateHBITMAPFromBitmap") &&
_get_fun(DisposeImage, "GdipDisposeImage") &&
_get_fun(GetImageDecoders, "GdipGetImageDecoders") &&
_get_fun(GetImageDecodersSize, "GdipGetImageDecodersSize") &&
_get_fun(GetImageEncoders, "GdipGetImageEncoders") &&
_get_fun(GetImageEncodersSize, "GdipGetImageEncodersSize") &&
_get_fun(SaveImageToFile, "GdipSaveImageToFile") &&
_get_fun(SaveImageToStream, "GdipSaveImageToStream"))
{
using namespace Gdiplus;
GdiplusStartupInput input;
GdiplusStartupOutput output;
Startup(&m_dwToken, &input, &output);
}
} }
return hinstGdiPlus; bool ret = (m_dwToken != 0);
::LeaveCriticalSection(&m_sect);
return ret;
} }
void FreeLib()
void ReleaseGDIPlus() throw()
{ {
if (hinstGdiPlus) ::EnterCriticalSection(&m_sect);
if (m_dwToken)
{ {
Shutdown(gdiplusToken); if (Shutdown)
Shutdown(m_dwToken);
Startup = NULL; m_dwToken = 0;
Shutdown = NULL;
GetImageEncodersSize = NULL;
GetImageEncoders = NULL;
GetImageDecodersSize = NULL;
GetImageDecoders = NULL;
CreateBitmapFromFile = NULL;
CreateHBITMAPFromBitmap = NULL;
CreateBitmapFromStream = NULL;
CreateBitmapFromHBITMAP = NULL;
SaveImageToStream = NULL;
SaveImageToFile = NULL;
DisposeImage = NULL;
::FreeLibrary(hinstGdiPlus);
hinstGdiPlus = NULL;
} }
if (m_hInst)
{
::FreeLibrary(m_hInst);
m_hInst = NULL;
}
_clear_funs();
::LeaveCriticalSection(&m_sect);
} }
}; // struct COMMON
static COMMON*& GetCommonPtr() void IncreaseCImageCount() throw()
{
::EnterCriticalSection(&m_sect);
++m_nCImageObjects;
::LeaveCriticalSection(&m_sect);
}
void DecreaseCImageCount() throw()
{
::EnterCriticalSection(&m_sect);
if (--m_nCImageObjects == 0)
{
ReleaseGDIPlus();
}
::LeaveCriticalSection(&m_sect);
}
};
static CInitGDIPlus s_gdiplus;
static bool InitGDIPlus() throw()
{ {
static COMMON *s_pCommon = NULL; return s_gdiplus.Init();
return s_pCommon;
} }
static COMMON& GetCommon() private:
{
COMMON*& pCommon = GetCommonPtr();
if (pCommon == NULL)
pCommon = new COMMON;
return *pCommon;
}
protected:
HBITMAP m_hbm; HBITMAP m_hbm;
mutable HGDIOBJ m_hbmOld; mutable HGDIOBJ m_hbmOld;
mutable HDC m_hDC; mutable HDC m_hDC;
@ -1070,7 +1096,7 @@ protected:
static Gdiplus::ImageCodecInfo* _getAllEncoders(UINT& cEncoders) static Gdiplus::ImageCodecInfo* _getAllEncoders(UINT& cEncoders)
{ {
UINT total_size = 0; UINT total_size = 0;
GetCommon().GetImageEncodersSize(&cEncoders, &total_size); s_gdiplus.GetImageEncodersSize(&cEncoders, &total_size);
if (total_size == 0) if (total_size == 0)
return NULL; // failure return NULL; // failure
@ -1082,14 +1108,14 @@ protected:
return NULL; // failure return NULL; // failure
} }
GetCommon().GetImageEncoders(cEncoders, total_size, ret); s_gdiplus.GetImageEncoders(cEncoders, total_size, ret);
return ret; // needs delete[] return ret; // needs delete[]
} }
static Gdiplus::ImageCodecInfo* _getAllDecoders(UINT& cDecoders) static Gdiplus::ImageCodecInfo* _getAllDecoders(UINT& cDecoders)
{ {
UINT total_size = 0; UINT total_size = 0;
GetCommon().GetImageDecodersSize(&cDecoders, &total_size); s_gdiplus.GetImageDecodersSize(&cDecoders, &total_size);
if (total_size == 0) if (total_size == 0)
return NULL; // failure return NULL; // failure
@ -1101,12 +1127,12 @@ protected:
return NULL; // failure return NULL; // failure
} }
GetCommon().GetImageDecoders(cDecoders, total_size, ret); s_gdiplus.GetImageDecoders(cDecoders, total_size, ret);
return ret; // needs delete[] return ret; // needs delete[]
} }
void AttachInternal(HBITMAP hBitmap, DIBOrientation eOrientation, void AttachInternal(HBITMAP hBitmap, DIBOrientation eOrientation,
LONG iTransColor) LONG iTransColor) throw()
{ {
Destroy(); Destroy();
@ -1187,11 +1213,12 @@ protected:
} }
private: private:
// NOTE: CImage is not copyable CImage(const CImage&) = delete;
CImage(const CImage&); CImage& operator=(const CImage&) = delete;
CImage& operator=(const CImage&);
}; };
DECLSPEC_SELECTANY CImage::CInitGDIPlus CImage::s_gdiplus;
} }
#endif #endif