创建一个C#DLL并从非托管C ++中使用它

时间:2010-03-20 12:48:45

标签: c# c++ interop export dllexport

我有一个本机(非托管)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,但我真的宁愿避免它,我怀疑我工作的任何其他开发人员都知道它。

8 个答案:

答案 0 :(得分:7)

非托管代码中的互操作的通用语言是COM。这很容易进入C#端,只需使用[ComVisible] attribute。您需要在C ++程序中编写COM代码才能使用它,如果您从未这样做过,那么就不那么容易上手了。如果您使用MSVC编译器,请从#import directive开始。

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

答案 1 :(得分:2)

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

答案 2 :(得分:1)

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

在本机世界中,对话框具有对话框模板结构,您可以将其“烹饪”到可执行文件中,无论是DLL还是EXE。精细。但在管理世界中,情况有所不同。 Winforms应用程序没有“对话框模板”资源类型。相反,表单只是代码。

然而:

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

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

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

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

答案 3 :(得分:1)

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

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

要做表格(或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到达),但你也可以使用类似的东西:

  

Process GetCurrentProcess() MainWindowHandle;。。

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

很抱歉,上面的声明Win32调用未显示。您可以通过搜索Web获取P / Invoke声明。 (我的Win32类是巨大的。)

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

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

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

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

答案 6 :(得分: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对象
  8. 然后,您可以使用IAppDomain.Load_2加载库。如果要从网络共享加载.NET DLL,则更复杂,因为您需要调用UnsafeLoadFrom,这在IAppDomain中不可用。但这也是可能的。

答案 7 :(得分:-1)

我打算将此作为评论发布在之前的帖子中,但由于你还没有接受任何答案,但也许它就是你要找的那个。

你的原帖有一个问题:“它特别容易吗?”对此的回答是强烈的 no ,正如你得到的答案所证明的那样。

如果其他建议(本地出口/ COM /等)是“超越你的头脑”(你的话!),而你无法潜入并学习,我的建议是你需要的重新考虑你提出的架构。

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

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