3

https://docs.microsoft.com/en-us/archive/blogs/brian_jones/embedding-any-file-type-like-で説明されているOLE手法を使用して、PDFファイルをWord文書に埋め込もうとしています 。 pdf-in-an-open-xml-file

プロジェクト全体が1つの場所にあり、1つの障害物を除いてほとんどそこにあるように、C#で提供されるC++コードを実装しようとしました。生成されたOLEオブジェクトのバイナリデータをWordドキュメントにフィードしようとすると、IOExceptionが発生します。

IOException:別のプロセスによって使用されているため、プロセスはファイル'C:\ Whereever\Whatever.pdf.bin'にアクセスできません。

.binファイル(以下の「oleOutputFileName」)を開くファイルハンドルがあり、それを取り除く方法がわかりません。私はCOMについて多くのことを知りません-私はここでそれを翼にしています-そして私はファイルハンドルがどこにあるか、またはそれを解放する方法を知りません。

これが私のC#化されたコードがどのように見えるかです。私は何が欠けていますか?

    public void ExportOleFile(string oleOutputFileName, string emfOutputFileName)
    {
        OLE32.IStorage storage;
        var result = OLE32.StgCreateStorageEx(
            oleOutputFileName,
            OLE32.STGM.STGM_READWRITE | OLE32.STGM.STGM_SHARE_EXCLUSIVE | OLE32.STGM.STGM_CREATE | OLE32.STGM.STGM_TRANSACTED,
            OLE32.STGFMT.STGFMT_DOCFILE,
            0,
            IntPtr.Zero,
            IntPtr.Zero,
            ref OLE32.IID_IStorage,
            out storage
        );

        var CLSID_NULL = Guid.Empty;

        OLE32.IOleObject pOle;
        result = OLE32.OleCreateFromFile(
            ref CLSID_NULL,
            _inputFileName,
            ref OLE32.IID_IOleObject,
            OLE32.OLERENDER.OLERENDER_NONE,
            IntPtr.Zero,
            null,
            storage,
            out pOle
        );

        result = OLE32.OleRun(pOle);

        IntPtr unknownFromOle = Marshal.GetIUnknownForObject(pOle);
        IntPtr unknownForDataObj;
        Marshal.QueryInterface(unknownFromOle, ref OLE32.IID_IDataObject, out unknownForDataObj);
        var pdo = Marshal.GetObjectForIUnknown(unknownForDataObj) as IDataObject;

        var fetc = new FORMATETC();
        fetc.cfFormat = (short)OLE32.CLIPFORMAT.CF_ENHMETAFILE;
        fetc.dwAspect = DVASPECT.DVASPECT_CONTENT;
        fetc.lindex = -1;
        fetc.ptd = IntPtr.Zero;
        fetc.tymed = TYMED.TYMED_ENHMF;

        var stgm = new STGMEDIUM();
        stgm.unionmember = IntPtr.Zero;
        stgm.tymed = TYMED.TYMED_ENHMF;
        pdo.GetData(ref fetc, out stgm);

        var hemf = GDI32.CopyEnhMetaFile(stgm.unionmember, emfOutputFileName);
        storage.Commit((int)OLE32.STGC.STGC_DEFAULT);

        pOle.Close(0);
        GDI32.DeleteEnhMetaFile(stgm.unionmember);
        GDI32.DeleteEnhMetaFile(hemf);
    }

更新1:「。binファイル」が意味するファイルを明確にしました。
更新2:私が取り除きたいものは使い捨てではないので、私は「使用中」のブロックを使用していません。(そして完全に正直に言うと、ファイルハンドルを削除するために何をリリースする必要があるのか​​わかりません。COMは私にとって外国語です。)

4

4 に答える 4

1

コードに少なくとも4つの潜在的なrefcountリークがあります。

OLE32.IStorage storage; // ref counted from OLE32.StgCreateStorageEx(
IntPtr unknownFromOle = Marshal.GetIUnknownForObject(pOle); // ref counted
IntPtr unknownForDataObj; // re counted from Marshal.QueryInterface(unknownFromOle
var pdo = Marshal.GetObjectForIUnknown(unknownForDataObj) as IDataObject; // ref counted

これらはすべてCOMオブジェクトへのポインタであることに注意してください。参照を保持する.NetタイプがRCWラッパーを指し、ファイナライザーで参照カウントを適切に解放しない限り、COMオブジェクトはGCによって収集されません。

IntPtrはそのようなタイプではなく、あなたvarも(コールIntPtrのリターンタイプから)であるため、3つになります。Marshal.GetObjectForIUnknown

Marshal.ReleaseすべてのIntPtr変数を呼び出す必要があります。

よくわかりませんOLE32.IStorage。これには、またはのいずれかが必要な場合がありMarshal.ReleaseますMarshal.ReleaseComPointer

更新:少なくとも1つの参照カウントを見逃していることに気づきました。ではvarなく、です。キャストは暗黙的に実行し、別の参照カウントを追加します。RCWを返しますが、これはGCが起動するまで遅延します。これをブロックで実行して、RCWをアクティブにすることができます。IntPtrIDataObjectasQueryInterfaceGetObjectForIUnknownusingIDisposable

一方、STGMEDIUM構造体にはIUnknown、解放していないポインタも1つあります。ReleaseStgMediumそのポインタを含む構造体全体を適切に破棄するために呼び出す必要があります。

私は疲れすぎて、今すぐコードを調べ続けることができません。明日戻ってきて、他の可能性のある参照カウントのリークを見つけようとします。その間、MSDNドキュメントで、呼び出しているすべてのインターフェイス、構造体、およびAPIを確認し、見逃した可能性のある他の参照カウントを見つけようとします。

于 2010-04-22T03:39:08.467 に答える
0

私は答えを見つけました、そしてそれはかなり簡単です。(単純すぎるかもしれません。ハックのように感じますが、COM プログラミングについてほとんど知らないので、そのまま使用します。)

ストレージ オブジェクトには複数の参照があったため、それらがすべてなくなるまで続行します。

var storagePointer = Marshal.GetIUnknownForObject(storage);
int refCount;
do
{
    refCount = Marshal.Release(storagePointer);
} while (refCount > 0);
于 2010-06-02T05:09:27.843 に答える
0

質問が古いことは知っていますが、これが問題を引き起こしたので、私にとって何がうまくいったかを共有する必要があると感じています.

最初は、Bernard Darnton 自身の回答を使用しようとしました。

var storagePointer = Marshal.GetIUnknownForObject(storage);
int refCount;
do
{
    refCount = Marshal.Release(storagePointer);
} while (refCount > 0);

ただし、このソリューションは最初はうまくいきましたが、最終的にいくつかの副次的な問題が発生しました。

したがって、フランシ・ペノフの回答に従って、コードに次の内容を追加しました。

            OLE32.ReleaseStgMedium(ref stgm);
            Marshal.Release(unknownForDataObj);
            Marshal.Release(unknownFromOle);
            Marshal.ReleaseComObject(storage);
于 2016-12-19T12:25:51.127 に答える