14

我有一个本机(非托管)C++ 应用程序(使用 wxWidgets 来获得它的价值)。我正在考虑用 C# 编写一个单独的工具应用程序,其中包含基于 winform 的对话框。将其中一些对话框放在单独的 DLL 中会很有用,因为我希望能够从我的 C++ 应用程序中使用它们。

但是我不知道要做到这一点需要多少混乱,是不是特别容易?

编辑

我不需要直接调用对话框函数。我只需要一种方法让我的 C++ 应用程序调用 C# DLL 中的 API 以传递数据,以及让 C# DLL 调用 C++ 应用程序中某种观察者/返回对象的方法。

例如来自 C++:

CSharpManager *pCSM = SomehowGetPointerToCSharpObject();
CSharpObserver pCSO = new CSharpObserver;

pCSM->RegisterCPlusPlusObserver(pCSO);
pCSM->LaunchDialog();

当用户在 C# 对话框中执行操作时,调用 pCSO 方法将数据传回 C++

所以我认为这几乎是一个原始的 C++/C# 通信问题。但是虽然我知道 C++ 和 C#,但我不知道 .net 本身是如何工作的。我知道 COM,但真的宁愿避免它,我怀疑与我一起工作的任何其他开发人员都知道它。

4

8 回答 8

7

非托管代码中互操作的通用语是 COM。这在 C# 方面很容易上手,只需使用[ComVisible]属性。您需要在您的 C++ 程序中编写 COM 代码才能使用它,如果您从未这样做过,那么上手并不容易。如果您使用 MSVC 编译器,请从#import 指令开始。

您的下一个选择是自己在非托管代码中托管 CLR,而不是依靠 COM 互操作层来处理它。它允许您直接创建托管类型。这也需要 COM,但只是为了加载和初始化 CLR。 这个项目展示了这种方法。

于 2010-03-20T15:27:42.697 回答
2

要么使用 COM,要么编写调用 C# 对话框的 C++/CLI 包装器,然后从非托管 C++ 代码调用此 C++/CLI 包装器。

于 2010-03-28T15:29:20.913 回答
1

我知道这里有一些答案,但没有一个指向一个有效的例子。当我遇到这个问题时,我能够通过这个例子弄清楚。

http://support.microsoft.com/kb/828736

于 2010-03-30T13:11:57.017 回答
1

这取决于您所说的“我希望能够从我的 C++ 应用程序中使用它们”的意思。

在原生世界中,对话框具有对话框模板结构,您可以将其“烹制”到可执行文件中,无论是 DLL 还是 EXE。美好的。但在托管世界中,情况有些不同。Winforms 应用程序没有“对话框模板”资源类型。相反,表单只是代码。

然而:

  • 您始终可以从非托管代码调用托管 DLL。这是微不足道的。该托管 DLL 可以显示您的 Winforms 对话框。因此,您的应用程序的本机 C++ 部分可以调用这些对话框。但是如果没有一些额外的工作,它就不能直接实例化它们。

  • 您始终可以在本机 C++ 代码和托管 DLL 之间插入 C++/CLI“shim DLL”。在 C++/CLI 中,您可以透明地加载托管和 .NET 资源/对话框。

  • 就此而言,您可以直接从本机代码调用 .NET 方法,而无需中间的 C++/CLI shim DLL,尽管它有点混乱。

但至于直接使用“.NET/Winforms 对话框资源”……不。不是在为 Winforms 和本机 C++ 使用相同的对话框模板的意义上。

于 2010-03-20T13:25:18.750 回答
0

如果要在 C++ 应用程序中加载任何 .NET DLL,则必须在 C++ 应用程序中托管 .NET。

您可以在此处找到 Microsoft 的示例: https ://code.msdn.microsoft.com/CppHostCLR-e6581ee0 该示例还包括一些必需的头文件。

简而言之,您需要执行以下操作:

  1. 使用 LoadLibrary 命令加载 mscoree.dll(否则您可以将 mscoree.dll 静态链接到您的项目中)
  2. 调用 mscoree.dll 导出的 CLRCreateInstance 函数,创建 ICLRMetaHost 对象
  3. 调用 ICLRMetaHost 对象的 GetRuntime 方法,以获取您首选 .NET 版本的 ICLRRuntimeInfo 对象。
  4. 检查版本是否可加载调用 ICLRRuntimeInfo.IsLoadable
  5. 从 ICLRRuntimeInfo 调用 GetInterface 方法以获取 ICorRuntimeHost
  6. 调用 ICorRuntimeHost 对象的 Start 方法
  7. 从 ICorRuntimeHost 对象调用 GetDefaultDomain 方法以获取 IAppDomain 对象

然后您可以使用 IAppDomain.Load_2 加载库。如果您想从网络共享加载 .NET DLL,则更加复杂,因为您需要调用 UnsafeLoadFrom,这在 IAppDomain 中不可用。但这也是可能的。

于 2014-11-28T10:02:33.897 回答
0

在从 C++ 调用的 C# DLL 中使用表单并不容易,但是一旦编写了一些实用程序代码,它就会相当健壮。对 C++ 代码的回调非常容易。

要执行 Forms(或 WPF),NativeWindow类是您的朋友。您需要比 NativeWindow 提供的更多功能,因此需要派生。下面的代码显示了一个派生自 NativeWindow 并提供BeginInvoke()调用和 Windows 消息事件处理程序的实现。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

/// <summary>
/// A <see cref="NativeWindow"/> for the main application window. Used
/// to be able to run things on the UI thread and manage window message
/// callbacks.
/// </summary>
public class NativeWindowWithCallbacks : NativeWindow, IDisposable
{
    /// <summary>
    /// Used to synchronize access to <see cref="NativeWindow.Handle"/>.
    /// </summary>
    private readonly object handleLock = new object();

    /// <summary>
    /// Queue of methods to run on the UI thread.
    /// </summary>
    private readonly Queue<MethodArgs> queue = new Queue<MethodArgs>();

    /// <summary>
    /// The message handlers.
    /// </summary>
    private readonly Dictionary<int, MessageHandler> messageHandlers = 
        new Dictionary<int, MessageHandler>();

    /// <summary>
    /// Windows message number to prompt running methods on the UI thread.
    /// </summary>
    private readonly int runOnUiThreadWindowsMessageNumber =
        Win32.RegisterWindowMessage(
                "NativeWindowWithCallbacksInvokeOnGuiThread");

    /// <summary>
    /// Handles the message.
    /// </summary>
    /// <param name="sender">
    /// The this.
    /// </param>
    /// <param name="m">
    /// The message.
    /// </param>
    /// <returns>
    /// True if done processing; false otherwise. Normally, returning
    /// true will stop other handlers from being called, but, for
    /// some messages (like WM_DESTROY), the return value has no effect.
    /// </returns>
    public delegate bool MessageHandler(object sender, ref Message m);

    /// <summary>
    /// Gets a value indicating whether the caller must call BeginInvoke
    /// when making UI calls (like <see cref="Control.InvokeRequired"/>).
    /// </summary>
    /// <returns>
    /// True if not running on the UI thread.
    /// </returns>
    /// <remarks>
    /// This can get called prior to detecting the main window (likely if 
    /// the main window has yet to be created). In this case, this method
    /// will return true even if the main window subsequently gets
    /// created on the current thread. This behavior works for queuing up
    /// methods that will update the main window which is likely the only 
    /// reason for invoking methods on the UI thread anyway.
    /// </remarks>
    public bool InvokeRequired
    {
        get
        {
            int pid;
            return this.Handle != IntPtr.Zero
                && Win32.GetWindowThreadProcessId(
                        new HandleRef(this, this.Handle), out pid)
                != Win32.GetCurrentThreadId();
        }
    }

    /// <summary>
    /// Like <see cref="Control.BeginInvoke(Delegate,Object[])"/> but
    /// probably not as good.
    /// </summary>
    /// <param name="method">
    /// The method.
    /// </param>
    /// <param name="args">
    /// The arguments.
    /// </param>
    /// <remarks>
    /// This can get called prior to finding the main window (likely if 
    /// the main window has yet to be created). In this case, the method 
    /// will get queued and called upon detection of the main window.
    /// </remarks>
    public void BeginInvoke(Delegate method, params object[] args)
    {
        // TODO: ExecutionContext ec = ExecutionContext.Capture();
        // TODO: then ExecutionContext.Run(ec, ...) 
        // TODO: in WndProc for more accurate security
        lock (this.queue)
        {
            this.queue.Enqueue(
                new MethodArgs { Method = method, Args = args });
        }

        if (this.Handle != IntPtr.Zero)
        {
            Win32.PostMessage(
                    new HandleRef(this, this.Handle),
                    this.runOnUiThreadWindowsMessageNumber,
                    IntPtr.Zero,
                    IntPtr.Zero);
        }
    }

    /// <summary>
    /// Returns the handle of the main window menu.
    /// </summary>
    /// <returns>
    /// The handle of the main window menu; Handle <see cref="IntPtr.Zero"/>
    /// on failure.
    /// </returns>
    public HandleRef MenuHandle()
    {
        return new HandleRef(
                this,
                this.Handle != IntPtr.Zero
                    ? Win32.GetMenu(new HandleRef(this, this.Handle))
                    : IntPtr.Zero);
    }

    /// <summary>
    /// When the instance gets disposed.
    /// </summary>
    public void Dispose()
    {
        this.ReleaseHandle();
    }

    /// <summary>
    /// Sets the handle.
    /// </summary>
    /// <param name="handle">
    ///   The handle.
    /// </param>
    /// <param name="onlyIfNotSet">
    /// If true, will not assign to an already assigned handle.
    /// </param>
    public void AssignHandle(IntPtr handle, bool onlyIfNotSet)
    {
        bool emptyBacklog = false;
        lock (this.handleLock)
        {
            if (this.Handle != handle
                    && (!onlyIfNotSet || this.Handle != IntPtr.Zero))
            {
                base.AssignHandle(handle);
                emptyBacklog = true;
            }
        }

        if (emptyBacklog)
        {
            this.EmptyUiBacklog();
        }
    }

    /// <summary>
    /// Adds a message handler for the given message number.
    /// </summary>
    /// <param name="messageNumber">
    /// The message number.
    /// </param>
    /// <param name="messageHandler">
    /// The message handler.
    /// </param>
    public void AddMessageHandler(
        int messageNumber,
        MessageHandler messageHandler)
    {
        lock (this.messageHandlers)
        {
            if (this.messageHandlers.ContainsKey(messageNumber))
            {
                this.messageHandlers[messageNumber] += messageHandler;
            }
            else
            {
                this.messageHandlers.Add(
                        messageNumber, (MessageHandler)messageHandler.Clone());
            }
        }
    }

    /// <summary>
    /// Processes the window messages.
    /// </summary>
    /// <param name="m">
    /// The m.
    /// </param>
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == this.runOnUiThreadWindowsMessageNumber && m.Msg != 0)
        {
            for (;;)
            {
                MethodArgs ma;
                lock (this.queue)
                {
                    if (!this.queue.Any())
                    {
                        break;
                    }

                    ma = this.queue.Dequeue();
                }

                ma.Method.DynamicInvoke(ma.Args);
            }

            return;
        }

        int messageNumber = m.Msg;
        MessageHandler mh;
        if (this.messageHandlers.TryGetValue(messageNumber, out mh))
        {
            if (mh != null)
            {
                foreach (MessageHandler cb in mh.GetInvocationList())
                {
                    try
                    {
                        // if WM_DESTROY (messageNumber == 2),
                        // ignore return value
                        if (cb(this, ref m) && messageNumber != 2)
                        {
                            return; // done processing
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(string.Format("{0}", ex));
                    }
                }
            }
        }

        base.WndProc(ref m);
    }

    /// <summary>
    /// Empty any existing backlog of things to run on the user interface
    /// thread.
    /// </summary>
    private void EmptyUiBacklog()
    {
        // Check to see if there is a backlog of
        // methods to run on the UI thread. If there
        // is than notify the UI thread about them.
        bool haveBacklog;
        lock (this.queue)
        {
            haveBacklog = this.queue.Any();
        }

        if (haveBacklog)
        {
            Win32.PostMessage(
                    new HandleRef(this, this.Handle),
                    this.runOnUiThreadWindowsMessageNumber,
                    IntPtr.Zero,
                    IntPtr.Zero);
        }
    }

    /// <summary>
    /// Holds a method and its arguments.
    /// </summary>
    private class MethodArgs
    {
        /// <summary>
        /// Gets or sets the method arguments.
        /// </summary>
        public object[] Args { get; set; }

        /// <summary>
        /// Gets or sets Method.
        /// </summary>
        public Delegate Method { get; set; }
    }
}

上述代码的主要原因是获得其中实现的BeginInvoke()调用 - 您需要该调用在 GUI 线程上创建自己的表单。但是,您需要有一个窗口句柄才能对 GUI 线程进行回调。最简单的方法是让 C++ 代码传入窗口句柄(作为 IntPtr 到达),但您也可以使用以下内容:

过程获取当前进程()主窗口句柄

即使您在从 C++ 调用的 C# 中,也可以获得主窗口的句柄。请注意,C++ 代码可能会更改主窗口句柄并使 C# 代码无效(当然,这可以通过侦听原始句柄上的正确 Windows 消息来捕获 - 您也可以使用上面的代码执行此操作)。

抱歉,没有显示上面的 Win32 调用声明。您可以通过搜索网络获取它们的 P/Invoke 声明。(我的 Win32 类很大。)

就 C++ 代码中的回调而言 - 只要您使回调相当简单,您就可以使用Marshal.GetDelegateForFunctionPointer将传入的函数指针(变成 IntPtr)转换为常规的旧 C# 委托。

因此,至少回调 C++ 非常容易(只要您正确定义了委托声明)。例如,如果您有一个接受char const *并返回 void 的 C++ 函数,您的委托声明将类似于:

public delegate void MyCallback([MarshalAs(UnmanagedType.LPStr)] string myText);

这涵盖了基础知识。使用带有传入窗口句柄的上述类在NativeWindowWithCallbacks.BeginInvoke()调用中创建您自己的基于表单的窗口。现在,如果您想使用 C++ 窗口代码,例如,在 C++ 代码管理的窗口中添加菜单项条目,事情又变得更加复杂。.Net 控件代码不喜欢与它没有创建的任何窗口进行交互。因此,要添加一个菜单项,您最终会编写包含大量 Win32 P/Invokes 的代码来执行与编写 C 代码时相同的调用。上面的NativeWindowWithCallbacks类将再次派上用场。

于 2012-09-11T20:05:58.440 回答
-1

我打算将此作为对较早帖子的评论发布,但是由于您尚未接受任何答案,所以它可能就是您要寻找的答案。

你原来的帖子有一个问题:“它特别容易吗?” 正如你得到的答案所证明的那样,这个问题答案是肯定的。

如果其他建议(本机导出/COM/等)“超出您的想象”(您的话!),并且您将无法深入学习,我的建议是您需要重新考虑您的建议的架构。

为什么不在 C++ 库中编写共享函数,这样您现有的 C++ 应用程序可以更轻松地使用它?作为一般规则,从托管代码使用本机组件比反之更容易 - 因此编写 C# 应用程序以使用共享 C++ DLL 将是一件容易得多的工作。

我意识到这并不能回答最初的技术问题,但也许它是对您面临的问题的更务实的回答。

于 2010-03-30T13:30:32.433 回答