與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+的繪圖渲染能力