Вопрос по visual-c++, c++, interop, c# – Как использовать C # BackgroundWorker, чтобы сообщить о прогрессе в собственном коде C ++?

-1

В моем решении Visual Studio у меня есть интерфейс, реализованный на C #, и некоторый код, реализованный на родном 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");
        }
    }
}
Просто абстрактная идея: учитывая, что BackgroundWorker - это просто отдельный поток, вы можете объявить небезопасный сегмент кода при вызове c #; раскрутить новую ветку; и объявить указатель на функцию, чтобы сообщить о прогрессе. Короче говоря, вы должны программировать на один уровень ниже абстракций потоков и делегатов. Antony Thomas
@Even Dark Прежде всего, ваш комментарий не имеет отношения к проблеме. Но в любом случае. Пользовательский интерфейс более удобен для написания на C #, интенсивного вычисления на C ++, я даже не говорю о случаях, когда у вас нет выбора, потому что вы не запускаете проект, у вас просто есть код. И когда у вас есть C ++ и C # код, вам может потребоваться связь между ними - в моем случае мне нужно сообщать о прогрессе. sergtk
Какой смысл писать нативный код? Вы используете управляемые объекты, поэтому естественно, что вы пишете это в управляемом коде. Если вы хотите написать что-то подобное на нативном c ++, то вам нужен нативный код Evan Dark

Ваш Ответ

2   ответа
2

In your C# code, declare your native C++ method with the DLLImport attribute, and call this method from your BackgroundWorker.ProgressChanged handler.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я не проверял ни один из этого кода, и это, возможно, не лучший подход, но, по крайней мере, в теории, я думаю, что это будет работать. Надеюсь, один из более опытных членов сможет проверить, действительно ли это правильно.

Это предполагает, что вы запускаете фоновый рабочий из C #, и вы хотитеProgressChanged событие в C # (я предполагаю, что это так, поскольку ваш пользовательский интерфейс находится в C #).

Вы все еще можете использоватьBackgroundWorker в C #, но просто вызовите свой собственный метод, используя DLLImport, который я упоминал выше. Вы также можете изменить сигнатуру вашего метода, чтобы взять указатель на функцию, совпадающий с сигнатурой дляReportProgressи затем вызовите этот делегат из вашего собственного кода.

MSDN имеет несколько статей наВыделение делегатов и указателей функций(хотя во всех примерах используется C ++ / CLI). Вы также можете посмотреть документацию дляDllImport а такжеMarshalAs атрибуты иUnmanagedType ENUM.

Например, если ваш родной метод был

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);
}

ВашDLLImport заfoo будет выглядеть

// 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);
}

Надеюсь, это имело какой-то смысл (и, надеюсь, это тоже верно!)

Как это позволяет вызывать worker.ReportProgress из кода C ++? sergtk
Мой плохой, я неправильно понял вопросы. Я думал, что вы пытаетесь сообщить о прогрессе от C # до вашего собственного кода C ++. Я не делал много C ++ взаимодействия, идущего в другую сторону, хотя я думаю, что у меня есть идея. Я вернусь к вам.
Я отредактировал свой ответ на то, что я считаю более правильным подходом.
Ваш ответ оказался весьма полезным для меня. Я наконец придумала готовый код. Вы можете посмотреть на это в моем ответе. Благодарю. sergtk
0

Пример следует. Он был протестирован на 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

Похожие вопросы