babalae/better-genshin-impact · critical · InvalidOperationException

同一个 BvFlow 不能并发执行

Error message

同一个 BvFlow 不能并发执行

What it means

Thrown when the MsRdpClient10 ActiveX control's SecuredSettings2 property returns null via late-binding reflection. SecuredSettings2 is the IMsRdpClientSecuredSettings interface that exposes keyboard-hook mode and audio redirection settings. A null return means the ActiveX object exists but has not reached the state where secured settings are exposed — typically because the control has not been fully created or the RDP client version on this Windows installation does not support this interface.

Solutions

  1. Verify Windows 10 or later with RDP 8+ client installed (run mstsc /version).
  2. Ensure ConnectToChildSession is called only after the WindowsFormsHost/AxHost control is loaded and visible (not in a collapsed/collapsed parent).
  3. Register or repair the RDP ActiveX COM component (regsvr32 mstscax.dll).
  4. Fall back to reading 'SecuredSettings' (older interface) if 'SecuredSettings2' is null, logging a diagnostic.

Example fix

// before
var securedSettings = GetComProperty(client, "SecuredSettings2")
    ?? throw new COMException("RDP ActiveX 未返回 SecuredSettings2。");

// after — fall back to legacy SecuredSettings
var securedSettings = GetComProperty(client, "SecuredSettings2")
    ?? GetComProperty(client, "SecuredSettings");
if (securedSettings is null)
    throw new COMException("RDP ActiveX 未返回 SecuredSettings2,且旧版 SecuredSettings 也不可用。");
Defensive patterns

Strategy: try-catch

Validate before calling

// Check interface availability before calling ConnectToChildSession
var ocx = GetOcx();
if (ocx is null) return; // control not ready
var hasSecuredSettings2 = ocx.GetType().InvokeMember(
    "SecuredSettings2", BindingFlags.GetProperty, null, ocx, null) is not null;

Type guard

static bool SupportsSecuredSettings2(object client) =>
    GetComProperty(client, "SecuredSettings2") is not null;

Try / catch

try
{
    var securedSettings = GetComProperty(client, "SecuredSettings2")
        ?? GetComProperty(client, "SecuredSettings");
}
catch (COMException ex) when (ex.ErrorCode == unchecked((int)0x80004005))
{
    // log and abort child-session connect with diagnostic
}

Prevention

When it happens

Trigger: Called in ConnectToChildSession right after setting basic connection properties (Server, DesktopWidth, etc.) via GetComProperty(client, "SecuredSettings2"). Returns null when the AxHost has a window handle but the underlying COM object hasn't finished initializing its secured-settings interface, or on a Windows SKU where MsRdpClient10 is not registered.

Common situations: Windows version older than 10 (no MsRdpClient10 registered), RDP client components disabled or uninstalled, ActiveX initialization race condition where ConnectToChildSession is called too early, or the CLSID A0C63C30-F08D-4AB4-907C-34905D770C7D resolved to an older MsTscAx that lacks SecuredSettings2.

Related errors


AI-assisted analysis of babalae/better-genshin-impact@a7cb36712d (2026-08-13). Data as JSON: /api/errors/2094be802be1ce17. Report an issue: GitHub.

Appendix: source

Thrown at BetterGenshinImpact/Core/BgiVision/BvFlow.cs:218

        return AddWaitStep(ParseTargets(targets, nameof(targets)), BvFlowCondition.AllDisappear,
            timeout, retryInterval);
    }

    public BvFlow Wait(int milliseconds)
    {
        if (milliseconds < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(milliseconds), "milliseconds 不能小于 0");
        }

        return AddStep($"Wait({milliseconds})", _ => _services.Delay(milliseconds));
    }

    public async Task<BvPage> Run()
    {
        if (Interlocked.CompareExchange(ref _isRunning, 1, 0) != 0)
        {
            throw new InvalidOperationException("同一个 BvFlow 不能并发执行");
        }

        try
        {
            BvFlowStep[] steps;
            lock (_syncRoot)
            {
                _hasStarted = true;
                steps = _steps.ToArray();
            }

            var context = new BvFlowExecutionContext();
            for (var i = 0; i < steps.Length; i++)
            {
                var step = steps[i];
                try
                {
                    _services.ThrowIfCancellationRequested();

View on GitHub (pinned to a7cb36712d)