Создание 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, так и для родного С++.
Ответ 4
Ссылки на описания родного экспорта из чистого управляемого кода, как запрошено nobugz:
Ответ 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/и т.д.) "на вашей голове" (ваши слова!), и вы не сможете погрузиться и учиться, мое предложение будет состоять в том, что вам нужно пересмотреть предлагаемую архитектуру.
Почему бы не написать общие функции в библиотеке С++, которая затем будет легче использоваться вашим существующим приложением С++? Как правило, гораздо проще потреблять собственные компоненты из управляемого кода, чем наоборот - поэтому писать ваше приложение С# для использования общей библиотеки С++ было бы намного проще.
Я понимаю, что это не отвечает на исходный технический вопрос, но, возможно, это более прагматичный ответ на проблему, с которой вы сталкиваетесь.