お世話になっております。
以前GDI+についての質問をした者ですが、懲りずにもう一つ聞かせて下さい。
GDI+を使って画像ファイルを開く方法は複数あるという話ですが、私は
○(あれば)以前作ったImageオブジェクトを解放(GdipDisposeImage)
○ファイルからImageオブジェクトを取得する(UCLoadImageFromFile)
○ピクチャボックスのハンドルからGraphicオブジェクトを取得する(GdipCreateFromHDC)
○グラフィック描画(GdipDrawImageRectI)
○Graphic解放(GdipDeleteGraphics)
という手順を使っています。
一連の動作でImageオブジェクトを解放しないで保持しているのは、GIFアニメーションファイルを扱う際にImageオブジェクトを保持している必要があるためと、
拡大や回転といった画像操作を行う時にファイルを読み直す必要がなくなるからです。
しかし、この方法でファイルを開くと、表示中のファイルにロックがかかるらしく、元のファイルに対して削除や名前変更などの編集が行えなくなってしまいます。
(どうやらImageオブジェクトを保持している間は自動的に編集ロックがかかるようです)
フォルダ内の要らない画像をチェックしながら、その場で捨てたり名前を変えたりという操作を想定しているので、この現象はなんとか回避したいのですが、
Imageオブジェクトを解放する以外に、ロックを解除する方法はないものでしょうか。
(このままだと、ほかのファイルはともかくGIFアニメファイルを見ながらファイルの編集をすることができなくなる)
GdipCloneImageを使ってImageのクローンを作り、最初のImageだけ消せばばいいのかと思って試したりしましたが、
クローン側のImageオブジェクトもファイルのロックをしてしまうようで効果はありませんでした。
どうやったらこれを回避できるのか、ご存じの方がいらっしゃいましたらご教授願います。
よろしくお願いします。
UCLoadImageFromFile というのは初耳なのですが、
GdipLoadImageFromFile とは別物なのでしょうか?
で。ロックを回避したいのであれば、GdipLoadImageFromFile ではなく、
GdipLoadImageFromStream の利用を検討してみてください。
# 参考情報
# http://support.microsoft.com/kb/309482/ja
魔界の仮面弁士様
お世話になっております。早速のお返事有り難うございます。
すみません UCLoadImageFromFileは私が書いたサブルーチンでした(汗
ご指摘の通り、GdipLoadImageFromFileのことです。混乱させてしまい申し訳ありませんでした。
GdipLoadImageFromStreamの件、調べながら進めようかと思います。
恥ずかしながら、ストリームオブジェクトというものを初めて知ったので戸惑っておりますが…
GdipSaveImageToStream か GdipCreateStreamOnFile を使うんでしょうか…? (どちらも引数の意味が分かりません)
厚かましい話ですが、今日はちょっとコーディングの時間が取れそうにないので また進展があったら(進展しなくても…)書き込みさせて頂くかと思います
>> GdipLoadImageFromStream の利用を検討してみてください。
あるいは、GdipCreateBitmapFromStream を使っても良いかな?
> GdipLoadImageFromStreamの件、調べながら進めようかと思います。
実装例。
Picture1.AutoRedraw = True
ret = GdipCreateFromHDC(Picture1.hdc, g)
Dim stm As ADODB.Stream
Set stm = New ADODB.Stream
stm.Type = adTypeBinary
stm.Open
stm.LoadFromFile strFileName
'ByVal IStream, ByVal OLE_HANDLE
ret = GdipLoadImageFromStream(stm, img)
ret = GdipGetImageWidth(img, lngWidth)
ret = GdipGetImageHeight(img, lngHeight)
ret = GdipDrawImageRectI(g, img, 0, 0, lngWidth, lngHeight)
ret = GdipDeleteGraphics(g)
ret = GdipDisposeImage(img)
stm.Close
Set stm = Nothing
Picture1.Refresh
実装例、その2
Dim pStm As stdole.IUnknown
' ByVal filename As Long
' ByVal access As Long
' ByRef img As stdole.IUnknown
ret = GdipCreateStreamOnFile( _
StrPtr(strFileName), _
GENERIC_READ Or GENERIC_WRITE, _
pStm)
' ByVal stm As stdole.IUnknown
' ByRef img As OLE_HANDLE
ret = GdipLoadImageFromStream(pStm, img)
魔界の仮面弁士様
お世話になっております。
時間が取れたので、教えて頂いた実装例その2を使って試してみました。
(パッと見てシンプルで簡単そうだったからです その1はまだ試していません)
パターン1
'===========================================================
Const GENERIC_READ As Long = &H80000000
Const GENERIC_WRITE As Long = &H40000000
Private UCImgObjhwd As Long
'===========================================================
Dim pStm As Long
Ret = GdipCreateStreamOnFile(StrPtr(strPath), GENERIC_READ Or GENERIC_WRITE, pStm)
Ret = GdipLoadImageFromStream(pStm, UCImgObjhwd) '< RET = 2(InvalidParameter)が出る
元の関数のデータ型で試して見ましたところ、GdipLoadImageFromStreamでエラーが返ってきて進めませんでした(できればこちらの方法で実装したかったところですが、原因が分からず次へ)
パターン2
'===========================================================
Private Declare Function UCGdipCreateStreamOnFile Lib "GdiPlus" Alias "GdipCreateStreamOnFile" _
(ByVal filename As Long, _
ByVal access As Long, _
ByRef img As stdole.IUnknown) As GdiPlus.GpStatus
Private Declare Function UCGdipLoadImageFromStream Lib "GdiPlus" Alias "GdipLoadImageFromStream" _
(ByVal stm As stdole.IUnknown, _
ByRef img As Long) As GdiPlus.GpStatus
Const GENERIC_READ As Long = &H80000000
Const GENERIC_WRITE As Long = &H40000000
Private UCImgObjhwd As OLE_HANDLE
'===========================================================
Dim pStm Asstdole.IUnknown
Ret = UCGdipCreateStreamOnFile(StrPtr(strPath), GENERIC_READ Or GENERIC_WRITE, pStm)
Ret = UCGdipLoadImageFromStream(pStm, UCImgObjhwd)
実装例その2を忠実に再現したらこうなるんでしょうか
自信がなかったですがちゃんと動きました
しかし、やはり開いた状態だとファイルの編集はできないようです
(GENERIC_READ・WRITE定数が間違っていなければ)ファイル編集を実装するにはイメージオブジェクトの解放が必要とするなら、保持するのはイメージオブジェクトではなくストリームオブジェクトということになるのでしょうか。
(GDIの関数を検索しても、ストリームの解放というコマンドはないような気がするので、ストリームオブジェクトは勝手に解放されるものかと勝手に推測していました。だからストリームを保持するという考えはなかったのですが)
もしそうだとしたら明示的に解放してやらなければメモリリークが起こりませんか(なにぶん無知なので的外れだとしたらご指摘下さい)
あ…と よく見たら、ストリームの解放は、仮面弁士さんの実装例その1だと行われているようですね…
APIか何かで解放の関数があったりするのかな…
後日実装例1を試してみたいと思います
> パターン1
> 元の関数のデータ型で試して見ましたところ
「元の関数のデータ型」とは何ですか?
> しかし、やはり開いた状態だとファイルの編集はできないようです
Image を取得後、Stream は閉じてください。
または、ファイルストリームではなくメモリストリームを使う手もあります。
> APIか何かで解放の関数があったりするのかな…
「Set pStm = Nothing」で解放されませんか?
魔界の仮面弁士様
早速のお返事、ありがとうございます。
説明が至らず本当に申し訳ありません。環境から書きます。
当方OSはXP、GDIをVB6上で動かすために、GDIPlus Type Library1.05を入手して使っています。
↓ここで入手しました
http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=42861&lngWId=1
「元の関数のデータ型」というのは、このライブラリが示しているデータ型のことです。(別に公式でもなんでもないようなので『元の』というのは私の表現が不適切でした)
これによると、
Function GdipCreateStreamOnFile(Filename As String, Access As Long, Stream As Long) As GpStatus
Function GdipLoadImageFromStream(Stream As Long, Image As Long) As GpStatus
となっています。そのため、ストリームはlong型のポインタかハンドルか何かが返ってくるのだろうと推測しておりました。
だから解放の仕方がよく分からないなー と勝手に悩んでいました。
(結局、long型でデータを渡してやっても、前述のようにGdipLoadImageFromStreamでエラーが返るのがよく分からず断念)
>Image を取得後、Stream は閉じてください。
>「Set pStm = Nothing」で解放されませんか?
とのことなので、前述のパターン2に一行追加して
'===========================================================
Dim pStm Asstdole.IUnknown
Ret = UCGdipCreateStreamOnFile(StrPtr(strPath), GENERIC_READ Or GENERIC_WRITE, pStm)
Ret = UCGdipLoadImageFromStream(pStm, UCImgObjhwd)
Set pStm = Nothing
'===========================================================
のようにしてみました。
しかし、コードは通りますが、やはりイメージオブジェクトがある間はファイルはロックされてしまっているようです。
(pStmは"Nothing"となりますので解放されているようです)
その後、イメージオブジェクトを解放すると、ファイルが編集可能になるのも一緒でした。
>ファイルストリームではなくメモリストリームを使う手もあります。
また知らない単語が…(汗
時間がある時に用語から調べてみます。
> GDIPlus Type Library1.05を入手して使っています。
う〜ん、それは試した事が無いです。(すべて自前で Declare しています)
一応、Stream から Image を生成後に Stream を解放するとファイル名を
変更できるようになり、また、その Image から GdipDrawImageRectI で
描画できることも確認しているので、大丈夫だとは思うのですけれども。
> そのため、ストリームはlong型のポインタかハンドルか何かが返ってくるのだろうと推測しておりました。
SDK を見ると、GdiPlusFlat.h では
GpStatus WINGDIPAPI GdipCreateStreamOnFile(
GDIPCONST WCHAR * filename,
UINT access,
IStream **stream);
と定義されていますね。
>> ファイルストリームではなくメモリストリームを使う手もあります。
> また知らない単語が…(汗
.NET でいえば、System.IO.FileStream と System.IO.MemoryStream の違いのようなものです。
試してはいませんが、API であれば、CreateStreamOnHGlobal で
用意できるかと思います。
http://msdn2.microsoft.com/en-us/library/aa378980.aspx
Windows CE版でよければ、機械翻訳があります。
http://mtbeta.msdn.microsoft.com/ja-jp/library/aa911491.aspx?altlang=ja-jp
あとはそのメモリストリームに、画像ファイルのバイナリを書きこんでから、
GdipLoadImageFromStream に渡せば、たとえロックされるとしても、それは
(画像ファイルではなく)メモリストリームなので、問題は無いかな、と。
で。先に書いた私の実装例その1 では、メモリストリームを生成するために、
ADO の Stream オブジェクトを使用してみた、という事です。
(ADODB.Stream は、COM の IStream インターフェイスを実装しています)
魔界の仮面弁士様
その後調べてみたら、
http://salv.miscnotes.com/11_programming/10vb_vbnet/74gdi_in_vb6/
で「GDIPlus Type Libraryは1.31まである」ということを知り(既にずいぶん古い記事ですが…)、
http://www.cyberactivex.com/
でDLしてみました。
それによると、
Function GdipCreateStreamOnFile(Filename As String, Access As Long, stream As IStream) As GpStatus
となっていました。
V1.05はまだバグだらけだったのか、結構な関数の引数等に変更が加わっているようです(単に内部処理の変更かもしれませんが)
これなら簡単… とやってみましたが↓
Dim pstm As IStream
Ret = GdipCreateStreamOnFile(Path, GENERIC_READ Or GENERIC_WRITE, pstm)
Ret = GdipLoadImageFromStream(pstm, UCImgObjhwd)
Set pstm = Nothing
やはり今までと同じく、ファイルの名前を変えられないです
そして実装例その1を試してみました
(ADO 2.8)
Private Declare Function UCGdipLoadImageFromStream Lib "GdiPlus" Alias "GdipLoadImageFromStream" _
(ByVal stm As IStream, _
ByVal img As OLE_HANDLE) As GdiPlus.GpStatus
Dim stm As ADODB.Stream
Set stm = New ADODB.Stream
stm.Type = adTypeBinary
stm.Mode = adModeReadWrite
stm.Open
stm.LoadFromFile Path
Ret = UCGdipLoadImageFromStream(stm, UCImgObjhwd)<Ret=2が出る
stm.Close
Set stm = Nothing
UCGdipLoadImageFromStreamで「不正なパラメータ」が出るてしまい先に進めませんでした
想像ですが、IStream型はGDIPlus Type Library1.31の参照設定で宣言できるようになったようなので(1.05時には出てこなかった)「ByVal stm As IStream」の部分が悪いのかもしれません
そこで試しに
Ret = UCGdipLoadImageFromStream(stm, UCImgObjhwd)(Declareに渡す)
を
Ret = GdipLoadImageFromStream(stm, UCImgObjhwd) (タイプライブラリに渡す)
にしてみましたが、今度は画像が上部の一部分しか表示されなくなりました
画像サイズは正常に取得されるのですが、上部一部を除いて全て灰色で表示されます うーむ?
しかし、この状態でならファイルの名前を書き換えられました
なんだか両方ともあと一歩の所なんですが…
>試してはいませんが、API であれば、CreateStreamOnHGlobal で
ありがとうございます。
今度はこれを試してみます… が、これまた難易度が高そうな…とちょっとビビっています(w;
ではまた。
「タイプライブラリによる GDI+ 呼び出し」と「Declare による GDI+ 呼び出し」を混在させず、
すべて Declare で実装した場合、正常に呼び出せますか?
とりあえず当方では、こんな感じのコードで動作しているのですけれども。
http://www.vb-user.net/junk/replySamples/2007.10.21.10.14/DrawFromStream.txt
なお、私は GDIPlus Type Library を試していないので、IStream の宣言については、
下記の OLELIB.TLB 「Edanmo's OLE interfaces & functions v1.81」を使用しました。
http://www.mvps.org/emorcillo/en/code/vb6/index.shtml
ADO の宣言については、MSADO15.DLL「Microsoft ActiveX Objects 2.8 Library」です。
> にしてみましたが、今度は画像が上部の一部分しか表示されなくなりました
データが完全に読み込まれる前に、ストリームを解放していませんか?
完全にロードさせるため、解放前に一度、全体像を描画させておいてみては如何でしょう。
ADODB.Stream (IStream および ISequentialStream の実装)だと、
都合が悪いみたいです。やはり、CreateStreamOnHGlobal の方が良いかな。
という事で作り直して見ました。追加の参照設定は不要です。
http://www.vb-user.net/junk/replySamples/2007.10.21.12.01/DrawFromStream.txt
ボタンを 4 つと、PictureBox を 1 つ、TextBox を 1 つ貼って、
TextBox には画像のフルパスを指定しておいてください。
魔界の仮面弁士様
お世話になっております。
提示して頂いたコードで、問題なく動作しました。
ファイルの名前も書き換えられました。
なぜこのような違いが出るのかは分かりませんが、暇を見て
2007/10/21(日) 10:22:48 で言われたことを検証して報告させて頂きます。
(今はちょっと時間がありません 申し訳ありません)
とりあえずこちらは解決済みとさせて頂きます。
こちらの至らぬ質問に付き合って頂き、有り難うございました。
勉強になりました。
今回のことで、未知の部分に何歩か足を踏み入れることができましたので、また少しずつ勉強していきたいと思います。
ああ… また解決にするのを忘れた…
すみません(汗
魔界の仮面弁士様
間が空いてしまい申し訳ありません
検証してみましたので、参考までに結果を載せておきます
検証1
>すべて Declare で実装した場合、正常に呼び出せますか?
提示していただいた
http://www.vb-user.net/junk/replySamples/2007.10.21.10.14/DrawFromStream.txt
を少しいじって検証しました。
参照設定も同じ状態にしてあります
サンプルコードから、GdipDisposeImageを取り除いた状態で(別のボタンのクリックイベントに移動)検証
Command1(ADODB.Stream)押下時は正常動作(ロックなし)
Command2(stdole.IUnknown)押下時はロックがかかる
ADODBのストリームとstdoleのとでは動作が違うようですね
何が違うのかは見当も付きませんが…
検証2
>データが完全に読み込まれる前に、ストリームを解放していませんか?
仰る通りでした。
ストリームの解放タイミングを遅らせてみたら、きちんと読み込まれているのを確認しました。
しかし非同期読み込みでもないのにこんなことが起こるとは予想外でした
コールバックでもない限り根本的な解決法がないような…
(コールバック使ったことないですけど)
以上です。
ツイート | ![]() |