【发布时间】:2025-12-01 04:30:01
【问题描述】:
我的组织有时需要使用 Excel 来生成一堆格式化的报表(在文档的意义上说“您的帐户余额是 $X”),将它们打印成 PDF,然后将它们组合成一个大 PDF。通常使用的方法涉及由索引单元驱动的单个工作表和另一个工作表上的人员/数据列表。 VBA 宏从 1 到 N 迭代索引单元格,然后每次使用 Adobe 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
我对@987654323@ 对象的生命周期以及打开文件句柄、Acrobat.exe 进程等感到好奇/担心,以防宏遇到错误或在执行过程中终止。不用超级担心,因为“关闭 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.WriteLines 和一些其他无关的代码。我使用以下 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-land 的引用。
例如,如果我只取消注释 obj.GC 和 obj.test pdf,我会被告知 weakref 不存在。
但是,如果我只取消注释 obj.test pdf,weakref 在之前和之后都是有效的(并且我发出“ReferenceEquals: true”)。
请注意,pdf一直都在 VBA 的范围内。我最初测试了如果你让pdf 也逃逸 VBA 范围会发生什么,但事实证明这并不重要。
这对我来说是一个比资源链接大得多的问题。除了永久存储在List 中生成的每个AcroPDDoc 对象以保持引用计数高于零之外,是否有任何解决方案?
【问题讨论】:
-
++ 用于格式良好的问题。一个问题可能是为什么不完全消除 VBA 的使用而只依赖 .NET 调用。 IE。您可以在 .NET 加载项中初始化并设置对工作表/单元格的引用并从 .NET 调用 PDF API?
-
其实我是要公开这样一个功能的(虽然上面没有提到)。我也需要这些函数的原因是,我已经知道很多逻辑不能准确地确认这种模式的情况——例如某些索引被跳过,或者有额外的逻辑来决定为每个索引打印出哪个工作表。