如何从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); } }
三、关键注意事项
- 必须严格执行内存生命周期管理:
GlobalLock→处理数据→GlobalUnlock→GlobalFree的流程不能少,否则会造成内存泄漏,甚至影响DTWAIN后续的扫描操作。 - DIB与BMP的区别:DTWAIN返回的是纯DIB数据(仅包含位图信息头+调色板+像素数据),而标准BMP文件需要额外的文件头,所以我们要手动构造
BITMAPFILEHEADER才能生成可正常打开的BMP文件。 - 为什么SafeFileHandle没用?:
SafeFileHandle只负责管理内核对象句柄,而全局内存句柄属于用户态内存资源,两者是完全不同的句柄类型,操作系统不会把内存句柄识别为合法的IO句柄。
内容来源于stack exchange
相关产品推荐
相关产品推荐

