CoInitializeSecurity 在 Visual Studio 2017 年抛出 RPC_E_TOO_LATE

CoInitializeSecurity throws RPC_E_TOO_LATE in Visual Studio 2017

我正在尝试 运行 应用程序在启动时调用 CoInitializeSecurity。这在 Visual Studio 2013 年有效,但在 Visual Studio 2017 年无效 - 我很好奇为什么会这样。

在 Visual Studio 2017 年启动时调用 CoInitializeSecurity 时,我得到一个 COMException,错误代码 RPC_E_TOO_LATE (0x80010119),表明已经对 CoInitialize 进行了调用,这Visual Studio 2013 年不会发生。

我在 Visual Studio 2013 年启用 Visual Studio 托管进程时或在调用 CoInitializeSecurity 之前加载使用 COM 的程序集时看到过此行为。

加载的程序集在 Visual Studio 2013 和 2017 之间有所不同(进入 App 构造函数时拍摄的快照),差异突出显示:

2013:

'WPFTestVS2017.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\WPFTestVS2017\bin\Debug\WPFTestVS2017.exe'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_64\PresentationCore\v4.0_4.0.0.0__31bf3856ad364e35\PresentationCore.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.Xaml\v4.0_4.0.0.0__b77a5c561934e089\System.Xaml.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll'.

2017:

'WPFTestVS2017.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\WPFTestVS2017\bin\Debug\WPFTestVS2017.exe'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_64\PresentationCore\v4.0_4.0.0.0__31bf3856ad364e35\PresentationCore.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.Xaml\v4.0_4.0.0.0__b77a5c561934e089\System.Xaml.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\Program Files (x86)\Microsoft Visual Studio17\Enterprise\Common7\IDE\Remote Debugger\x64\Runtime\Microsoft.VisualStudio.Debugger.Runtime.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll'.

'WPFTestVS2017.exe' (CLR v4.0.30319: WPFTestVS2017.exe): Loaded 'C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll'.

远程调试器让我产生了怀疑,因为它让我想起了 Visual Studio 托管进程。另一行不同的是 System.Core.dll,它没有出现在 VS2013 的加载程序集中。

代码:

App.xaml

<Application x:Class="WPFTestVS2017.App"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            StartupUri="MainWindow.xaml">
</Application>

App.xaml.cs

using System;
using System.Runtime.InteropServices;
using System.Windows;

namespace WPFTestVS2017
{
    internal static class NativeMethods
    {
        private enum RpcAuthnLevel
        {
            Default = 0,
            None = 1,
            Connect = 2,
            Call = 3,
            Pkt = 4,
            PktIntegrity = 5,
            PktPrivacy = 6
        }

        private enum RpcImpLevel
        {
            Default = 0,
            Anonymous = 1,
            Identify = 2,
            Impersonate = 3,
            Delegate = 4
        }

        private enum EoAuthnCap
        {
            None = 0x0000,
            MutualAuth = 0x0001,
            StaticCloaking = 0x0020,
            DynamicCloaking = 0x0040,
            AnyAuthority = 0x0080,
            MakeFullSIC = 0x0100,
            Default = 0x0800,
            SecureRefs = 0x0002,
            AccessControl = 0x0004,
            AppID = 0x0008,
            Dynamic = 0x0010,
            RequireFullSIC = 0x0200,
            AutoImpersonate = 0x0400,
            NoCustomMarshal = 0x2000,
            DisableAAA = 0x1000
        }

        [DllImport("Ole32.dll",
            ExactSpelling = true,
            EntryPoint = "CoInitializeSecurity",
            CallingConvention = CallingConvention.StdCall,
            SetLastError = false,
            PreserveSig = false)]
        private static extern void CoInitializeSecurity(
            IntPtr pVoid,
            int cAuthSvc,
            IntPtr asAuthSvc,
            IntPtr pReserved1,
            uint dwAuthnLevel,
            uint dwImpLevel,
            IntPtr pAuthList,
            uint dwCapabilities,
            IntPtr pReserved3);

        public static void Initialize()
        {
            CoInitializeSecurity(IntPtr.Zero,
                -1,
                IntPtr.Zero,
                IntPtr.Zero,
                (uint)RpcAuthnLevel.PktPrivacy,
                (uint)RpcImpLevel.Impersonate,
                IntPtr.Zero,
                (uint)EoAuthnCap.DynamicCloaking,
                IntPtr.Zero);
        }
    }

    public partial class App : Application
    {
        public App()
        {
            NativeMethods.Initialize();
        }
    }
}

MainWindow.xaml

<Window x:Class="WPFTestVS2017.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"/>

MainWindow.xaml.cs

using System.Windows;

namespace WPFTestVS2017
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

编辑:

我对App.xaml.cs做了以下修改:

public App()
{
    try
    {
        NativeMethods.Initialize();
    }
    catch (Exception e)
    {
        MessageBox.Show(e.ToString());
    }
}

消息框在 Visual Studio 2017 年调试时出现,但是,当 运行 在 Visual Studio.

之外运行相同的可执行文件时,它不会出现

您正在与 VS2017 中托管调试引擎的变化作斗争。它与您猜测的内容没有任何关系,我认为删除 Visual Studio Hosting Process 选项可能是相关的。瞎猜,这是一个黑盒子,如果没有微软调试团队的帮助,很难被攻破。

您有几种可能的解决方法,按实用性排序:

  1. 工具 > 选项 > 调试 > 常规,勾选 "Use Managed Compatibility Mode" 复选框。这将用旧的调试引擎替换新的调试引擎,最后在 VS2010 中使用。您会错过一些最新的调试器功能(新的 PDB 格式、return 值检查、64 位编辑+继续),这些功能几乎不会阻止您调试 WPF 应用程序。

  2. 如果不需要,您可以阻止函数抛出异常。将 [DllImport] 的 PreserveSig 属性 更改为 true,将 return 类型从 void 更改为 int。它仍然会失败,由负 return 值指示,但您可以继续调试其余代码。也许您想使用 return 值来设置您用来绕过棘手的 COM 代码的全局变量。

  3. 如果不需要,您可以延迟初始化调试引擎,直到 CoInitializeSecurity 调用之后。追加 System.Diagnostics.Debugger.Launch();,用 #if DEBUG 包裹。现在您可以按 Ctrl+F5 开始调试,当您得到提示时,select VS 的 运行 实例作为所需的调试器。使用“调试”>“附加到进程”是类似的解决方法。

我终于设法找到了这个问题的解决方案 - 这个问题似乎起源于 STAThread

App.xaml 的属性中将构建操作从 ApplicationDefinition 切换到 Page 允许我们定义自己的 Main 方法,而不是使用 compiler-generated App.g.cs.

中的一个

仍然使用问题中的NativeMethods class作为参考:

internal static class NativeMethods
{
    private enum RpcAuthnLevel
    {
         Default = 0,
         None = 1,
         Connect = 2,
         Call = 3,
         Pkt = 4,
         PktIntegrity = 5,
         PktPrivacy = 6
    }

    private enum RpcImpLevel
    {
         Default = 0,
         Anonymous = 1,
         Identify = 2,
         Impersonate = 3,
         Delegate = 4
     }

     private enum EoAuthnCap
     {
         None = 0x0000,
         MutualAuth = 0x0001,
         StaticCloaking = 0x0020,
         DynamicCloaking = 0x0040,
         AnyAuthority = 0x0080,
         MakeFullSIC = 0x0100,
         Default = 0x0800,
         SecureRefs = 0x0002,
         AccessControl = 0x0004,
         AppID = 0x0008,
         Dynamic = 0x0010,
         RequireFullSIC = 0x0200,
         AutoImpersonate = 0x0400,
         NoCustomMarshal = 0x2000,
         DisableAAA = 0x1000
     }

     [DllImport("Ole32.dll",
         ExactSpelling = true,
         EntryPoint = "CoInitializeSecurity",
         CallingConvention = CallingConvention.StdCall,
         SetLastError = false,
         PreserveSig = false)]
     private static extern void CoInitializeSecurity(
         IntPtr pVoid,
         int cAuthSvc,
         IntPtr asAuthSvc,
         IntPtr pReserved1,
         uint dwAuthnLevel,
         uint dwImpLevel,
         IntPtr pAuthList,
         uint dwCapabilities,
         IntPtr pReserved3);

    public static void Initialize()
    {
        CoInitializeSecurity(IntPtr.Zero,
            -1,
            IntPtr.Zero,
            IntPtr.Zero,
            (uint)RpcAuthnLevel.PktPrivacy,
            (uint)RpcImpLevel.Impersonate,
            IntPtr.Zero,
            (uint)EoAuthnCap.DynamicCloaking,
            IntPtr.Zero);
    }
}

新的 Main 方法需要首先调用 CoInitializeSecurity,如下所示:

internal static void Main()
{
    NativeMethods.Initialize();
}

这里缺少一些东西,比如之前 运行 在 compiler-generated Main 中的逻辑。当我们调用 CoInitializeSecurity 时,我们现在需要 运行 具有 STA 线程模型的线程中的 App 构造函数,如下所示:

[DebuggerNonUserCode]
internal static void STAMain()
{
    //This is what the compiler-generated Main method executes by default
    App app = new App();
    app.InitializeComponent();
    app.Run();
}

//Marking this as [STAThread] will cause RPC_E_TOO_LATE
internal static void Main()
{
    //This call won't throw an RPC_E_TOO_LATE COMException anymore
    NativeMethods.Initialize();
    
    /*
      We will have to create a GUI thread manually here 
      since the COM threading model isn't STA for this thread
    */
    Thread guiThread = new Thread(STAMain);
    guiThread.SetApartmentState(ApartmentState.STA);
    guiThread.Start();
}

免责声明:我不完全确定这个解决方案有多明智或明智,但它似乎允许应用程序正常工作,并且没有托管兼容模式。