Windows 10 使用已注册的动词调用 ShellExecute 时出错

Windows 10 gives error when calling ShellExecute with verb that is registered

我们 运行 遇到了一个奇怪的问题,看来我们不是唯一的问题(见底部注释)。

问题是我们想用 shell 动词 printto 调用 ShellExecute。我们正在检查它是否已注册,如果已注册,则启动一个流程。可以在 ProcessStartInfo.Verbs.

中检索已注册的动词
ProcessStartInfo startInfo = new ProcessStartInfo();

startInfo.FileName = @"C:\test.jpg";
startInfo.Verb = "printto";
startInfo.Arguments = "MyPrinter";
startInfo.UseShellExecute = true;

if (!startInfo.Verbs.Contains("printto"))
    throw new Exception("PrintTo is not supported!");

try
{
    Process.Start(startInfo);
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 1155)
{
    Console.WriteLine("Somehow printto is NOT registered...");
}

当 运行ning Windows 10 将照片 UWP 应用程序作为默认查看器时,控制台将打印出代码为 1155 的 Win32Exception,这意味着文件类型未注册 (对于给定的动词)。如果(旧的)windows 图片查看器是默认的,这有效。

另请注意,我们正在检查动词是否已注册,只有在已注册时才调用此方法。微软似乎在这里做了一些不同的事情。

最大的问题是:为什么这两个 MS API 不再一起玩,我们如何避免这种情况?

注: 有一个旧的讨论,答案不是特别正确,但问题描述也略有不同: Windows 8 blows error on c# process for printing pdf file, how?

因此我决定提出一个新问题,希望它符合 SO 原则。

ProcessStartInfo.Verbs 属性 有些问题,因为它没有考虑更新版本的 Windows(Windows 8 及更高版本 afaik)检索已注册应用程序的方式. 属性 只检查在 HKCR\.ext 下定义的 ProgId 注册的动词(在 reference source 中可以看到)并且不考虑其他地方,例如在注册表键 HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ext 或其他一些地方,例如通过策略定义。

获取注册动词

最好的方法是不要依赖直接检查注册表(如 ProcessStartInfo class 所做的那样),而是使用适当的 Windows API 功能AssocQueryString 检索关联的 ProgId:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32;

class Program
{
    private static void Main(string[] args)
    {
        string fileName = @"E:\Pictures\Sample.jpg";
        string progId = AssocQueryString(AssocStr.ASSOCSTR_PROGID, fileName);

        var verbs = GetVerbsByProgId(progId);

        if (!verbs.Contains("printto"))
        {
            throw new Exception("PrintTo is not supported!");
        }

    }

    private static string[] GetVerbsByProgId(string progId)
    {
        var verbs = new List<string>();

        if (!string.IsNullOrEmpty(progId))
        {
            using (var key = Registry.ClassesRoot.OpenSubKey(progId + "\shell"))
            {
                if (key != null)
                {
                    var names = key.GetSubKeyNames();
                    verbs.AddRange(
                        names.Where(
                            name => 
                                string.Compare(
                                    name, 
                                    "new", 
                                    StringComparison.OrdinalIgnoreCase) 
                                != 0));
                }
            }
        }

        return verbs.ToArray();
    }

    private static string AssocQueryString(AssocStr association, string extension)
    {
        uint length = 0;
        uint ret = AssocQueryString(
            AssocF.ASSOCF_NONE, association, extension, "printto", null, ref length);
        if (ret != 1) //expected S_FALSE
        {
            throw new Win32Exception();
        }

        var sb = new StringBuilder((int)length);
        ret = AssocQueryString(
            AssocF.ASSOCF_NONE, association, extension, null, sb, ref length);
        if (ret != 0) //expected S_OK
        {
            throw new Win32Exception();
        }

        return sb.ToString();
    }

    [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern uint AssocQueryString(
        AssocF flags,
        AssocStr str,
        string pszAssoc,
        string pszExtra,
        [Out] StringBuilder pszOut,
        ref uint pcchOut);

    [Flags]
    private enum AssocF : uint
    {
        ASSOCF_NONE = 0x00000000,
        ASSOCF_INIT_NOREMAPCLSID = 0x00000001,
        ASSOCF_INIT_BYEXENAME = 0x00000002,
        ASSOCF_OPEN_BYEXENAME = 0x00000002,
        ASSOCF_INIT_DEFAULTTOSTAR = 0x00000004,
        ASSOCF_INIT_DEFAULTTOFOLDER = 0x00000008,
        ASSOCF_NOUSERSETTINGS = 0x00000010,
        ASSOCF_NOTRUNCATE = 0x00000020,
        ASSOCF_VERIFY = 0x00000040,
        ASSOCF_REMAPRUNDLL = 0x00000080,
        ASSOCF_NOFIXUPS = 0x00000100,
        ASSOCF_IGNOREBASECLASS = 0x00000200,
        ASSOCF_INIT_IGNOREUNKNOWN = 0x00000400,
        ASSOCF_INIT_FIXED_PROGID = 0x00000800,
        ASSOCF_IS_PROTOCOL = 0x00001000,
        ASSOCF_INIT_FOR_FILE = 0x00002000
    }

    private enum AssocStr
    {
        ASSOCSTR_COMMAND = 1,
        ASSOCSTR_EXECUTABLE,
        ASSOCSTR_FRIENDLYDOCNAME,
        ASSOCSTR_FRIENDLYAPPNAME,
        ASSOCSTR_NOOPEN,
        ASSOCSTR_SHELLNEWVALUE,
        ASSOCSTR_DDECOMMAND,
        ASSOCSTR_DDEIFEXEC,
        ASSOCSTR_DDEAPPLICATION,
        ASSOCSTR_DDETOPIC,
        ASSOCSTR_INFOTIP,
        ASSOCSTR_QUICKTIP,
        ASSOCSTR_TILEINFO,
        ASSOCSTR_CONTENTTYPE,
        ASSOCSTR_DEFAULTICON,
        ASSOCSTR_SHELLEXTENSION,
        ASSOCSTR_DROPTARGET,
        ASSOCSTR_DELEGATEEXECUTE,
        ASSOCSTR_SUPPORTED_URI_PROTOCOLS,
        ASSOCSTR_PROGID,
        ASSOCSTR_APPID,
        ASSOCSTR_APPPUBLISHER,
        ASSOCSTR_APPICONREFERENCE,
        ASSOCSTR_MAX
    }
}

实际打印图像

但是,这并不能解决您的实际问题,即在 Windows 10 上打印图像。如果您的要求只是打印图像,您可以使用 PrintDocument class 来自 System.Drawing.Printing 命名空间,如相关 post 中所述:Print images in C#:

PrintDocument pd = new PrintDocument();
pd.PrintPage += PrintPage;
pd.Print();    

private void PrintPage(object o, PrintPageEventArgs e)
{
    System.Drawing.Image img = System.Drawing.Image.FromFile("D:\Foto.jpg");
    Point loc = new Point(100, 100);
    e.Graphics.DrawImage(img, loc);     
}