我有一个本机(非托管)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,但我真的宁愿避免它,我怀疑我工作的任何其他开发人员都知道它。
答案 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)
根据nobugz的要求链接到纯托管代码的本机导出描述:
答案 4 :(得分:1)
我知道这里有一些答案,但没有一个指向一个有效的例子。当我遇到这个问题时,我能够理解这个例子。
答案 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到达),但你也可以使用类似的东西:
即使你在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 该示例还包括一些头文件,这些文件是必需的。
简而言之,您需要执行以下操作:
然后,您可以使用IAppDomain.Load_2加载库。如果要从网络共享加载.NET DLL,则更复杂,因为您需要调用UnsafeLoadFrom,这在IAppDomain中不可用。但这也是可能的。
答案 7 :(得分:-1)
我打算将此作为评论发布在之前的帖子中,但由于你还没有接受任何答案,但也许它就是你要找的那个。
你的原帖有一个问题:“它特别容易吗?”对此的回答是强烈的 no ,正如你得到的答案所证明的那样。
如果其他建议(本地出口/ COM /等)是“超越你的头脑”(你的话!),而你无法潜入并学习,我的建议是你需要的重新考虑你提出的架构。
为什么不在C ++库中编写共享函数,然后现有的C ++应用程序可以更容易地使用它?作为一般规则,从托管代码中使用本机组件比反之亦然要容易得多 - 因此编写C#应用程序以使用共享C ++ DLL将会更容易。
我意识到这并不能回答最初的技术问题,但也许这是对你所面临的问题的一个更务实的答案。