You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用C#调用QPDF DLL实现qpdf --qdf --object-streams=disable命令?

实现QPDF命令的C# PInvoke等效调用

我刚好熟悉QPDF的C++ API和PInvoke调用,帮你梳理下正确的步骤来实现qpdf --qdf --object-streams=disable input.pdf editable.pdf这个命令的等效功能:

核心原理

QPDF的C++ API是面向对象的,所以PInvoke时必须严格遵循它的对象生命周期:

  • 先创建QPDF对象并加载输入PDF
  • 基于这个初始化好的QPDF对象创建QPDFWriter对象
  • 配置QPDFWriter的参数(比如启用QDF模式、禁用对象流)
  • 执行写入操作
  • 最后销毁所有对象,避免内存泄漏

第一步:定义必要的枚举和PInvoke签名

首先要把C里的枚举和函数签名转换成C#的PInvoke格式(函数名是通过dumpbin获取的C mangled名称,不同版本的QPDF DLL可能有差异,需要你对应自己的版本调整):

using System;
using System.Runtime.InteropServices;

// 对应C++中的qpdf_object_stream_e枚举
public enum QpdfObjectStreamMode
{
    qpdf_oas_preserve = 0,
    qpdf_oas_disable = 1,
    qpdf_oas_generate = 2
}

public static class QpdfNative
{
    // QPDF对象相关函数
    [DllImport("qpdf21.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr _ZN3QPDFC1Ev(); // QPDF::QPDF() 构造函数

    [DllImport("qpdf21.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void _ZN3QPDF4readEPKcS1_([In] IntPtr qpdf, [MarshalAs(UnmanagedType.LPStr)] string filename, [MarshalAs(UnmanagedType.LPStr)] string password); // QPDF::read(char const*, char const*)

    [DllImport("qpdf21.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void _ZN3QPDFD1Ev([In] IntPtr qpdf); // QPDF::~QPDF() 析构函数

    // QPDFWriter对象相关函数
    [DllImport("qpdf21.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr _ZN10QPDFWriterC1EPK3QPDFPKc([In] IntPtr qpdf, [MarshalAs(UnmanagedType.LPStr)] string filename); // QPDFWriter::QPDFWriter(QPDF const*, char const*) 构造函数

    [DllImport("qpdf21.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void _ZN10QPDFWriter19setObjectStreamModeE20qpdf_object_stream_e([In] IntPtr writer, QpdfObjectStreamMode mode); // QPDFWriter::setObjectStreamMode(qpdf_object_stream_e)

    [DllImport("qpdf21.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void _ZN10QPDFWriter10setQDFModeEb([In] IntPtr writer, bool value); // QPDFWriter::setQDFMode(bool)

    [DllImport("qpdf21.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void _ZN10QPDFWriter5writeEv([In] IntPtr writer); // QPDFWriter::write()

    [DllImport("qpdf21.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void _ZN10QPDFWriterD1Ev([In] IntPtr writer); // QPDFWriter::~QPDFWriter() 析构函数
}

第二步:完整的调用流程代码

下面是实现目标命令的完整C#代码,严格遵循对象生命周期,解决你之前遇到的内存访问异常问题:

public void ConvertToEditablePdf(string inputPath, string outputPath)
{
    IntPtr qpdf = IntPtr.Zero;
    IntPtr writer = IntPtr.Zero;

    try
    {
        // 1. 创建QPDF对象
        qpdf = QpdfNative._ZN3QPDFC1Ev();
        if (qpdf == IntPtr.Zero)
        {
            throw new InvalidOperationException("Failed to create QPDF object");
        }

        // 2. 加载输入PDF(无密码则传null)
        QpdfNative._ZN3QPDF4readEPKcS1_(qpdf, inputPath, null);

        // 3. 创建QPDFWriter对象,关联到已初始化的QPDF对象
        writer = QpdfNative._ZN10QPDFWriterC1EPK3QPDFPKc(qpdf, outputPath);
        if (writer == IntPtr.Zero)
        {
            throw new InvalidOperationException("Failed to create QPDFWriter object");
        }

        // 4. 启用QDF模式(对应命令行的--qdf参数)
        QpdfNative._ZN10QPDFWriter10setQDFModeEb(writer, true);

        // 5. 禁用对象流(对应命令行的--object-streams=disable参数)
        QpdfNative._ZN10QPDFWriter19setObjectStreamModeE20qpdf_object_stream_e(writer, QpdfObjectStreamMode.qpdf_oas_disable);

        // 6. 执行写入操作
        QpdfNative._ZN10QPDFWriter5writeEv(writer);
    }
    finally
    {
        // 务必销毁对象,避免内存泄漏
        if (writer != IntPtr.Zero)
        {
            QpdfNative._ZN10QPDFWriterD1Ev(writer);
        }
        if (qpdf != IntPtr.Zero)
        {
            QpdfNative._ZN3QPDFD1Ev(qpdf);
        }
    }
}

为什么你之前会遇到AccessViolationException?

直接调用setObjectStreamMode时,你没有先创建有效的QPDFWriter对象,或者这个对象没有关联一个已初始化的QPDF对象。C++的成员函数本质上会隐式传递this指针,PInvoke时如果传入的对象指针无效(比如未初始化的IntPtr),就会访问无效内存,触发这个异常。

额外注意事项

  • 确保qpdf21.dll和它的依赖库(比如加密相关的动态库)都在程序运行目录下
  • 程序位数要和DLL一致(64位程序用64位DLL,32位同理),否则会出现加载错误
  • QPDF的read函数失败时会抛出C++异常,你可以添加SEH异常处理来捕获这类错误

内容的提问来源于stack exchange,提问作者prw56

火山引擎 最新活动