我可以 运行 WPF Windows 在没有 Forms Interop Toolkit 的情况下由 VB6 调用吗?

Can I Run WPF Windows called by VB6 Without Forms Interop Toolkit?

我们有一个要迁移到 .NET 的 VB6 应用程序。我从打开 WPF window 的 VB6 调用 .NET 函数。我们经常需要这些 windows 在主线程上 运行ning 以便它的行为就像另一个 VB6 窗体一样。我们还需要 运行 来自这些新 windows 的异步操作,因此需要 Dispatcher 和 SynchronizationContext。当我们第一次创建调用我们的 WPF windows.

的服务时,我们只需启动一个新的 Dispatcher 和 DispatcherSynchronizationContext

有一次,我打开了一个 WPF window,但它没有绘制边框。 使用上述方法是否存在我不知道的线程问题?这看起来是解决此问题的正确方法吗?我担心这种方法存在竞争条件或隐藏的问题,因为我们自己管理调度程序,这与典型的 .net 应用程序不同。

我知道有 Interop Forms Toolkit,但我们不需要 vb6 代码来直接与表单交互,所以这看起来有点过分和麻烦。我们才刚刚开始编写这些 windows 和这个接口,所以最好尽快知道我的方法是否错误。

您必须为 WPF windows 公开 .tlb 和 .idl 文件提供 COM 接口(ActiveX 用户控件)。

为此,我通常创建一个单独的项目,它只关心这个接口并引用您的 WPF 项目。在那里添加一个class或几个可以被VB6程序实例化的,根据需要提供所有的GUID和接口描述。

对于这个项目,要么在项目构建属性中使用 'Register for COM Interop' 选项,要么(我的偏好)在项目的构建事件脚本中使用 regasm 来创建 . tlb 和 .idl.

您部署:您的 WPF DLL、.tlb 和 .idl。 然后从您的 VB6 项目中引用此类型库,根据需要创建和使用对象。

我建议您在 WPF 和 COM 接口代码中管理所有同步,因为带有 COM 的 VB6 往往是 STA。

希望这对您有所帮助:)

另一种选择是将 WPF windows 构建为单独的进程,然后从 VB6 中调用它们。 这是关于如何执行此操作的线程: What is the VB 6 equivalent of Process.Start?

同样,特别是如果您不需要来自 VB6 的任何交互,您应该在 WPF exe 中管理所有线程。

到目前为止,一切对我来说都很好。 COM 可见方法和事件的基本示例:

// COM visible interfaces
[ComVisible(true)]
[Guid("..."), InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IWpfCtl
{
    void Show();

    void Close();

    void ComMethod(string key);
}

[ComVisible(true)]
[Guid("..."), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IWpfCtlEvents
{
    void OnTestButton(string message);
}



// Implementation
public delegate void TestButtonDelegate(string message);

[ComVisible(true)]
[Guid("..."), ComSourceInterfaces(typeof(IWpfCtlEvents)), ClassInterface(ClassInterfaceType.None)]
public class WpfCtl : IWpfCtl, IDisposable
{
    Thread _windowThread;
    WpfWindow _wpfWindow;

    public event TestButtonDelegate OnTestButton;

    // Default parameterless constructor
    public WpfCtl()
    {
    }

    // Instantiate and show a WPF Window
    public void Show()
    {
        // create a thread  
        _windowThread = new Thread(new ThreadStart(() =>
        {
            // create synchronization context
            SynchronizationContext.SetSynchronizationContext(
                new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));

            // instantiate the window
            _wpfWindow = new WpfWindow();

            // shut down the dispatcher on window close
            _wpfWindow.Closed += (s, e) =>
                    Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);

            // bind to window events
            _wpfWindow.testButton.Click += (sender, args) => OnTestButtonClick("Test button was clicked");

            // show the window
            _wpfWindow.Show();

            // start the dispatcher processing  
            System.Windows.Threading.Dispatcher.Run();
        }));

        // set the apartment state  
        _windowThread.SetApartmentState(ApartmentState.STA);

        // make the thread a background thread  
        _windowThread.IsBackground = true;

        // start the thread  
        _windowThread.Start();
    }

    public void Dispose()
    {
        Close();
    }

    // Close window
    public void Close()
    {
        if (_windowThread != null && _windowThread.IsAlive && _wpfWindow != null)
        {
            if (_wpfWindow.Dispatcher.CheckAccess())
                _wpfWindow.Close();
            else
            {
                _wpfWindow.Dispatcher.Invoke(() => { _wpfWindow.Close(); });
            }
        }
    }

    // COM visible method
    public void ComMethod(string key)
    {
        if (_wpfWindow != null)
            _wpfWindow.Dispatcher.Invoke(() => { _wpfWindow.Method(key); });
    }

    // COM event
    private void OnTestButtonClick(string message)
    {
        if (OnTestButton != null)
        {
            OnTestButton.Invoke(message);
        }
    }
}