You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不依赖System.Windows.Forms在控制台/WinUI3中使用OCX遇E_UNEXPECTED错误求助

问题分析与解决方案

错误原因

  • 缺少消息循环:OCX作为ActiveX控件依赖Windows消息循环处理内部事件,即便使用STA线程,控制台线程默认没有启动消息循环,导致COM对象无法完成初始化或处理方法调用。
  • 无窗口宿主:多数OCX控件需要绑定到一个窗口句柄作为宿主容器,控制台环境没有提供合适的窗口上下文,调用CommConnect时控件无法完成初始化流程。

可行解决方案

1. 为STA线程添加消息循环

修改STA线程代码,手动启动Windows消息循环,确保OCX控件能处理内部消息:

internal class Program
{
    static void Main()
    {
        var thread = new Thread(Foo);
        thread.IsBackground = true;
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

        Console.ReadKey();
    }

    static void Foo()
    {
        try
        {
            var guid1 = new Guid("A1574A0D-6BFA-4BD7-9020-DED88711818D");
            var type = Type.GetTypeFromCLSID(guid1);
            var a = Activator.CreateInstance(type);

            var b = a as KHOpenAPILib.KHOpenAPI;
            b.CommConnect();

            // 启动消息循环,保持线程存活并处理OCX消息
            Application.Run();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}

注意:需要引用System.Windows.Forms程序集,若控制台项目无法引用,可改为WinForms项目隐藏窗口运行。

2. 用Win32窗口作为OCX宿主

创建一个隐藏的Win32窗口,将OCX控件嵌入其中,提供必要的窗口上下文:

internal class Program
{
    [DllImport("user32.dll")]
    private static extern IntPtr CreateWindowEx(int dwExStyle, string lpClassName, string lpWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);

    [DllImport("user32.dll")]
    private static extern bool DestroyWindow(IntPtr hWnd);

    [DllImport("ole32.dll")]
    private static extern int OleCreate(IntPtr pCLSID, ref Guid riid, int dwRenderOpt, IntPtr pFormatEtc, IntPtr pClientSite, IntPtr pStorage, out IntPtr ppvObj);

    private const int WS_CHILD = 0x40000000;
    private static Guid IID_IOleObject = new Guid("00000112-0000-0000-C000-000000000046");

    static void Main()
    {
        var thread = new Thread(Foo);
        thread.IsBackground = true;
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

        Console.ReadKey();
    }

    static void Foo()
    {
        // 注册窗口类(简化示例,实际需处理窗口过程)
        IntPtr hInstance = System.Diagnostics.Process.GetCurrentProcess().Handle;
        IntPtr hWnd = CreateWindowEx(0, "STATIC", "", WS_CHILD, 0, 0, 100, 100, IntPtr.Zero, IntPtr.Zero, hInstance, IntPtr.Zero);

        if (hWnd != IntPtr.Zero)
        {
            try
            {
                Guid clsid = new Guid("A1574A0D-6BFA-4BD7-9020-DED88711818D");
                IntPtr pOleObj;
                int result = OleCreate(IntPtr.Zero, ref IID_IOleObject, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out pOleObj);

                if (result == 0)
                {
                    var oleObject = Marshal.GetObjectForIUnknown(pOleObj) as IOleObject;
                    oleObject.SetClientSite(null); // 需实现IOleClientSite接口,此处简化
                    oleObject.DoVerb(0, null, null, 0, hWnd, new Rectangle(0, 0, 100, 100));

                    var khApi = oleObject as KHOpenAPILib.KHOpenAPI;
                    khApi.CommConnect();

                    // 启动消息循环
                    MSG msg;
                    while (GetMessage(out msg, IntPtr.Zero, 0, 0))
                    {
                        TranslateMessage(ref msg);
                        DispatchMessage(ref msg);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
            finally
            {
                DestroyWindow(hWnd);
            }
        }
    }

    // 定义必要的Win32结构体和接口
    [StructLayout(LayoutKind.Sequential)]
    private struct MSG
    {
        public IntPtr hwnd;
        public uint message;
        public IntPtr wParam;
        public IntPtr lParam;
        public uint time;
        public POINT pt;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT
    {
        public int x;
        public int y;
    }

    [DllImport("user32.dll")]
    private static extern bool GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);

    [DllImport("user32.dll")]
    private static extern bool TranslateMessage(ref MSG lpMsg);

    [DllImport("user32.dll")]
    private static extern IntPtr DispatchMessage(ref MSG lpMsg);

    [ComImport, Guid("00000112-0000-0000-C000-000000000046")]
    private interface IOleObject
    {
        void SetClientSite([MarshalAs(UnmanagedType.Interface)] object pClientSite);
        void DoVerb(int iVerb, IntPtr lpmsg, [MarshalAs(UnmanagedType.Interface)] object pActiveSite, int lindex, IntPtr hwndParent, [In] Rectangle lprcPosRect);
        // 其他接口方法可省略,仅实现必要部分
    }
}

此方案需要实现完整的IOleClientSite接口来处理OCX的宿主交互,适合对Win32 API熟悉的场景。

3. WinUI3与WinForms进程通信

由于WinUI3无法直接引用System.Windows.Forms,可以创建一个独立的WinForms控制台/窗口程序作为OCX的宿主,通过命名管道、WCF或共享内存与WinUI3应用通信,间接调用OCX的功能。

4. 检查OCX文档与注册状态

  • 确认OCX已通过regsvr32.exe正确注册(管理员权限运行)。
  • 查阅KHOpenAPI的官方文档,确认CommConnect方法是否需要前置初始化参数,或是否仅支持UI环境调用。

内容的提问来源于stack exchange,提问作者appletree5641

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 10:05:29