问题
In my Visual Studio solution I have UI implemented in C# and some code implemented in native C++.
I use BackgroundWorker class to implemenent reporting progress of execution long operation.
How can I use BackgroundWorker
to report progress from my native C++ code?
In other words, how can I rewrite the C# code below to native C++ and call obtained C++ code from C#? If it is not possible to rewrite the code below directly, it could be good to know about other equivalent solutions. Thanks.
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");
}
}
}
回答1:
In your C# code, declare your native C++ method with the DLLImport attribute, and call this method from your BackgroundWorker.ProgressChanged
handler.
DISCLAIMER: I have not tested any of this code, and this may not be the best approach, but at least in theory I think this would work. Hopefully one of the more experienced members here can verify if this is actually correct.
This assumes you are starting the background worker from C#, and you want the ProgressChanged
event in C# (I assume this is the case since your UI is in C#).
You can still use the BackgroundWorker
in C#, but just have it call your native method using the DLLImport I mentioned above. You can also modify the signature of your method to take a function pointer that matches the signature for ReportProgress
, and then call that delegate from your native code.
MSDN has some articles on Marshalling delegates and function pointers(although the examples all use C++/CLI). You may also want to look at the documentation for the DLLImport and MarshalAs attributes, and the UnmanagedType enum.
For instance, if your native method was
void foo(int arg1, BOOL arg2)
{
// Your code here
}
you would define a function pointer type in your native code as
// Corresponds to void BackgroundWorker.ReportProgress(int progress, object state)
typedef void (*NativeReportProgress) (int, void*);
and change your native signature to
void foo(int arg1, BOOL arg2, NativeReportProgress progressPtr)
{
// Some code.
progressPtr(progressValue, stateVar);
}
Your DLLImport
for foo
would look like
// 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);
Then your worker would look like
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);
}
Hopefully that made some sense (and hopefully it's right, too!)
回答2:
Example follows. It was tested on x86 C# and native 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# class which interoperate with C++ code:
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();
}
}
}
Following links might be useful:
- Interop with Native Libraries (Mono)
- How to: Marshal Callbacks and Delegates Using C++ Interop
- How to: Marshal Function Pointers Using PInvoke
来源:https://stackoverflow.com/questions/11909484/how-to-use-c-sharp-backgroundworker-to-report-progress-in-native-c-code