C# ITextSharp中的PdfCopyForms导致堆栈溢出错误
在这种方法中,我试图从一个PDF文档中获取输入字段,将它们粘贴到另一个文档中,并将结果打印为PDF文件。结果将是一个新的PDF文件,其中包含第一个PDF的输入字段和第二个PDF的静态内容 我编写了一些我认为可以执行此任务的代码,但每次执行“copier.close()”时都会遇到StackOverflow错误。这是它抛出的错误:C# ITextSharp中的PdfCopyForms导致堆栈溢出错误,c#,itextsharp,stack-overflow,C#,Itextsharp,Stack Overflow,在这种方法中,我试图从一个PDF文档中获取输入字段,将它们粘贴到另一个文档中,并将结果打印为PDF文件。结果将是一个新的PDF文件,其中包含第一个PDF的输入字段和第二个PDF的静态内容 我编写了一些我认为可以执行此任务的代码,但每次执行“copier.close()”时都会遇到StackOverflow错误。这是它抛出的错误: An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
代码如下:
public static void AddFormFieldsFromSource(string sourcePath, string secondSourcePath, string targetPath) {
lock (syncLock) {
PdfReader.unethicalreading = true;
PdfReader readerMain = new PdfReader(sourcePath);
FileStream stream = new FileStream(targetPath, FileMode.Create, FileAccess.Write);
PdfCopyForms copier = new PdfCopyForms(stream);
PdfReader secondSourceReader = new PdfReader(secondSourcePath);
copier.AddDocument(secondSourceReader);
copier.CopyDocumentFields(readerMain);
copier.Close();
secondSourceReader.Close();
}
}
源路径是我获取输入字段的来源,第二个源路径是我获取静态内容的来源
我用于SourcePath变量的PDF位于此处:
我用于secondSourcePath变量的PDF位于以下位置:
另外,我使用的是ITextSharp版本5.5.0
知道为什么会抛出StackOverflow错误吗?我不在代码中进行任何递归调用。我的第一个猜测是我试图错误地完成这项任务。另一种可能是ITextSharp可能有一个bug
更新:我将源代码下载到最新版本的ITextSharp(5.5.1),构建了一个dll以便调试,然后在代码中引用了该dll。此方法中的类PdfIndirectReference中出现堆栈溢出错误:
public class PdfIndirectReference : PdfObject {
....
internal PdfIndirectReference(int type, int number, int generation) : base(0, new StringBuilder().Append(number).Append(' ').Append(generation).Append(" R").ToString()) {
this.number = number;
this.generation = generation;
}
在dll代码的调用堆栈中,我发现它在
itextsharp.text.pdf.PdfCopyFieldsImp.Propagate()
这一定是堆栈溢出发生的原因
因此,它不会出现在我的代码中,而是出现在dll中。你知道如何解决这个问题吗?我用iText和Java复制了这个问题;同样的问题也发生在这里,所以原因很可能是相同的
PdfCopyForms
在内部使用PdfCopyFormsImp
,它是从PdfCopyFieldsImp
派生的。后一个类提供了执行字段和表单复制的繁重工作的基本方法,其中包括propagate
,当堆栈溢出时,OP在调用堆栈中多次发现该方法
与观察到的堆栈溢出留下的印象相反,PdfCopyFieldsImp
确实有一种机制,通过标记已访问的对象来防止无休止的循环:
/**
* Sets a reference to "visited" in the copy process.
* @param ref the reference that needs to be set to "visited"
* @return true if the reference was set to visited
*/
protected boolean setVisited(PRIndirectReference ref) {
IntHashtable refs = visited.get(ref.getReader());
if (refs != null)
return refs.put(ref.getNumber(), 1) != 0;
else
return false;
}
此方法同时将来自某个PdfReader
的对象引用标记为已访问,并返回以前是否访问过该对象
至少对于所有PdfReader
实例的引用,在已访问的
映射中有一个条目,它是这样做的,来自PdfReader
实例的引用,如果没有这样的条目,则总是声称尚未访问(返回false
)。因此,在多次访问的情况下,后一类读者的参考文献不会被视为已访问
PdfReader
实例在已访问的
映射中仅在一个代码位置获取条目:只有使用addDocument添加到副本的读卡器才能获取该条目
使用PdfCopyForms
将表单字段从一个文档添加到另一个PDF文档中,显然不会对要复制表单的读者使用addDocument
,而是使用copyDocumentFields
。因此,循环预防在这里不起作用
通过为复制表单的读卡器在已访问的
映射中添加条目,可以防止堆栈溢出。我是在PdfCopyFormsImp.copyDocumentFields
public void copyDocumentFields(PdfReader reader) throws DocumentException {
if (!reader.isOpenedWithFullPermissions())
throw new IllegalArgumentException(MessageLocalization.getComposedMessage("pdfreader.not.opened.with.owner.password"));
if (readers2intrefs.containsKey(reader)) {
reader = new PdfReader(reader);
}
else {
if (reader.isTampered())
throw new DocumentException(MessageLocalization.getComposedMessage("the.document.was.reused"));
reader.consolidateNamedDestinations();
reader.setTampered(true);
}
reader.shuffleSubsetNames();
readers2intrefs.put(reader, new IntHashtable());
visited.put(reader, new IntHashtable()); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
fields.add(reader.getAcroFields());
updateCalculationOrder(reader);
}
免责声明:我没有检查PdfCopyForms
在此次更改后是否完全按照要求工作。我只是用Java对它进行了测试,只发现不再发生堆栈溢出,OP用例中生成的PDF看起来还可以;同样的问题也发生在这里,所以原因很可能是相同的
PdfCopyForms
在内部使用PdfCopyFormsImp
,它是从PdfCopyFieldsImp
派生的。后一个类提供了执行字段和表单复制的繁重工作的基本方法,其中包括propagate
,当堆栈溢出时,OP在调用堆栈中多次发现该方法
与观察到的堆栈溢出留下的印象相反,PdfCopyFieldsImp
确实有一种机制,通过标记已访问的对象来防止无休止的循环:
/**
* Sets a reference to "visited" in the copy process.
* @param ref the reference that needs to be set to "visited"
* @return true if the reference was set to visited
*/
protected boolean setVisited(PRIndirectReference ref) {
IntHashtable refs = visited.get(ref.getReader());
if (refs != null)
return refs.put(ref.getNumber(), 1) != 0;
else
return false;
}
此方法同时将来自某个PdfReader
的对象引用标记为已访问,并返回以前是否访问过该对象
至少对于所有PdfReader
实例的引用,在已访问的
映射中有一个条目,它是这样做的,来自PdfReader
实例的引用,如果没有这样的条目,则总是声称尚未访问(返回false
)。因此,在多次访问的情况下,后一类读者的参考文献不会被视为已访问
PdfReader
实例在已访问的
映射中仅在一个代码位置获取条目:只有使用addDocument添加到副本的读卡器才能获取该条目
使用PdfCopyForms
将表单字段从一个文档添加到另一个PDF文档中,显然不会对要复制表单的读者使用addDocument
,而是使用copyDocumentFields
。因此,循环预防在这里不起作用
通过为复制表单的读卡器在已访问的
映射中添加条目,可以防止堆栈溢出。我是在PdfCopyFormsImp.copyDocumentFields
public void copyDocumentFields(PdfReader reader) throws DocumentException {
if (!reader.isOpenedWithFullPermissions())
throw new IllegalArgumentException(MessageLocalization.getComposedMessage("pdfreader.not.opened.with.owner.password"));
if (readers2intrefs.containsKey(reader)) {
reader = new PdfReader(reader);
}
else {
if (reader.isTampered())
throw new DocumentException(MessageLocalization.getComposedMessage("the.document.was.reused"));
reader.consolidateNamedDestinations();
reader.setTampered(true);
}
reader.shuffleSubsetNames();
readers2intrefs.put(reader, new IntHashtable());
visited.put(reader, new IntHashtable()); //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
fields.add(reader.getAcroFields());
updateCalculationOrder(reader);
}
免责声明:我没有检查PdfCopyForms
在此次更改后是否完全按照要求工作。我只是用Java对它进行了测试,只发现不再发生堆栈溢出,OP用例中生成的PDF看起来还可以。我使用iText和Jav重现了这个问题