如何使用 C# BackgroundWorker 报告本机 C++ 代码的进度?
Posted
技术标签:
【中文标题】如何使用 C# BackgroundWorker 报告本机 C++ 代码的进度?【英文标题】:How to use C# BackgroundWorker to report progress in native C++ code? 【发布时间】:2012-08-10 21:26:01 【问题描述】:在我的 Visual Studio 解决方案中,我用 C# 实现了 UI,并用原生 C++ 实现了一些代码。
我使用BackgroundWorker class来实现执行长操作的报告进度。
如何使用 BackgroundWorker
报告我的原生 C++ 代码的进度?
也就是说,如何将下面的 C# 代码重写为原生 C++ 并从 C# 调用获得的 C++ 代码? 如果无法直接重写下面的代码,最好了解其他等效解决方案。谢谢。
class MyClass
public void Calculate(object sender, DoWorkEventArgs e)
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < StepCount; i++)
if (worker.CancellationPending)
e.Cancel = true;
break;
// code which handles current iteration here
worker.ReportProgress((i + 1) * 100 / StepCount,
"Report progress message");
【问题讨论】:
只是一个抽象的想法:鉴于BackgroundWorker只是一个单独的线程,你可以在调用c#时声明一个不安全的代码段;分拆一个新线程;并声明一个函数指针返回报告进度。简而言之,您是否可以在线程和委托的抽象之下的一层进行编程。 写原生代码有什么意义?您使用托管对象,因此很自然地在托管代码中编写它。如果你想用原生 c++ 写这样的东西,那么你需要原生代码 @Even Dark 首先,您的评论与问题无关。但无论如何。 UI用C#写更方便,C++计算密集型代码,我什至不说你没有选择的情况,因为你没有启动项目,你只有一个代码。当你有 C++ 和 C# 代码时,你可能需要它们之间的一些通信——在我的例子中,我需要报告进度。 【参考方案1】:
在您的 C# 代码中,使用 DLLImport 属性声明您的本机 C++ 方法,并从您的 BackgroundWorker.ProgressChanged
处理程序中调用此方法。
免责声明:我没有测试过任何代码,这可能不是最好的方法,但至少在理论上我认为这是可行的。希望这里有经验的成员之一可以验证这是否真的正确。
这假设您正在从 C# 启动后台工作程序,并且您希望在 C# 中使用 ProgressChanged
事件(我假设是这种情况,因为您的 UI 是在 C# 中)。
您仍然可以在 C# 中使用 BackgroundWorker
,但只需让它使用我上面提到的 DLLImport 调用您的本机方法。您还可以修改方法的签名以获取与 ReportProgress
的签名匹配的函数指针,然后从您的本机代码调用该委托。
MSDN 上有一些关于Marshalling delegates and function pointers 的文章(尽管示例都使用了 C++/CLI)。您可能还想查看DLLImport 和MarshalAs 属性以及UnmanagedType 枚举的文档。
例如,如果您的本地方法是
void foo(int arg1, BOOL arg2)
// Your code here
你可以在你的本地代码中定义一个函数指针类型
// Corresponds to void BackgroundWorker.ReportProgress(int progress, object state)
typedef void (*NativeReportProgress) (int, void*);
并将您的本机签名更改为
void foo(int arg1, BOOL arg2, NativeReportProgress progressPtr)
// Some code.
progressPtr(progressValue, stateVar);
foo
的 DLLImport
看起来像
// Delegate type for BackgroundWorker.ReportProgress
delegate void ReportProgressDelegate(int progress, object state);
// The MarshalAs attribute should handle the conversion from the .NET
// delegate to a native C/C++ function pointer.
[DLLImport]
void foo([MarshalAs(UnmanagedType.I4)] Int32 arg1,
[MarshalAs(UnmanagedType.Bool)] bool arg2,
[MarshalAs(UnmanagedType.FunctionPointer)] ReportProgressDelegate progressDel);
那么你的工人看起来像
void DoWork(object sender, DoWorkEventArgs e)
var worker = (BackgroundWorker)sender;
// Notice that worker.ReportProgress is not followed the by ().
// We're not actually calling the method here, we're just passing
// a function pointer to that method into foo.
foo(intArg, boolArg, worker.ReportProgress);
希望这是有道理的(希望它也是正确的!)
【讨论】:
我的错,我误解了这些问题。我以为您正在尝试将 C# 的进度报告回您的本机 C++ 代码。我没有做太多的 C++ 互操作,尽管我想我有一个想法。我会回复你的。 我已经编辑了我认为更正确方法的答案。 您的回答对我来说似乎很有用。我终于想出了准备使用的代码。你可以看看我的回答。谢谢。【参考方案2】:示例如下。 它在 x86 C# 和本机 Visual C++ 上进行了测试:
CppLayer.h:
#ifdef CPPLAYER_EXPORTS
#define CPPLAYER_API __declspec(dllexport)
#else
#define CPPLAYER_API __declspec(dllimport)
#endif
extern "C"
typedef void (__stdcall *ReportProgressCallback)(int, char *);
typedef bool (__stdcall *CancellationPendingCallback)();
struct CPPLAYER_API WorkProgressInteropNegotiator
ReportProgressCallback progressCallback;
CancellationPendingCallback cancellationPending;
bool cancel;
;
CPPLAYER_API void __stdcall CppLongFunction(WorkProgressInteropNegotiator& negotiator);
CppLayer.cpp:
#include "stdafx.h"
#include "CppLayer.h"
#include <iostream>
extern "C"
// This is an example of an exported function.
CPPLAYER_API void __stdcall CppLongFunction(WorkProgressInteropNegotiator& negotiator)
const int STEP_COUNT = 12;
char * messages[3] = "ONE", "TWO", "THREE";
for (int i = 0; i < STEP_COUNT; i++)
Sleep(100);
if (negotiator.cancellationPending())
negotiator.cancel = true;
break;
std::cout << "Calculate " << i << std::endl;
negotiator.progressCallback((i + 1) * 100 / STEP_COUNT, messages[i % 3]);
;
与 C++ 代码互操作的 C# 类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Threading;
namespace CSharpLayer
class SandboxCppProgress
public delegate void ReportProgressCallback(int percentage, string message);
public delegate bool CancellationPendingCallback();
[StructLayout(LayoutKind.Sequential)]
public class WorkProgressInteropNegotiator
public ReportProgressCallback reportProgress;
public CancellationPendingCallback cancellationPending;
#pragma warning disable 0649
// C# does not see this member is set up in native code, we disable warning to avoid it.
public bool cancel;
#pragma warning restore 0649
[DllImport("CppLayer.dll")]
public static extern void CppLongFunction([In, Out] WorkProgressInteropNegotiator negotiator);
static void CSharpLongFunctionWrapper(object sender, DoWorkEventArgs e)
BackgroundWorker bw = sender as BackgroundWorker;
WorkProgressInteropNegotiator negotiator = new WorkProgressInteropNegotiator();
negotiator.reportProgress = new ReportProgressCallback(bw.ReportProgress);
negotiator.cancellationPending = new CancellationPendingCallback(() => bw.CancellationPending);
// Refer for details to
// "How to: Marshal Callbacks and Delegates Using C++ Interop"
// http://msdn.microsoft.com/en-us/library/367eeye0%28v=vs.100%29.aspx
GCHandle gch = GCHandle.Alloc(negotiator);
CppLongFunction(negotiator);
gch.Free();
e.Cancel = negotiator.cancel;
static EventWaitHandle resetEvent = null;
static void CSharpReportProgressStatus(object sender, ProgressChangedEventArgs e)
string message = e.UserState as string;
Console.WriteLine("Report 0:00% with message '1'", e.ProgressPercentage, message);
BackgroundWorker bw = sender as BackgroundWorker;
if (e.ProgressPercentage > 50)
bw.CancelAsync();
static void CSharpReportComplete(object sender, RunWorkerCompletedEventArgs e)
if (e.Cancelled)
Console.WriteLine("Long operation canceled!");
else if (e.Error != null)
Console.WriteLine("Long operation error: 0", e.Error.Message);
else
Console.WriteLine("Long operation complete!");
resetEvent.Set();
public static void Main(string[] args)
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.ProgressChanged += CSharpReportProgressStatus;
bw.DoWork += CSharpLongFunctionWrapper;
bw.RunWorkerCompleted += CSharpReportComplete;
resetEvent = new AutoResetEvent(false);
bw.RunWorkerAsync();
resetEvent.WaitOne();
以下链接可能有用:
Interop with Native Libraries (Mono) How to: Marshal Callbacks and Delegates Using C++ Interop How to: Marshal Function Pointers Using PInvoke【讨论】:
以上是关于如何使用 C# BackgroundWorker 报告本机 C++ 代码的进度?的主要内容,如果未能解决你的问题,请参考以下文章
C#如何在BackgroundWorker 后台线程中使用定时器?
C# form发起backgroundworker 当form close时 backgroundworker 还会继续工作吗