Создание DLL С# и использование его из неуправляемого С++

У меня есть собственное (неуправляемое) приложение на С++ (с использованием wxWidgets для чего оно стоит). Я рассматриваю отдельное приложение-инструмент, которое должно быть написано на С#, которое будет содержать диалоги на основе winform. добавив некоторые из этих диалогов в отдельную DLL, было бы полезно, поскольку я надеюсь, что смогу использовать их из своего приложения на С++.

Но я понятия не имею, сколько понадобится для достижения этой цели, это особенно легко?

ИЗМЕНИТЬ

Мне не нужно напрямую обращаться к функциям диалогов. Мне просто нужно, чтобы мое приложение на С++ вызывало API в DLL С# для передачи данных и способ для вызова С# DLL методов на каком-то объекте наблюдателя/обратной части в приложении С++.

например, из С++:

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

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

Как пользователь делает материал в диалоговом окне С#, методы pCSO вызываются для передачи данных обратно в С++

Итак, я думаю, что это довольно сложный вопрос с С++/С#. Но хотя я знаю С++ и С#, я не знаю, как работает сам .net. Я знаю COM, но я бы предпочел избежать этого, я сомневаюсь, что другие разработчики, с которыми я работаю, знают это.

Ответы

Ответ 1

lingua franca interop в неуправляемом коде - COM. Очень легко перейти на сторону С#, просто используйте атрибут [ComVisible] . Вам нужно будет написать COM-код в своей программе на С++, чтобы использовать его, а не так легко, если вы никогда этого не сделали. Начните с директивы #, если вы используете компилятор MSVC.

Следующий вариант - разместить CLR в своем неуправляемом коде самостоятельно, а не полагаться на слой взаимодействия COM, чтобы позаботиться об этом. Он позволяет создавать управляемые типы напрямую. Для этого также требуется COM, но только для загрузки и инициализации CLR. Этот проект показывает подход.

Ответ 2

Либо используйте COM, либо напишите обертку С++/CLI, которая вызывает ваш диалог С#, а затем вызовите эту оболочку С++/CLI из вашего неуправляемого кода на С++.

Ответ 3

Это зависит от того, что вы подразумеваете под "Я надеюсь, что смогу использовать их из своего приложения на С++".

В родном мире диалоговое окно имеет структуру шаблона диалога, и вы можете "сварить" это в свой исполняемый файл, будь то DLL или EXE. Хорошо. Но в управляемом мире все немного отличается. Тип ресурса типа "диалогового шаблона" для приложений Winforms отсутствует. Вместо этого формы - это просто код.

Однако:

  • Вы всегда можете вызвать в управляемую DLL из неуправляемого кода. Это тривиально. Эта управляемая DLL может отображать ваши диалоги Winforms. Таким образом, родная часть С++ вашего приложения может вызывать эти диалоги. Но он не может создавать их непосредственно без дополнительной работы.

  • Вы всегда можете вставить CIMM/CLI "shim DLL" между вашим собственным кодом С++ и управляемой DLL. В С++/CLI вы можете прозрачно загружать как управляемые, так и .NET-ресурсы/диалоги.

  • В этом случае вы можете вызвать .NET-методы непосредственно из собственного кода без промежуточной DLL промежуточной библиотеки С++/CLI, хотя это немного грязно.

Но что касается использования диалогового ресурса .NET/Winforms напрямую... нет. Не в смысле использования того же шаблона диалога как для Winforms, так и для родного С++.

Ответ 5

Я знаю, что здесь есть несколько ответов, но ни один из них не указывает на рабочий пример. Когда я столкнулся с этой проблемой, я смог понять этот пример.

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

Ответ 6

Использование форм в С# DLL, вызываемой из С++, непросто, но после того, как у вас есть код полезности, он может быть довольно надежным. Обратные вызовы для кода на С++ замечательно легки.

Чтобы делать формы (или 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(), реализованному внутри, - вам нужен этот вызов для создания ваших собственных форм в потоке графического интерфейса. Но вам нужно иметь дескриптор окна, прежде чем вы сможете вернуться к потоку графического интерфейса. Самое простое, чтобы код С++ передавал дескриптор окна (прибывающий как IntPtr), но вы также можете использовать что-то вроде:

Процесс. GetCurrentProcess(). MainWindowHandle;

чтобы получить дескриптор в главном окне, даже если вы находитесь на С#, вызванном из С++. Обратите внимание, что код С++ может изменить дескриптор главного окна и оставить код С# с недопустимым (это можно, конечно, захватить, прослушивая правильные сообщения Windows на исходном дескрипторе, вы также можете сделать это с помощью кода выше).

Извините, но объявления, описанные выше, не показаны. Вы можете получить объявления P/Invoke для них, выполнив поиск в Интернете. (Мой класс Win32 огромен.)

Что касается обратных вызовов в код на С++, то при условии, что обратные вызовы достаточно просты, вы можете использовать Marshal.GetDelegateForFunctionPointer для преобразования передается в указатель функции (который превращается в IntPtr) в обычный старый делегат С#.

Итак, по крайней мере, перезвонить на С++ замечательно легко (пока вы правильно определили объявление делегата). Например, если у вас есть функция С++, которая принимает char const * и возвращает void, ваше объявление делегата будет выглядеть примерно так:

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

Это охватывает основы. Используйте вышеуказанный класс с переданным в дескрипторе окна, чтобы создать свои собственные окна на основе форм в вызове NativeWindowWithCallbacks.BeginInvoke(). Теперь, если вы хотите играть с кодом Windows С++, добавьте элемент меню в окно, которое управляет кодом С++, все снова усложняется. Код элемента управления .Net не любит взаимодействовать с любыми окнами, которые он не создавал. Итак, чтобы добавить элемент меню, вы в конечном итоге нарисуете код с большим количеством Win32 P/Invokes, чтобы делать то же самое, что и вы, если бы написали C-код. Вышеприведенный класс NativeWindowWithCallbacks снова пригодится.

Ответ 7

Если вы хотите загрузить любую .NET DLL в приложение С++, вы должны разместить .NET в своем приложении на С++.

Здесь вы можете найти пример от Microsoft: https://code.msdn.microsoft.com/CppHostCLR-e6581ee0 Этот пример также содержит некоторые файлы заголовков, которые требуются.

Короче вам нужно сделать следующее:

  • Загрузите файл mscoree.dll с помощью команды LoadLibrary (иначе вы могли бы статически связать mscoree.dll с вашим проектом)
  • Вызвать функцию CLRCreateInstance, которая экспортируется mscoree.dll, для создания объекта ICLRMetaHost
  • Вызвать метод GetRuntime объекта ICLRMetaHost, чтобы получить объект ICLRRuntimeInfo из вашей предпочтительной версии .NET.
  • Проверьте, загружается ли версия, вызывающая ICLRRuntimeInfo.IsLoadable
  • Вызвать метод GetInterface из ICLRRuntimeInfo, чтобы получить ICorRuntimeHost
  • Вызов метода Start объекта ICorRuntimeHost
  • Вызвать метод GetDefaultDomain из объекта ICorRuntimeHost для объекта IAppDomain

Затем вы можете загружать библиотеки, используя IAppDomain.Load_2. Если вы хотите загрузить .NET DLL из сетевых ресурсов, это сложнее, потому что вам нужно вызвать UnsafeLoadFrom, который недоступен в IAppDomain. Но это также возможно.

Ответ 8

Я собирался опубликовать это как комментарий к предыдущему сообщению, но так как вы еще не приняли никаких ответов, возможно, это будет тот, который вы ищете.

У вашего оригинального сообщения был один вопрос: "Это особенно легко?" Ответ на этот вопрос является подчеркнутым no, о чем свидетельствуют полученные ответы.

Если другие предложения (собственный экспорт/COM/и т.д.) "на вашей голове" (ваши слова!), и вы не сможете погрузиться и учиться, мое предложение будет состоять в том, что вам нужно пересмотреть предлагаемую архитектуру.

Почему бы не написать общие функции в библиотеке С++, которая затем будет легче использоваться вашим существующим приложением С++? Как правило, гораздо проще потреблять собственные компоненты из управляемого кода, чем наоборот - поэтому писать ваше приложение С# для использования общей библиотеки С++ было бы намного проще.

Я понимаю, что это не отвечает на исходный технический вопрос, но, возможно, это более прагматичный ответ на проблему, с которой вы сталкиваетесь.