GDI+的Image及衍生類別中涉及到IStream流,在Delphi和C++Builder中廣泛使用的TStream不能直接作為參數進行傳遞,VCL提供了一個TStreamAdapter類,用於把VCL流TStream轉換為IStream。TStreamAdapter的構造過程原型如下:
constructor Create(Stream: TStream; Ownership: TStreamOwnership = soReference);
其中的TStreamOwnership枚舉類型有2個值:soReference和,如果是soOwned,TStream對象由TStreamAdapter釋放,否則由使用者自己處理。請看下面的用流建立Image的Delphi代碼:
var
Stream: TMemoryStream;
Adapter: TStreamAdapter;
Image: TGpImage;
begin
Stream := TMemoryStream.Create;
Stream.LoadFromFile('Test.jpg');
Adapter := TStreamAdapter.Create(Stream, soOwned);
Image := TGpImage.Create(Adapter);
try
.....
finally
Image.Free;
end;
end;
代碼中,首先建立一個記憶體流,並從磁碟中裝入一個影像檔,然後用TStreamAdapter將這個流封裝起來,轉換為IStream,供建立TGpImage使用。注意,我在建立TStreamAdapter對象時使用了soOwned,這樣,由Adapter負責釋放Stream,否則,必須在Image.Free後面Stream.Free。但是TStreamAdapter對象Adapter為什麼沒有釋放代碼呢?因為TStreamAdapter是個介面實作類別,作為IStream傳遞給TGpImage.Create後,由系統自動跟蹤釋放,如果我們將它Free,會引起錯誤,如果非要顯式釋放它,必須用var I: IStream; I := Adapter; I := nil;的方法釋放,而且必須在Image.Free之前,因為在Image.Free的同時,Adapter也隨著IStream一起被釋放掉了。
C++Builder與Delphi享有同一個VCL,也可按照Delphi的方法使用TStream,但是,由於C++Builder使用的GDI+程式碼封裝含在Gdiplus名字空間中,所有函數均採用inline方式,不是真正的函數調用方式,按照Delphi的方式,用TStreamAdapter將TStream轉換為IStream傳遞給Image的建構函式,將會出現運行時刻錯誤,因為如果Image的建構函式是真正的函數,在傳遞介面參數時,會對介面對象作隱式的引用調用,而inline函數一般只是直接將函數體內容插入調用函數的地方,沒有真正地傳遞參數的過程,所以,我們必須顯式的做一個引用調用。請看下面儲存Image到流的C++代碼片斷:
bool __fastcall GetImageEncoderClsid(AnsiString format, PGUID Clsid)
{
UINT num, size = 0;
DllExports::GdipGetImageEncodersSize(&num, &size);
if (size == 0) return false;
ImageCodecInfo *Info = (ImageCodecInfo*)DllExports::GdipAlloc(size);
if(Info == NULL) return false;
UINT i = 0;
try
{
DllExports::GdipGetImageEncoders(num, size, Info);
for (; i < num && CompareText(Info[i].MimeType, format) != 0; i ++);
if (i < num)
memcpy(Clsid, &Info[i].Clsid, sizeof(TGUID));
}
__finally
{
DllExports::GdipFree(Info);
}
return i < num? true : false;
}
void __fastcall TGdipBitmap::SaveToStream(Classes::TStream* Stream)
{
TGUID Clsid;
if (ImageFormat != "" && GetImageEncoderClsid(ImageFormat, &Clsid))
{
Gdiplus::Bitmap Image(Handle, Palette);
TStreamAdapter *Adapter = new TStreamAdapter(Stream, soReference);
Adapter->_AddRef();
try
{
if (Image.Save((IStream*)*Adapter, &Clsid) != Ok)
throw new Exception("Save Image fail");
}
__finally
{
Adapter->_Release();
}
}
else
TBitmap::SaveToStream(Stream);
}
代碼中,顯式的調用了TStreamAdapter的_AddRef()和_Release()函數,如果將這個調用省略,運行時會出現錯誤。
由於本人是業餘愛好者,水平和功力有限,錯誤在所難免,歡迎來信指導:maozefa@hotmail.com
補充(2008.9.7):從本文舉例中可以看到,在C++Builder的GDI+中 使用TStreamAdapter,必須顯式的調用其_AddRef()和_Release()函數,否則會出錯,而Delphi中使用TStreamAdapter,似乎不必顯式調用_AddRef和_Release,程式就工作的很好,但是如果多次使用TStreamAdapter的同一個對象,同樣也會出錯,也需要顯式調用_AddRef和_Release。而Delphi的TStreamAdapter的_AddRef和_Release不是public方法,只能轉換為IStream後調用。請看下面的代碼片斷:
var
tmp: TGpBitmap;
Adapter: TStreamAdapter;
Stream: TStream;
Clsid: TGUID;
begin
Stream := TMemoryStream.Create;
Adapter := TStreamAdapter.Create(Stream, soOwned);
GetEncoderClsid('image/bmp', Clsid);
IStream(Adapter)._AddRef;
Image.Save(Adapter, Clsid);
tmp := TGpBitmap.Create(Adapter);
try
......
......
finally
tmp.Free;
IStream(Adapter)._Release;
end;
代碼中的Image是函數中的一個TGpImage型別參數,代碼先把Image儲存到記憶體流,然後用這個流建立一個臨時TGpBitmap對象,便於進行操作,操作完畢,釋放臨時對象。為了適應GDI+的IStream介面,代碼中用TStreamAdapter進行了轉換,如果不顯式地調用_AddRef和_Release,這段代碼根本就運行不下去。