gpt4 book ai didi

c# - 从 .NET 传递到 VBA 的 COM 对象的对象生命周期

转载 作者:行者123 更新时间:2023-11-30 16:10:11 25 4
gpt4 key购买 nike

我的组织有时需要使用 Excel 生成一堆格式化的报表(从文档的意义上说“您的账户余额是 $X”),将它们打印成 PDF,然后将它们合并成一个大的 PDF。通常使用的方法涉及由索引单元格驱动的单个工作表和另一工作表上的人员/数据列表。 VBA 宏将索引单元格从 1 迭代到 N,然后使用 Adob​​e Distiller API 每次打印格式化的工作表并组合结果。

出于各种原因,我想在我们的 VSTO Excel 加载项中用 C# 实现此宏的大部分逻辑,以便该过程的 VBA 端减少到几行。

我决定公开一个大致如下所示的 API:

AcroPDDoc PdfBegin(Worksheet worksheet, string filename);
void PdfAddPage(AcroPDDoc pdf, Worksheet worksheet);
void PdfComplete(AcroPDDoc pdf);

您的想法是编写以下形式的 VBA:

Sub PrintToPdf()
Dim obj As IMySharedObject
Set obj = Application.COMAddIns("MyAddIn").Object

Dim pdf As Acrobat.AcroPDDoc

Dim i As Long
For i = 1 To 10
Range("counter").Value = i

If i = 1 Then
Set pdf = obj.PdfBegin(Sheets("Statement"), "C:\myFile.pdf")
Else
PdfAddPage pdf, Sheets("Statement")
End If
Next i

PdfComplete pdf
End Sub

我对 AcroPDDoc 对象的生命周期以及打开的文件句柄、Acrobat.exe 进程等感到好奇/担心,以防宏遇到错误或在执行中途终止。 super 并不担心,因为必要时“关闭 Excel 并重新打开”是一个可接受的解决方案。我用 C# 编写了以下代码:

internal static class Printing
{
private static WeakReference weakref;

public static AcroPDDoc PdfBegin(Worksheet worksheet, string filename)
{
SetAdobeOutputFile(filename);
worksheet.PrintOut(ActivePrinter: "Adobe PDF");

AcroPDDoc pdf = new AcroPDDoc();
pdf.Open(filename);
weakref = new WeakReference(pdf);

return pdf;
}

public static void GC()
{
System.GC.Collect();
}

public static void test(AcroPDDoc pdf)
{
if (weakref != null) {
System.Diagnostics.Debug.WriteLine("IsAlive pre: " + weakref.IsAlive);
if (weakref.IsAlive) System.Diagnostics.Debug.WriteLine("ReferenceEquals: " + Object.ReferenceEquals(pdf, weakref.Target));
}

GC.Collect();

if (weakref != null) System.Diagnostics.Debug.WriteLine("IsAlive post: " + weakref.IsAlive);
}
}

我已经删除了一堆额外的 Debug.WriteLine 和一些其他无关的代码。我使用以下 VBA 对其进行了测试:

Sub foo()
Dim obj As IUDFSharedObject
Set obj = Application.COMAddIns("MyAddIn").Object

Dim pdf As Acrobat.AcroPDDoc
Set pdf = obj.PdfBegin(Sheets("Statement"), "C:\myFile.pdf")
'obj.GC
'obj.test pdf
End Sub

我发现一般情况下,.NET 在其垃圾收集引用计数中不包括发送到 VBA 领域的引用。

例如,如果我只取消注释 obj.GCobj.test pdf,我会被告知 weakref 不存在。

但是,如果我只取消注释 obj.test pdfweakref 在之前和之后都有效(并且我发出“ReferenceEquals: true”)。

请注意 pdf 一直在 VBA 范围内。我最初测试过如果让 pdf 也转义 VBA 范围会发生什么,但事实证明这无关紧要。

这对我来说是一个比资源链接大得多的问题。除了将 List 中生成的每个 AcroPDDoc 对象永久存储在某处以使引用计数保持在零以上之外,是否有任何解决方案?

最佳答案

感谢上面的@yms,我已经弄清楚发生了什么,并提出了一个我相当满意的解决方案。首先,对 API 进行轻微修改:

void PdfBegin(AcroPDDoc pdf, Worksheet worksheet, string filename);
void PdfAddPage(AcroPDDoc pdf, Worksheet worksheet);
void PdfComplete(AcroPDDoc pdf);

每个 C# 方法在返回之前都会调用 Mashal.ReleaseComObject(pdf)。我读过Marshal.ReleaseComObject considered dangerous ,但我测试了他所说的特定故障模式,发现它在实践中似乎并未发生。

VBA 现在必须从一开始就提供 AcroPDDoc 对象。因此,典型的用法如下所示:

Sub PrintToPdf()
Dim obj As IMySharedObject
Set obj = Application.COMAddIns("MyAddIn").Object

Dim pdf As New AcroPDDoc

Dim i As Long
For i = 1 To 10
Range("counter").Value = i

If i = 1 Then
obj.PdfBegin pdf, Sheets("Statement"), "C:\myFile.pdf"
Else
obj.PdfAddPage pdf, Sheets("Statement")
End If
Next i

obj.PdfComplete pdf
End Sub

本质上只是声明现在是 As New AcroPDDoc 而不是 As AcroPDDoc 和后来的 Set

测试表明,一旦 AcroPDDoc 的引用计数超出范围或引用设置为 Nothing,VBA 会非常迅速地减少它的引用计数。这包括在子例程中出现错误并且用户结束执行的情况。

最后,Acrobat.exe 进程也会在其引用计数达到零时提示自行终止,即使它打开了一个文件也是如此。

关于c# - 从 .NET 传递到 VBA 的 COM 对象的对象生命周期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26288984/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com