-1

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");
        }
    }
}
sergtk
  • 10,714
  • 15
  • 75
  • 130
  • 1
    Just an abstract idea: Given that BackgroundWorker is just a separate thread, you can declare an unsafe code segment in calling c#; spin off a new thread; and declare a function pointer back to report progress. In short, have you to program at one level below the abstractions of threads and delegates. – Antony Thomas Aug 10 '12 at 21:58
  • What is the point in writing it native code? You use managed objects, so it's natural that you write this in managed code. If you want to write something like this in native c++, THEN you need native code – Evan Dark Aug 11 '12 at 00:01
  • @Even Dark First of all your comment does not relates to the problem. But anyway. UI is more convenient to write in C#, calculation intensive code in C++, I even don't say about cases when you have no choice because you don't start the project, you just have a code. And when you have C++ and C# code, you might need some communication between them - in my case I need to report progress. – sergtk Aug 11 '12 at 14:24

2 Answers2

2

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!)

sergtk
  • 10,714
  • 15
  • 75
  • 130
Brandon Dybala
  • 324
  • 2
  • 11
  • My bad, I misunderstood the questions. I thought you were trying to report progress from C# back to your native C++ code. I haven't done much C++ interop going the other way, although I think I have an idea. I'll get back to you. – Brandon Dybala Aug 10 '12 at 23:41
  • 1
    I've edited my answer to what I think is a more correct approach. – Brandon Dybala Aug 11 '12 at 01:08
  • Your answer appeared quite useful for me. I finally come up with ready to use code. You may take a look to it in my answer. Thanks. – sergtk Aug 23 '12 at 23:58
0

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:

sergtk
  • 10,714
  • 15
  • 75
  • 130