WIA in C#的各种坑

zsx in 代码笔记 / 3 / 6162

哎,Windows下的扫描仪API Windows Image Acquisition (WIA) (https://msdn.microsoft.com/en-us/library/windows/desktop/ms630368(v=vs.85).aspx)真是难用到令人发指,弄一篇文章记录下。

首先是错误码定义与错误提示汉化(完整列表:https://msdn.microsoft.com/en-us/library/windows/desktop/ms630183(v=vs.85).aspx

    class WIAErrorData

    {

        public ulong Code;

        public string Meaning;

        public string ErrorCode;

    }

    class WIAError

    {

        private static Dictionary<ulong, WIAErrorData> ErrorData = new Dictionary<ulong, WIAErrorData>()

        {

            {0x80210006, new WIAErrorData() { Code = 0x80210006, ErrorCode = "WIA_ERROR_BUSY", Meaning = "扫描仪正被别的软件使用。关闭正在使用扫描仪的任何应用程序或等待它完成,然后重试。" }},

            {0x80210016, new WIAErrorData() { Code = 0x80210016, ErrorCode = "WIA_ERROR_COVER_OPEN", Meaning = "扫描仪盖子被打开,请关闭。" }},

            {0x8021000A, new WIAErrorData() { Code = 0x8021000A, ErrorCode = "WIA_ERROR_DEVICE_COMMUNICATION", Meaning = "与扫描仪通信失败。确保设备已开机并连接到PC。如果问题仍然存在,请断开并重新连接扫描仪。" }},

            {0x8021000D, new WIAErrorData() { Code = 0x8021000D, ErrorCode = "WIA_ERROR_DEVICE_LOCKED", Meaning = "扫描仪被锁定。关闭正在使用扫描仪的任何应用程序或等待它完成,然后重试。" }},

            {0x8021000E, new WIAErrorData() { Code = 0x8021000E, ErrorCode = "WIA_ERROR_EXCEPTION_IN_DRIVER", Meaning = "扫描仪驱动程序异常。" }},

            {0x80210001, new WIAErrorData() { Code = 0x80210001, ErrorCode = "WIA_ERROR_GENERAL_ERROR", Meaning = "与扫描仪通讯发生未知错误" }},

            {0x8021000C, new WIAErrorData() { Code = 0x8021000C, ErrorCode = "WIA_ERROR_INCORRECT_HARDWARE_SETTING", Meaning = "扫描仪配置不正确。" }},

            {0x8021000B, new WIAErrorData() { Code = 0x8021000B, ErrorCode = "WIA_ERROR_INVALID_COMMAND", Meaning = "该设备不支持此命令。" }},

            {0x8021000F, new WIAErrorData() { Code = 0x8021000F, ErrorCode = "WIA_ERROR_INVALID_DRIVER_RESPONSE", Meaning = "扫描仪驱动程序响应无效。" }},

            {0x80210009, new WIAErrorData() { Code = 0x80210009, ErrorCode = "WIA_ERROR_ITEM_DELETED", Meaning = "扫描仪被删除,不再可用。" }},

            {0x80210017, new WIAErrorData() { Code = 0x80210017, ErrorCode = "WIA_ERROR_LAMP_OFF", Meaning = "扫描仪的灯灭了。" }},

            {0x80210021, new WIAErrorData() { Code = 0x80210021, ErrorCode = "WIA_ERROR_MAXIMUM_PRINTER_ENDORSER_COUNTER", Meaning = "扫描被中断,扫描仪扫描页数达到最大。" }},

            {0x80210020, new WIAErrorData() { Code = 0x80210020, ErrorCode = "WIA_ERROR_MULTI_FEED", Meaning = "同时有多张进纸,扫描出现错误。" }},

            {0x80210005, new WIAErrorData() { Code = 0x80210005, ErrorCode = "WIA_ERROR_OFFLINE", Meaning = "扫描仪没开机。" }},

            {0x80210002, new WIAErrorData() { Code = 0x80210002, ErrorCode = "WIA_ERROR_PAPER_JAM", Meaning = "扫描仪卡纸。" }},

            {0x80210004, new WIAErrorData() { Code = 0x80210004, ErrorCode = "WIA_ERROR_PAPER_PROBLEM", Meaning = "扫描仪的进纸器出现未知问题。" }},

            {0x80210007, new WIAErrorData() { Code = 0x80210007, ErrorCode = "WIA_ERROR_WARMING_UP", Meaning = "扫描仪正在预热。" }},

            {0x80210008, new WIAErrorData() { Code = 0x80210008, ErrorCode = "WIA_ERROR_USER_INTERVENTION", Meaning = "扫描仪出现问题。确保其已经开机且线路连接正确。" }},

            {0x80210015, new WIAErrorData() { Code = 0x80210015, ErrorCode = "WIA_S_NO_DEVICE_AVAILABLE", Meaning = "没有找到扫描仪。确保设备已开机并连接到电脑。" }}



        };

        public static WIAErrorData ParseError(int ErrorCode)

        {

            var UInt = Convert.ToUInt32(4294967296 + ErrorCode);

            if (ErrorData.ContainsKey(UInt)) {

                return ErrorData[UInt];

            }

            throw new Exception("Unknown ErrorCode in WIAError: " + ErrorCode);

        }

    }

直接复制这里代码的,要注意一下我没写0x80210003,因为这个我拿来当完成判断。


然后继续吐槽一下市面上各种代码。

比如这两篇文章的代码:

http://forums.codeguru.com/showthread.php?439027-Windows-Image-Acquisition-(WIA)-Code

http://www.codeproject.com/Tips/792316/WIA-Scanner-in-Csharp-Windows-Forms

还有这几个项目的代码

https://github.com/gideondsouza/AutoDocumentFeed_for_WIA

https://github.com/rbobot/rbscan


建议不要测试了。就像这个答案说的一样:

http://stackoverflow.com/questions/32708547/scanning-with-wia-automatic-feeder-scanner-fails-for-second-page

I tested your code on hp scanjet 5590, it only manages to get one image, even when I step through the code in debugger. On second call dialog.ShowTransfer throws ArgumentException with message "Value does not fall within the expected range." I managed to make it work by resetting 'image' and 'dialog' objects and also setting an explicit transfer format ID. Also your code wrote images to the same file. Here is what worked for me if applied to your code.

我的扫描仪是EPSON DS-560,系统是Windows 10 64-bit,但是每每执行这些项目到这行就一定会报错。

WIA.ImageFile image = (WIA.ImageFile)wiaCommonDialog.ShowTransfer(item, wiaFormatBMP, false);

ArgumentException,值不在指定的范围内。问题是什么值在什么范围内呢?查了WIA的Log,未果。

经过多种测试,

WIA.Item item = device.Items[1] as WIA.Item;

通过这种手段,来隐式取得扫描仪设备,一定会报错。正确的手段是:

WIA.CommonDialog dialog = new WIA.CommonDialog();

WIA.Items items = dialog.ShowSelectItems(device);

if (items == null)

{

    return;

}

WIA.Item item = items[1];

嗯,在这里我们必须弹一个窗,不弹窗到上面那里就会报错。


我们想调整一下扫描属性,怎么做呢?

翻MSDN,https://msdn.microsoft.com/en-us/library/windows/desktop/ms630194(v=vs.85).aspx,扫描仪甚至还可以纠偏:https://msdn.microsoft.com/en-us/library/windows/desktop/ms630196(v=vs.85).aspx


这些属性都在IWIAItem内实现,我们上面的item就是它的实现。很好,我们试试:

参照https://www.leadtools.com/help/leadtools/v19/dh/wa/leadtools.wia~leadtools.wia.wiapropertyid.html,找到每个Property对应的ID和值。

纠偏,是WIA_IPS_AUTO_DESKEW,对应0x00000C23,即3107.

item.Properties[3107L].set_Value(0L); // WIA_IPS_AUTO_DESKEW

OK,失败。就算是"3107"之类也是失败。我们只要把item.Properties遍历出来,就发现里面缺少了大量值。就算你是Windows 10,这里标注“This property is supported only by Windows Vista and later.”的基本也都不能用。估计是COM组件的实现问题,还是得用C++才能弄。所以,就不要想着扫描自动纠偏了。我们能做的,也就改改DPI改改扫描图片大小而已。


继续坑。

WIA.ImageFile image = (WIA.ImageFile)wiaCommonDialog.ShowTransfer(item, wiaFormatBMP, false);

回到这行代码来。这个函数的第二个参数是formatId,看起来是不是很美妙?

然而它有的时候并不理这个参数。为啥?不知道。

怎么解决?

item.Properties["4106"].set_Value(wiaFormatJPEG); // 4106 = WIA_IPA_FORMAT

为啥?不知道,随他便了。


然后上面那一堆代码,都有这么一段代码拿来检测是不是还有剩下的页数。

                    Property documentHandlingSelect = null;

                    Property documentHandlingStatus = null;

                    foreach (Property prop in device.Properties)

                    {

                        if (prop.PropertyID == WIA_PROPERTIES.WIA_DPS_DOCUMENT_HANDLING_SELECT)

                            documentHandlingSelect = prop;

                        if (prop.PropertyID == WIA_PROPERTIES.WIA_DPS_DOCUMENT_HANDLING_STATUS)

                            documentHandlingStatus = prop;

                    }

                    hasMorePages = false;

                    if (documentHandlingSelect != null)

                    {

                        if ((Convert.ToUInt32(documentHandlingSelect.get_Value()) & WIA_DPS_DOCUMENT_HANDLING_SELECT.FEEDER) != 0)

                        {

                            hasMorePages = ((Convert.ToUInt32(documentHandlingStatus.get_Value()) & WIA_DPS_DOCUMENT_HANDLING_STATUS.FEED_READY) != 0);

                        }

                    }


然而,在有的打印机上,这并没有什么卵用。

我们应该干的事,是

                try

                {

                    ICommonDialog wiaCommonDialog = new CommonDialog();

                    ImageFile image = (ImageFile)wiaCommonDialog.ShowTransfer(item, wiaFormatJPEG, false);

                    image.SaveFile(fileName);                    

                    try

                    {

                        Marshal.FinalReleaseComObject(image);

                    }

                    finally

                    {

                        image = null;

                    }

                }

                catch (COMException e)

                {

                    if (e.ErrorCode == -2145320957) // 0x80210003

                    {

                        OnEnd();

                    }

                    else

                    {

                        OnError(WIAError.ParseError(e.ErrorCode));

                    }

                    hasMorePages = false;

                    break;

                }

嗯,用0x80210003错误码来检测是不是打印完成。上面错误提示明明白白写了,扫描仪缺纸就是0x80210003。

为什么网上流传的都会失败?我怎么知道。



为什么WIA这么蠢?算了,其实考虑到WMI,这个还是可以接受。

如果本文对你有帮助,你可以用支付宝支持一下:

Alipay QrCode
luffer at 2020/3/12[回复]
WIA.Items items = dialog.ShowSelectItems(device);
Item这么改确实不报错了,可是我扫100张啊,还有时候更多,每扫一次就要点一下,这还叫高速扫描仪?
算了,我还是用组件来实现吧,这WIA放弃了
at 2018/11/12[回复]
一些功能是否支持是否与扫描仪的驱动有关?字数字数字数
zsx at 2018/11/13[回复]
我猜测很大可能是这样的,但是问题原因确实不是很好查。