You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从DTwain返回的DIB句柄(IntPtr)读取数据并写入文件?

如何从DTwain返回的DIB句柄(IntPtr)读取数据并写入文件?

你遇到的核心问题是对句柄类型的误解:你拿到的IntPtr是Windows的全局内存块句柄(HGLOBAL),指向的是内存中的DIB(设备无关位图)数据,而非操作系统的文件/IO句柄。SafeFileHandle和FileStream是专门用于管理内核对象句柄(比如文件、进程、管道)的类,完全不适用这种内存句柄,这就是为什么你会收到“句柄无效”的报错。

根据DTWAIN的文档要求,你必须手动完成全局内存的锁定、数据读取、解锁和释放流程——这一步绕不开,和SafeFileHandle没有任何关系。下面是具体的解决步骤和代码示例:

一、先准备必要的Windows API声明和结构体

要处理全局内存和DIB数据,我们需要P/Invoke调用Windows的内存操作API,同时定义DIB和BMP文件的相关结构体:

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

// Windows内存操作API的P/Invoke声明
public static class Win32MemoryApi
{
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr GlobalLock(IntPtr hMem);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GlobalUnlock(IntPtr hMem);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr GlobalFree(IntPtr hMem);
}

// DIB的位图信息头结构体(描述位图的尺寸、色彩等信息)
[StructLayout(LayoutKind.Sequential)]
public struct BITMAPINFOHEADER
{
    public uint biSize;
    public int biWidth;
    public int biHeight;
    public ushort biPlanes;
    public ushort biBitCount;
    public uint biCompression;
    public uint biSizeImage;
    public int biXPelsPerMeter;
    public int biYPelsPerMeter;
    public uint biClrUsed;
    public uint biClrImportant;
}

// BMP文件头结构体(生成合法BMP文件必须的头部)
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct BITMAPFILEHEADER
{
    public ushort bfType;
    public uint bfSize;
    public ushort bfReserved1;
    public ushort bfReserved2;
    public uint bfOffBits;
}

二、修改扫描后的DIB句柄处理逻辑

替换你原来错误的SafeFileHandle/FileStream代码,改用以下流程处理DIB句柄:

// 拿到DIB句柄后开始处理
var dibHandle = TwainAPI.DTWAIN_GetAcquiredImage(images, 0, 0);
Console.WriteLine($"Handle: {dibHandle} - {dibHandle.ToInt64():X}");

if (dibHandle != IntPtr.Zero)
{
    // 1. 锁定全局内存,获取DIB数据的直接访问指针
    IntPtr dibDataPtr = Win32MemoryApi.GlobalLock(dibHandle);
    if (dibDataPtr == IntPtr.Zero)
    {
        Console.WriteLine("无法锁定全局内存块!");
        return;
    }

    try
    {
        // 2. 解析DIB的信息头,获取位图的基本参数
        BITMAPINFOHEADER bmpInfo = Marshal.PtrToStructure<BITMAPINFOHEADER>(dibDataPtr);

        // 3. 计算DIB的总大小:信息头 + 调色板(如果有) + 像素数据
        uint paletteSize = 0;
        if (bmpInfo.biBitCount <= 8)
        {
            // 8位及以下的位图有调色板,每个调色板项占4字节
            paletteSize = (uint)(1 << bmpInfo.biBitCount) * 4;
        }
        uint totalDibSize = bmpInfo.biSize + paletteSize + bmpInfo.biSizeImage;

        // 4. 把DIB数据从内存复制到字节数组
        byte[] dibBytes = new byte[totalDibSize];
        Marshal.Copy(dibDataPtr, dibBytes, 0, (int)totalDibSize);

        // 5. 构造BMP文件头(DIB本身没有文件头,需要手动添加才能生成可打开的BMP文件)
        BITMAPFILEHEADER bmpFileHeader = new BITMAPFILEHEADER
        {
            bfType = 0x4D42, // 对应ASCII的"BM",BMP文件的标识
            bfSize = totalDibSize + (uint)Marshal.SizeOf<BITMAPFILEHEADER>(),
            bfOffBits = (uint)Marshal.SizeOf<BITMAPFILEHEADER>() + bmpInfo.biSize + paletteSize
        };

        // 6. 将BMP文件头和DIB数据写入磁盘
        string outputPath = @"C:\Users\ckeller\Downloads\testImage.bmp";
        using (var fs = File.OpenWrite(outputPath))
        {
            // 先写入BMP文件头
            byte[] fileHeaderBytes = new byte[Marshal.SizeOf<BITMAPFILEHEADER>()];
            IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf<BITMAPFILEHEADER>());
            Marshal.StructureToPtr(bmpFileHeader, headerPtr, false);
            Marshal.Copy(headerPtr, fileHeaderBytes, 0, fileHeaderBytes.Length);
            Marshal.FreeHGlobal(headerPtr);
            fs.Write(fileHeaderBytes, 0, fileHeaderBytes.Length);

            // 再写入DIB的核心数据
            fs.Write(dibBytes, 0, dibBytes.Length);
        }

        Console.WriteLine($"BMP文件已成功保存到: {outputPath}");
    }
    finally
    {
        // 7. 必须解锁全局内存,否则会导致内存泄漏
        Win32MemoryApi.GlobalUnlock(dibHandle);
        // 8. 释放全局内存句柄,归还系统资源
        Win32MemoryApi.GlobalFree(dibHandle);
    }
}

三、关键注意事项

  1. 必须严格执行内存生命周期管理:GlobalLock→处理数据→GlobalUnlock→GlobalFree的流程不能少,否则会造成内存泄漏,甚至影响DTWAIN后续的扫描操作。
  2. DIB与BMP的区别:DTWAIN返回的是纯DIB数据(仅包含位图信息头+调色板+像素数据),而标准BMP文件需要额外的文件头,所以我们要手动构造BITMAPFILEHEADER才能生成可正常打开的BMP文件。
  3. 为什么SafeFileHandle没用?:SafeFileHandle只负责管理内核对象句柄,而全局内存句柄属于用户态内存资源,两者是完全不同的句柄类型,操作系统不会把内存句柄识别为合法的IO句柄。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 07:03:12