如何使用 C# 后台工作线程在本机C++代码中报告进度

How to use C# BackgroundWorker to report progress in native C++ code?

本文关键字:代码 C++ 报告 本机 何使用 后台 工作 线程      更新时间:2023-10-16

在我的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 封送函数指针