Question

Je suis en train d'intégrer un fichier PDF dans un document Word en utilisant la technique OLE décrite ici: http://blogs.msdn.com/brian_jones/archive/2009/07/21/embedding-any-file-type-like-pdf-in-an-open-xml-file.aspx

J'ai essayé de mettre en œuvre le code C ++ fourni en C # pour que l'ensemble du projet est en un seul endroit et je suis presque là sauf un barrage routier. Lorsque je tente de nourrir les données binaires d'objets OLE générés dans le document Word, je reçois un IOException.

  

IOException: Le processus ne peut pas accéder au fichier "C: \ Où \ Whatever.pdf.bin., Car il est utilisé par un autre processus

Il y a une poignée de fichier ouvrir le fichier .bin (le « oleOutputFileName » ci-dessous) et je ne sais pas comment se débarrasser de lui. Je ne sais pas une énorme quantité sur COM - Je suis ici envolant -. Et je ne sais pas où le descripteur de fichier est ou comment libérer

Voici ce que mon C # -ised code ressemble. Qu'est-ce que je manque?

    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);
    }

Mise à jour 1: Précisé quel fichier que je voulais dire par « le fichier .bin »
. MISE À JOUR 2: Je ne suis pas en utilisant « en utilisant » blocs parce que les choses que je veux se débarrasser de ne sont pas jetables. (Et pour être parfaitement honnête, je ne suis pas sûr de ce que je dois libérer pour enlever la poignée de fichier, COM étant une langue étrangère pour moi.)

Était-ce utile?

La solution 2

J'ai trouvé la réponse et il est assez simple. (Probablement trop simple -. Il se sent comme un hack mais comme je sais si peu que je COM programmation vais juste aller avec elle)

L'objet de stockage a plusieurs références à ce sujet, donc juste à aller jusqu'à ce qu'ils sont tous partis:

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

Autres conseils

Je vois au moins quatre fuites de Refcount potentiels dans votre code:

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

Notez que tous ces sont des pointeurs vers des objets COM. Les objets COM ne sont pas collectées par GC sauf si le type .Net qui contient les points de référence à une enveloppe de RCW et va bien publier son compte de référence dans son finaliseur.

IntPtr n'est pas ce type et votre var est également IntPtr (du type de retour de l'appel Marshal.GetObjectForIUnknown), de sorte que fait trois.

Vous devriez appeler Marshal.Release sur toutes vos variables IntPtr.

Je ne suis pas sûr de OLE32.IStorage. Celui-ci pourrait avoir besoin soit Marshal.Release ou Marshal.ReleaseComPointer .

Mise à jour: Je viens de remarquer que je manqué au moins un compte de référence. Le var n'est pas un IntPtr, c'est un IDataObject. Le casting de as fera un QueryInterface implicite et ajouter un autre chef ref. Bien que GetObjectForIUnknown retourne un RCW, celui-ci est retardée jusqu'à ce que les coups de pied de GC. Vous pouvez le faire dans le bloc using pour activer le IDisposable dessus.

Pendant ce temps, la struct STGMEDIUM a également un pointeur IUnknown vous n'êtes pas Releasing. Vous devez appeler ReleaseStgMedium pour éliminer correctement les struct tout, y compris ce pointeur.

Je suis trop fatigué pour continuer à regarder à travers le code en ce moment. Je reviendrai demain et essayer de trouver d'autres fuites de comptage possibles réf. Pendant ce temps, vous vérifiez la documentation MSDN pour toutes les interfaces, struct et les API que vous appelez et essayer de comprendre tout autre compte ref vous auriez pu manquer.

Je sais que la question est vieux, mais je sens que je dois que cela m'a causé quelques problèmes à partager ce qui a fonctionné pour moi.

Dans un premier temps, je l'ai essayé d'utiliser propre réponse de Bernard Darnton:

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

Cependant, même si la solution a fonctionné au début, il a fini par causer des problèmes collatéraux.

Alors, après réponse Franci Penov, j'ajouté ce qui suit au code:

            OLE32.ReleaseStgMedium(ref stgm);
            Marshal.Release(unknownForDataObj);
            Marshal.Release(unknownFromOle);
            Marshal.ReleaseComObject(storage);

J'ai écrit cela pour libérer des objets 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);
                }
            }
        }
    }

Vous passez les objets que vous souhaitez libérer par exemple dans une finally et il « libère toutes les références à un Runtime Callable Wrapper (RCW) en fixant son compteur de références à 0. »

Il ne convient pas si vous voulez libérer la dernière référence créé, mais garder les références créées avant.

Il a travaillé pour moi sans fuites de mémoire.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top