我正在尝试使用此处描述的OLE技术将PDF文件嵌入到Word文档中: 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 \ Whatever.pdf.bin',因为它正由另一个进程使用。
有一个文件句柄打开.bin文件(下面的“oleOutputFileName”),我不知道如何摆脱它。关于COM,我不知道有多少 - 我在这里wing - 我不知道文件句柄在哪里或者如何释放它。
这是我的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对我来说是外语。)
答案 0 :(得分:1)
我发现代码中至少有四个可能的引用计数泄漏:
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
调用的返回类型),因此会生成三个。
您应该在所有IntPtr
个变量上致电Marshal.Release
。
我不确定OLE32.IStorage
。这个可能需要Marshal.Release
或Marshal.ReleaseComPointer
。
更新:我刚注意到我错过了至少一次参考计数。 var
不是IntPtr
,而是IDataObject
。 as
强制转换将执行隐式QueryInterface
并添加另一个引用计数。虽然GetObjectForIUnknown
会返回一个RCW,但是这个会被延迟,直到GC启动。您可能希望在using
块中执行此操作以激活其上的IDisposable
。
同时,STGMEDIUM
结构也有一个IUnknown
指针,你没有发布。你应该调用ReleaseStgMedium
来正确处理整个结构,包括那个指针。
我太累了,无法继续查看代码。我明天会回来,并试图找到其他可能的ref count泄漏。同时,您检查MSDN文档中所有正在调用的接口,结构和API,并尝试找出您可能错过的任何其他引用计数。
答案 1 :(得分:0)
我找到了答案,这很简单。 (可能太简单了 - 感觉就像是一个黑客,但因为我对COM编程知之甚少,所以我只会选择它。)
存储对象上有多个引用,所以只要它们全部消失就继续:
var storagePointer = Marshal.GetIUnknownForObject(storage);
int refCount;
do
{
refCount = Marshal.Release(storagePointer);
} while (refCount > 0);
答案 2 :(得分:0)
我知道问题已经过时了,但由于这给我带来了一些麻烦,我觉得我需要分享对我有用的东西。
起初,我试图使用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);
答案 3 :(得分:0)
我写这篇文章是为了发布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语句中,它“通过将其引用计数设置为0来释放对运行时可调用包装器(RCW)的所有引用。”
如果您想要发布最后创建的引用但保留之前创建的引用,则不适用。
它对我有用,没有内存泄漏。