在C#中释放OLE ISTORAGE文件句柄
题
我正在尝试使用此处描述的OLE技术将PDF文件嵌入Word文档中:http://blogs.msdn.com/brian_jones/archive/2009/07/21/embedding-any-file-type-like-pdf-pdf-in-an-an-open-open-xml-file.aspx
我尝试实现C#中提供的C ++代码,以使整个项目都位于一个地方,几乎在那里,除了一个路障。当我尝试将生成的Ole Object二进制数据馈送到Word文档中时,我会得到IOException。
IOException:该过程无法访问文件'c: whyere whying.pdf.bin',因为另一个过程正在使用它。
有一个文件句柄打开.bin文件(下面的“ OleoutputfileName”),我不知道如何摆脱它。我不知道关于com的大量数量 - 我在这里挥动它 - 我不知道文件句柄在哪里或如何释放它。
这是我的C#-ISED代码的样子。我想念什么?
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:我不使用“使用”块,因为我想摆脱的东西不可支配。 (说实话,我不确定我需要发布什么来删除文件句柄,对我来说是外语。)
解决方案 2
我找到了答案,这很简单。 (可能太简单了 - 感觉像是一个黑客,但由于我对com编程的了解鲜为人知,所以我将继续使用它。)
存储对象上有多个参考,因此请继续前进直到它们全部消失:
var storagePointer = Marshal.GetIUnknownForObject(storage);
int refCount;
do
{
refCount = Marshal.Release(storagePointer);
} while (refCount > 0);
其他提示
我看到您的代码中至少有四个潜在的重新数泄漏:
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包装器,否则不会由GC收集COM对象,并将在最终确定器中正确释放其参考计数。
IntPtr
不是这样的类型 var
也是 IntPtr
(从返回类型 Marshal.GetObjectForIUnknown
打电话),这使三个。
你应该打电话 Marshal.Release
一切 IntPtr
变量。
我不确定 OLE32.IStorage
. 。这个也许需要 Marshal.Release
或者 Marshal.ReleaseComPointer
.
更新: 我只是注意到我至少错过了一个裁判数。这 var
不是 IntPtr
, , 这是一个 IDataObject
. 。这 as
演员会隐含 QueryInterface
并添加另一个裁判计数。虽然 GetObjectForIUnknown
返回一个RCW,该rcw被延迟到GC启动为止。您可能想在 using
块激活 IDisposable
在上面。
同时, STGMEDIUM
结构也有一个 IUnknown
指针您没有发布。你应该打电话 ReleaseStgMedium
正确处理整个结构,包括该指针。
我太累了,无法继续查看代码。我明天回来,尝试找到其他可能的裁判计数泄漏。同时,您检查了您正在调用的所有接口,结构和API的MSDN文档,并尝试找出您可能错过的任何其他裁判计数。
我知道这个问题很旧,但是由于这给我带来了麻烦,我觉得我需要分享对我有用的东西。
起初,我尝试使用伯纳德·达恩顿(Bernard Darnton)的答案:
var storagePointer = Marshal.GetIUnknownForObject(storage); int refCount; do { refCount = Marshal.Release(storagePointer); } while (refCount > 0);
但是,即使该解决方案起初有效,最终还是引起了一些抵押问题。
因此,在Franci Penov回答之后,我在代码中添加了以下内容:
OLE32.ReleaseStgMedium(ref stgm);
Marshal.Release(unknownForDataObj);
Marshal.Release(unknownFromOle);
Marshal.ReleaseComObject(storage);
我已经为发布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);
}
}
}
}
您将要在最后的语句中发布的对象传递,并“通过将其参考计数设置为0来释放所有引用到运行时可呼叫包装器(RCW)。”
如果您想发布最后创建的参考,但请保留之前创建的引用,则不合适。
它对我有用,没有内存泄漏。