开发者

Creating a C# DLL and using it from unmanaged C++

I have a native (unmanaged) C++ application (using wxWidgets for what it's worth). I'm considering a separate tool application to be written in C# which would contain winform-based dialogs. putting some of those dialogs in a separate DLL would be useful as I'd hope to be able to use them from my C++ app.

But I have no idea how much messing about is needed to accomplish this, is it particularly easy?

EDIT:

开发者_运维百科

I don't need to call dialogs functions directly. I just need a way for my C++ app to call an API in the C# DLL in order to pass data, and a way for the C# DLL to call methods on some kind of observer/back object in the C++ app.

e.g from C++:

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

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

As the user does stuff in the C# dialog, pCSO methods are called to pass data back into C++

So it's pretty much a raw C++/C# communication question, I think. But though I know C++ and C# I don't know how .net itself works. I know COM but would really rather avoid it, I doubt any other developers I work with know it.


The lingua franca of interop in unmanaged code is COM. That's very easy to get going on the C# side, just use the [ComVisible] attribute. You'll need to write COM code in your C++ program to use it, not so easy to get going if you've never done that. Start with the #import directive if you use the MSVC compiler.

Your next option is to host the CLR in your unmanaged code yourself rather than relying on the COM interop layer to take care of it. It allows you to create managed types directly. This requires COM as well, but only to get the CLR loaded and initialized. This project shows the approach.


Either use COM, or write a C++/CLI wrapper that calls your C# dialog, then call this C++/CLI wrapper from your unmanaged C++ code.


It depends on what you mean by "I'd hope to be able to use them from my C++ app."

In the native world, a dialog has a dialog template structure and you can "cook" this into your executable, be it DLL or EXE. Fine. But in the managed world, things are a little different. There is no "dialog template" resource type for Winforms applications. Instead, forms are just code.

However:

  • You can always CALL INTO a managed DLL from unmanaged code. This is trivial. That managed DLL can display your Winforms dialogs. So the native C++ portion of your app can invoke those dialogs. But it can't instantiate them directly without some additional work.

  • You can always insert a C++/CLI "shim DLL" between your native C++ code and your managed DLL. Within C++/CLI, you can load both managed and .NET resources/dialogs transparently.

  • For that matter, you can call .NET methods directly from native code, without an intermediary C++/CLI shim DLL, though it's a little messy.

But as for using the ".NET/Winforms dialog resource" directly...no. Not in the sense of using the same dialog template for both Winforms as well as native C++.


Links to descriptions of native exports from pure managed code, as requested by nobugz:

  • http://www.codeproject.com/KB/dotnet/DllExporter.aspx
  • http://www.csharphelp.com/archives3/archive500.html
  • http://www.codeproject.com/KB/dotnet/DllExport.aspx?msg=2657379
  • http://www.codeproject.com/kb/dotnet/emilio_managed_unmanaged.aspx


I know there are a few answers here, but none of them point to a working example. When I ran into this problem I was able to figure out thanks to this example.

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


Using forms in a C# DLL called from C++ is not easy, but once you have some utility code written, it can be fairly robust. Callbacks to C++ code are remarkably easy.

To do Forms (or WPF for that matter), The NativeWindow class is your friend. You want more capability than NativeWindow gives you so a derivation is in order. The code below shows an implementation that derives from NativeWindow and provides for a BeginInvoke() call and for windows message event handlers.

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; }
    }
}

The main reason for the above code is to get at the BeginInvoke() call implemented within - you need that call to create your own forms on the GUI thread. But, you need to have a window handle before you can have a call back on the GUI thread. The easiest thing is to have the C++ code pass the window handle in (arriving as an IntPtr) but you can also use something like:

Process.GetCurrentProcess().MainWindowHandle;

to get the handle to the main window even when you are in C# called from C++. Note that the C++ code could change the main window handle and leave the C# code with an invalid one (this can, of course, be captured by listening for the proper windows messages on the original handle - you can also do this with the code above).

Sorry but the declarations Win32 calls above are not shown. You can get the P/Invoke declarations for them by searching the web. (My Win32 class is huge.)

As far as callbacks into the C++ code go - as long as you make the callbacks fairly simple you can use Marshal.GetDelegateForFunctionPointer to convert a passed in function pointer (which gets turned into an IntPtr) into a regular old C# delegate.

So, at least calling back to C++ is remarkably easy (as long as you get the delegate declaration defined properly). For instance if you have a C++ function that takes a char const * and returns void, your delegate declaration will look something like:

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

That covers the basics. Use the above class with a passed in window handle to create your own forms based windows within the NativeWindowWithCallbacks.BeginInvoke() call. Now, if you want to play with the C++ windows code, to, say, add a menu item entry in a window the C++ code manages, things again get more complicated. The .Net controls code doesn't like interfacing with any windows it didn't create. So, to add a menu item, you end up writing code with lots of Win32 P/Invokes to do the identical calls you would do if you wrote C code. The above NativeWindowWithCallbacks class will again come in handy.


If you want to load an any .NET DLL in a C++ application then you must host .NET in your C++ application.

You can find an example from Microsoft here: https://code.msdn.microsoft.com/CppHostCLR-e6581ee0 That example also includes some header files, which are required.

In short you need to do the following:

  1. Load the mscoree.dll using the LoadLibrary command (otherwise you could statically link the mscoree.dll into your project)
  2. Call the CLRCreateInstance function, which is exported by the mscoree.dll, to create an ICLRMetaHost object
  3. Call the GetRuntime method of the ICLRMetaHost object, to get an ICLRRuntimeInfo object of your preferred .NET version.
  4. Check whether the version is loadable calling ICLRRuntimeInfo.IsLoadable
  5. Call the GetInterface method from the ICLRRuntimeInfo to get the ICorRuntimeHost
  6. Call the Start method of the ICorRuntimeHost object
  7. Call the GetDefaultDomain method from the ICorRuntimeHost object to geht the IAppDomain object

Then you can load libraries using IAppDomain.Load_2. If you want to load .NET DLLs from network shares it is more complex, because you need to call UnsafeLoadFrom, which is not available in IAppDomain. But this is also possible.


I was going to post this as a comment to an earlier post, but since you've not accepted any answers yet maybe it'll be the one you're looking for.

Your original post had one question: "is it particularly easy?" The answer to that is an emphatic no, as evidenced by the answers you're getting.

If the other suggestions (native exports/COM/etc) are "way over your head" (your words!), and you're not going to be able to dive in and learn, my suggestion would be that you need to reconsider your proposed architecture.

Why not write the shared functions in a C++ library, which can then more easily be used by your existing C++ application? As a general rule it's a lot easier to consume native components from managed code than vice versa - so writing your C# app to consume the shared C++ DLL would be a much easier job.

I realise this doesn't answer the original technical question, but perhaps it's a more pragmatic answer to the problem you're facing.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜