Создание C # DLL и использование ее из неуправляемого C ++ - PullRequest
14 голосов
/ 20 марта 2010

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

Но я понятия не имею, насколько много нужно для этого, разве это особенно легко?

EDIT

Мне не нужно напрямую вызывать функции диалогов. Мне просто нужен способ для моего приложения C ++ вызывать API в C # DLL для передачи данных, и способ для 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 ]

7 голосов
/ 20 марта 2010

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

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

2 голосов
/ 28 марта 2010

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

1 голос
/ 30 марта 2010

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

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

1 голос
/ 22 марта 2010

Ссылки на описания собственного экспорта из чистого управляемого кода в соответствии с запросом nobugz:

1 голос
/ 20 марта 2010

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

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

Тем не менее:

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

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

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

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

0 голосов
/ 28 ноября 2014

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

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

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

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

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

0 голосов
/ 12 сентября 2012

Использование форм в C # DLL, вызываемой из C ++, нелегко, но если у вас написан некоторый служебный код, он может быть довольно надежным. Обратные вызовы кода 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. Проще всего сделать так, чтобы код C ++ передавал дескриптор окна (поступая как IntPtr), но вы также можете использовать что-то вроде:

Процесс * 1 020 * * * +1021 GetCurrentProcess () * * * тысяча двадцать-дв 1 023 * MainWindowHandle ;..

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

Извините, но объявления Win32 выше не отображаются. Вы можете получить декларации 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 снова пригодится.

0 голосов
/ 30 марта 2010

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

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

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

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

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

...