4

say I have a C++ library function for computing PI:

// pi.h:
#ifdef BUILDING_DLL
#define DLL_MACRO __declspec(dllexport)
#else
#define DLL_MACRO __declspec(dllimport)
#endif

namespace Cpp {
  class PI {
  public:
    static double DLL_MACRO compute();
  };
}; 

// pi.cpp: 
#include "pi.h"
#include <cmath>

double 
Cpp::PI::compute() {
   // Leibnitz summation formulae:
   double sum = 0.0;
   for(long n = 0; n < 100*1000*1000; n++)
     sum += 4.0*pow(-1.0, n)/(2*n + 1.0);
   return sum;
} 

I need to call this function from C#, and I want to use C++/CLI as a "bridge". However this C++ function is somewhat slow. Therefore the C# code calling this function need to get callbacks telling it how far the function has come in %. The C# code might need some state, e.g. a progress bar, to deal with this info. So the callbacks from C++ must enter into a member function on the C# side.

So I introduce:

// piCLI.h: The C++/CLI "bridge" between C# and C++
#include "pi.h"
#pragma once
namespace CLI {
   public ref class PI abstract {
   public:
      double compute() {
        return Cpp::PI::compute();
      }
      virtual void progress(int percentCompleted) = 0;        
   };
 }; 

and

 namespace CSharp
 {
    public class PI : CLI.PI
    {
       public override void progress(int percentCompleted)
       {
          System.Console.WriteLine(percentCompleted + "% completed.");
       }
     }
  }

Now invoking CSharp.PI.compute() works fine :-). It forwards the call to Cpp::PI::compute() as intended.

But how do I get the C++ library to forward progress updates to CSharp.PI.progress() whilst Cpp::PI::compute() is running?

Thanks in advance for any answers!

Andy
  • 3,251
  • 4
  • 32
  • 53
  • 1
    Some of the information in [this post](http://stackoverflow.com/questions/5030936/c-callback-to-send-text-back-to-c) may be useful. – Mark Wilkins Jun 28 '11 at 14:13

2 Answers2

9

I would take a function pointer/delegate approach as well:

// pi.h:
#pragma once

#ifndef DLL_MACRO
#ifdef BUILDING_DLL
#define DLL_MACRO __declspec(dllexport)
#else
#define DLL_MACRO __declspec(dllimport)
#endif
#endif

namespace Cpp {
    typedef void (__stdcall *ComputeProgressCallback)(int);

    class PI {
    public:
        static double DLL_MACRO compute(ComputeProgressCallback callback);
    };
}

// pi.cpp: 
#include "pi.h"
#include <cmath>

double Cpp::PI::compute(Cpp::ComputeProgressCallback callback) {
    double sum = 0.;
    for (long n = 0L; n != 100000000L; ++n) {
        sum += 4. * std::pow(-1., n) / (2L * n + 1.);
        callback(/*impl*/);
    }
    return sum;
}
// piCLI.h: The C++/CLI "bridge" between C# and C++
#pragma once
#include "pi.h"

namespace CLI {
    public delegate void ComputeProgressDelegate(int percentCompleted);

    public ref class PI abstract sealed {
    public:
        static double compute(ComputeProgressDelegate^ callback) {
            using System::IntPtr;
            using System::Runtime::InteropServices::Marshal;

            IntPtr cbPtr = Marshal::GetFunctionPointerForDelegate(callback);
            return Cpp::PI::compute(
                static_cast<Cpp::ComputeProgressCallback>(cbPtr.ToPointer())
            );
        }
    };
}
namespace CSharp {
    public static class PI {
        public static double compute() {
            CLI.PI.compute(
                percentCompleted => System.Console.WriteLine(
                    percentCompleted.ToString() + "% completed."
                )
            );
        }
    }
}

Or, to override an abstract progress method rather than creating a delegate on the C# side:

// piCLI.h: The C++/CLI "bridge" between C# and C++
#pragma once
#include "pi.h"

namespace CLI {
    public ref class PI abstract {
        delegate void ComputeProgressDelegate(int percentCompleted);

    public:
        double compute() {
            using System::IntPtr;
            using System::Runtime::InteropServices::Marshal;

            ComputeProgressDelegate^ callback = gcnew ComputeProgressDelegate(
                this,
                &PI::progress
            );
            IntPtr cbPtr = Marshal::GetFunctionPointerForDelegate(callback);
            return Cpp::PI::compute(
                static_cast<Cpp::ComputeProgressCallback>(cbPtr.ToPointer())
            );
        }

    protected:
        virtual void progress(int percentCompleted) abstract;
    };
}
namespace CSharp {
    public sealed class PI : CLI.PI {
        protected override void progress(int percentCompleted) {
            System.Console.WriteLine(
                percentCompleted.ToString() + "% completed."
            );
        }
    }
}
ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • Thank you! I just ran your second approach and it works beautifully :-) – Andy Jun 28 '11 at 16:14
  • I guess that if I write Cpp::PI::compute() as an extern "C" function I can skip C++/CLI altogether and use Marshal::GetFunctionPointerForDelegate() to pass a pointer to a C# function to C++ directly? – Andy Jun 29 '11 at 12:14
  • 1
    @Andreas : "*I guess that if I write Cpp::PI::compute() as an extern "C" function I can skip C++/CLI altogether*" Yes indeed. "*and use Marshal::GetFunctionPointerForDelegate() to pass a pointer to a C# function to C++ directly?*" No need for `Marshal.GetFunctionPointerForDelegate()`, you can declare the P/Invoke function in C# to take a delegate of the appropriate signature and the CLR will marshal the delegate to a function pointer for you. – ildjarn Jun 29 '11 at 12:16
  • How would I extend this model to pass a class (object) back to the callback? – JohnB Mar 16 '15 at 16:24
  • @JohnB : Use [`GCHandle`](https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.gchandle.aspx) to obtain an `IntPtr`, and have the native side pass around the resulting `void*`. – ildjarn Jul 24 '15 at 02:53
1

Pass a C++/cli defined Native function to your native C++ as a callback and upon callback use a gcroot to call your managed C# function.

ghimireniraj
  • 397
  • 1
  • 5
  • Thanks! So you are suggesting that I add a function pointer argument to Cpp::PI::compute() and then pass in a pointer to a static method with the same signature in C++/CLI? If possible it would be good if I could pass in a member function. Do you think it would be possible to use an abstract virtual class instead of a function pointer? – Andy Jun 28 '11 at 15:18