Освобождение дескриптора файла OLE IStorage в C # - PullRequest
3 голосов
/ 22 апреля 2010

Я пытаюсь встроить PDF-файл в документ Word, используя метод OLE, описанный здесь: http://blogs.msdn.com/brian_jones/archive/2009/07/21/embedding-any-file-type-like-pdf-in-an-open-xml-file.aspx

Я пытался реализовать код C ++, предоставляемый в C #, чтобы весь проект находился в одном месте и был почти там, за исключением одного контрольно-пропускного пункта. Когда я пытаюсь передать сгенерированные двоичные данные объекта OLE в документ Word, я получаю IOException.

IOException: процесс не может получить доступ к файлу 'C: \ Wherever \ Whever.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 ]

1 голос
/ 22 апреля 2010

Я вижу по крайней мере четыре потенциальных утечки в вашем коде:

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-объекты. COM-объекты не собираются GC, если только .Net-тип, который содержит ссылки, указывает на оболочку RCW и должным образом не выпустит счетчик ссылок в своем финализаторе.

IntPtr не относится к этому типу, и ваш var также равен IntPtr (из типа возврата вызова Marshal.GetObjectForIUnknown), так что получается три.

Вы должны вызвать Marshal.Release для всех ваших IntPtr переменных.

Я не уверен насчет OLE32.IStorage. Для этого может потребоваться Marshal.Release или Marshal.ReleaseComPointer.

Обновление: Я только что заметил, что пропустил хотя бы один счетчик ссылок. var это не IntPtr, это IDataObject. as приведёт к неявному QueryInterface и добавит еще один счетчик ссылок. Хотя GetObjectForIUnknown возвращает RCW, этот задерживается до тех пор, пока не активируется GC. Возможно, вы захотите сделать это в блоке using, чтобы активировать IDisposable на нем.

Между тем, структура STGMEDIUM также имеет один указатель IUnknown, который вы не отпускаете. Вы должны вызвать ReleaseStgMedium, чтобы правильно утилизировать всю структуру, включая этот указатель.

Я слишком устал, чтобы продолжать просматривать код прямо сейчас. Я вернусь завтра и попытаюсь найти другие возможные утечки. Тем временем вы проверяете документы MSDN для всех интерфейсов, структур и API, которые вы вызываете, и пытаетесь выяснить любые другие ссылки, которые вы, возможно, пропустили.

0 голосов
/ 23 марта 2017

Я написал это для освобождения объектов com:

public static void ReleaseComObjects(params object[] objects)
    {
        if (objects == null)
        {
            return;
        }

        foreach (var obj in objects)
        {
            if (obj != null)
            {
                try
                {
                    Marshal.FinalReleaseComObject(obj);
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                }
            }
        }
    }

Вы передаете объекты, которые хотите освободить, например, в операторе finally, и он "освобождает все ссылки на Runtime Callable Wrapper (RCW) путем установкиего счетчик ссылок равен 0. "

Это не подходит, если вы хотите освободить последнюю созданную ссылку, но сохранить ссылки, созданные ранее.

Это сработало для меня снет утечек памяти.

0 голосов
/ 19 декабря 2016

Я знаю, что вопрос старый, но так как это вызвало у меня некоторые проблемы, я чувствую, что мне нужно поделиться тем, что сработало для меня.

Сначала я попытался использовать собственный ответ Бернарда Дарнтона:

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);
0 голосов
/ 02 июня 2010

Я нашел ответ, и это довольно просто. (Возможно, слишком просто - это похоже на хак, но так как я так мало знаю о программировании COM, я просто собираюсь пойти на это.)

Объект хранилища имел несколько ссылок на него, поэтому просто продолжайте, пока все они не исчезнут:

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