与GDI相比,GDI+要强大很多。对于Windows应用程序来说,用GDI是比较多的,也是比较熟练的,GDI+相对用的较少一点,但是现在GDI+的使用已经很普遍了。GDI+支持各种类型图片的处理,比如常见的bmp、jpg、gif、png等类型,特别是GDI+处理png图片时有很大的优势。有时我们需要将图片文件加载到内存中,然后进行UI的绘制,由于要支持多种类型的图片的载入,所以首先想到的是使用GDI+中的图片处理类Image或Bitmap。有时我们也需要将内存中的位图数据,保存成各种类型的图片文件,我们也要用到图片处理类Image或Bitmap。GDI+功能强大,但相对GDI而言,要难用很多,在使用的过程中也有很多需要注意的地方。下面结合本人在实际开发过程中遇到的问题,进行一些总结,以供参考。
1、GDI+库的加载与卸载
在程序初始化时,添加加载GDI+的代码:
- ULONG_PTR m_gdiplusToken;
- // 初始化GDI+
- Gdiplus::GdiplusStartupInput gdiplusStartupInput;
- Gdiplus::GdiplusStartup( &m_gdiplusToken, &gdiplusStartupInput, NULL );
在程序退出时,添加卸载GDI+的代码:
- // 释放GDI+资源
- Gdiplus::GdiplusShutdown( m_gdiplusToken );
- enum Status
- {
- Ok = 0,
- GenericError = 1,
- InvalidParameter = 2,
- OutOfMemory = 3,
- ObjectBusy = 4,
- InsufficientBuffer = 5,
- NotImplemented = 6,
- Win32Error = 7,
- WrongState = 8,
- Aborted = 9,
- FileNotFound = 10,
- ValueOverflow = 11,
- AccessDenied = 12,
- UnknownImageFormat = 13,
- FontFamilyNotFound = 14,
- FontStyleNotFound = 15,
- NotTrueTypeFont = 16,
- UnsupportedGdiplusVersion = 17,
- GdiplusNotInitialized = 18,
- PropertyNotFound = 19,
- PropertyNotSupported = 20,
- #if (GDIPVER >= 0x0110)
- ProfileNotFound = 21,
- #endif //(GDIPVER >= 0x0110)
- };
2、静态函数FromFile、FromHBitmap和FromStream的使用
FromFile主要是将图片文件加载到GDI+对象中,FromHBitmap和FromStream函数则是将内存中的图片数据加载到GDI+对象中。我们平常处理图片加载与格式转换时主要用到两个类:Bitmap类和Image类。Bitmap类继承于Image类,这三个函数它都有。Image类则只有FromFile和FromStream函数。在使用这三个函数时,要注意一下几点。
(1) 对于FromFile、FromHBitmap和FromStream这三个函数,都是静态函数,MSDN对于返回值的说明:This method returns a pointer to the new Bitmap/Image object(在VS中GO到函数的定义出也是能看出来的,函数返回是new出来的对象)。这意味着什么呢?因为返回的是新创建的类的对象,是需要我们使用者来负责销毁的,即对象使用完了后需要我们手动将之delete掉。如果不delete掉,不仅会导致内存泄漏,也会导致GDI句柄泄漏。这点在我们的项目开发中是深有体会的,特别是GDI句柄泄漏使用了专门的工具进行检测的。
(2) 在使用Image::FromFile时,要注意将指定的文件加载到Image对象中后,会将磁盘上对应的文件“锁住”,其他地方如果要同时加载该文件则可能会出问题,这也是我们在开发过程中遇到的问题。我们的处理办法是,不使用Image::FromFile函数,使用Image::FromStream。对于Image::FromStream,我们先将文件读到内存中,然后再将内存中数据倒到流中,然后调用Image::FromStream从流中将图片数据加载到Image对象中。使用Image::FromStream的流程较复杂,使用时要注意,也有一些陷阱,下面我们会谈到。
(3) 对于GDI+提供的函数,对于需要传入字符串的参数,一般均是WCHAR*宽字节类型,所以在调用之前要确保传入字符串是宽字节的。这点和COM接口类似,一般都要传入宽字节的字符串。
3、Image::FromStream的使用
此处主要讲如何将图片文件加载到Image对象中的,使用Image::FromStream加载的流程大概为:先将图片文件读到HGLOBAL内存中,然后调用CreateStreamOnHGlobal函数在HGLOBAL内存数据基础上创建流,最后调用Image::FromStream将图片数据加载到new出来的Image对象中。相关的代码如下所示:
- Image* m_pImg; // 定义成CXXXXXXXXX类的成员变量
- BOOL CXXXXXXXXX::Load( LPCTSTR pszFileName )
- {
- ASSERT( pszFileName != NULL );
- CFile file;
- DWORD dwSize;
- // 打开文件
- if ( !file.Open( szFileName,
- CFile::modeRead |
- CFile::shareDenyWrite ) )
- {
- TRACE( _T( "Load (file): Error opening file %s\n" ), szFileName );
- return FALSE;
- };
- // 根据文件大小分配HGLOBAL内存
- dwSize = (DWORD)file.GetLength();
- HGLOBAL hGlobal = GlobalAlloc( GMEM_MOVEABLE | GMEM_NODISCARD, dwSize );
- if ( !hGlobal )
- {
- TRACE( _T( "Load (file): Error allocating memory\n" ) );
- return FALSE;
- };
- char *pData = reinterpret_cast<char*>(GlobalLock(hGlobal));
- if ( !pData )
- {
- TRACE( _T( "Load (file): Error locking memory\n" ) );
- GlobalFree( hGlobal );
- return FALSE;
- };
- // 将文件内容读到HGLOBAL内存中
- TRY
- {
- file.Read( pData, dwSize );
- }
- CATCH( CFileException, e );
- {
- TRACE( _T( "Load (file): An exception occured while reading the file %s\n"),
- szFileName );
- GlobalFree( hGlobal );
- e->Delete();
- file.Close();
- return FALSE;
- }
- END_CATCH
- GlobalUnlock( hGlobal );
- file.Close();
- // 利用hGlobal内存中的数据创建stream
- IStream *pStream = NULL;
- if ( CreateStreamOnHGlobal( hGlobal, TRUE, &pStream ) != S_OK )
- {
- return FALSE;
- }
- m_pImg = Image::FromStream( pStream );
- ASSERT( m_pImg != NULL )
- // 要加上这一句,否则由GlobalAlloc得来的hGlobal内存没有被释放,导致内存泄露,由于
- // CreateStreamOnHGlobal第二个参数被设置为TRUE,所以调用pStream->Release()会自动
- // 将hGlobal内存(参见msdn对CreateStreamOnHGlobal的说明)
- pStream->Release();
- .......// 后续代码此处省略
- }
如上面的代码,必须要加上pStream->Release();这句,否则会导致内存泄漏,因为上面GlobalAlloc来的内存没有释放。但是代码中使用完后并没有调用GlobalFree来释放内存,那自动释放内存是如何做到的呢?那我们就来看看MSDN中,对CreateStreamOnHGlobal函数的说明:
- WINOLEAPI CreateStreamOnHGlobal(
- __in HGLOBAL hGlobal,
- __in BOOL fDeleteOnRelease, // 主要看这个参数的说明
- __out LPSTREAM* ppstm
- );
也就是说,当将fDeleteOnRelease参数设置为FALSE时,调用pStream->Release();时就不会自动释放GlobalAlloc来的内存,此时必须手动调用GlobalFree来释放;当将fDeleteOnRelease参数设置为TRUE时,在调用pStream->Release();是会自动将GlobalAlloc来的内存释放掉。
4、GDI+的绘图渲染能力