如何使用 C# 后台工作线程在本机C++代码中报告进度
How to use C# BackgroundWorker to report progress in native C++ code?
在我的Visual Studio解决方案中,我用C#实现了UI,并在本机C++中实现了一些代码。
我使用 BackgroundWorker 类来报告执行长操作的进度。
如何使用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");
}
}
}
在 C# 代码中,使用 DLLImport 属性声明本机C++方法,并从
BackgroundWorker.ProgressChanged
处理程序调用此方法。
免责声明:我没有测试过任何这段代码,这可能不是最好的方法,但至少在理论上我认为这是可行的。 希望这里更有经验的成员之一可以验证这是否真的正确。
这假设您从 C# 启动后台工作线程,并且您希望 C# 中使用 ProgressChanged
事件(我假设是这种情况,因为您的 UI 是 C# 中的)。
您仍然可以在 C# 中使用 BackgroundWorker
,但只需让它使用我上面提到的 DLLImport 调用本机方法即可。 您还可以修改方法的签名,使其采用与 ReportProgress
的签名匹配的函数指针,然后从本机代码调用该委托。
MSDN 有一些关于封送委托和函数指针的文章(尽管这些示例都使用 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);
}
希望这是有道理的(希望这也是对的!
示例如下。它在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();
}
}
}
以下链接可能很有用:
- 与本机库互操作(单声道)
- 如何:使用C++互操作封送回调和委托
- 如何:使用 PInvoke 封送函数指针
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 代码在main()中运行,但在函数中出现错误
- 在VS代码中交叉编译Windows与Linux上的MinGW的SDL程序
- 编译包含字符串的代码时遇到问题
- 我在c++代码中生成了一个运行时#3异常
- 如何在linux终端中同时编译和运行c++代码
- 为cl.exe(Visual Studio代码)指定命令行C++版本
- 在Linux for Windows上编译C++代码时出错
- 我的字符计数代码计算错误.为什么
- 孤立代码块在结构中引发异常
- 在编译C++代码(具有dlib和opencv)到WASM时面临问题
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 处理小于cpu数据总线的数据类型.(c++转换为机器代码)
- 此代码是否违反一个定义规则
- 为什么我的代码在输出中增加了93天
- 我的简单if-else语句是如何无法访问的代码
- 使用动态分配的数组会导致代码分析发出虚假的C6386缓冲区溢出警告
- 为什么在这个代码结束循环中没有得到结束
- 在c代码之间共享数据的最佳方式
- 这个指针和内存代码打印是什么?我不知道是打印垃圾还是如何打印我需要的值