2012-08-10 79 views
-1

在我的Visual Studio解決方案中,我有用C#實現的UI和用本地C++實現的一些代碼。如何使用C#BackgroundWorker報告本地C++代碼的進度?

我使用BackgroundWorker class來實現執行長操作的報告進度。

如何使用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"); 
     } 
    } 
} 
+1

只是一個抽象的概念:鑑於BackgroundWorker的只是一個單獨的線程,可以在調用C#聲明不安全的代碼段;分開一個新的線程;並聲明一個函數指針來報告進度。總之,你有沒有在線索和代表的抽象層次之下進行編程? – 2012-08-10 21:58:07

+0

寫它的本地代碼有什麼意義?您使用託管對象,因此您可以使用託管代碼編寫這些內容。如果你想用native C++寫這樣的東西,那麼你需要本地代碼 – 2012-08-11 00:01:14

+0

@Even Dark首先你的評論並不涉及到問題。但不管怎麼說。在C#中編寫用戶界面更加方便,C++中的計算密集型代碼,我甚至不會說你沒有選擇的情況,因爲你沒有啓動項目,只需要一個代碼。而當你有C++和C#代碼時,你可能需要在它們之間進行一些溝通 - 在我的情況下,我需要報告進度。 – sergtk 2012-08-11 14:24:13

回答

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

以下鏈接可能是有用的:

1

在你的C#代碼,聲明你的本地C++方法與DllImport屬性,並從 BackgroundWorker.ProgressChanged處理程序調用此方法。

免責聲明:我沒有測試過任何這些代碼,這可能不是最好的方法,但至少在理論上我認爲這會起作用。希望這裏更有經驗的成員之一可以驗證這是否確實是正確的。

這假設你從C#啓動後臺工作,並且你想在C#中使用ProgressChanged事件(我認爲這是你的UI在C#中的情況)。

您仍然可以使用C#中的BackgroundWorker,但只是把它叫使用我上面提到的的DllImport你的本地方法。您還可以修改方法的簽名,以獲取與ReportProgress的簽名匹配的函數指針,然後從您的本機代碼調用該代理。

MSDN在Marshalling delegates and function pointers上有一些文章(儘管這些例子都使用C++/CLI)。您可能還想查看DLLImportMarshalAs屬性以及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); 
} 

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

希望這取得了一定的意義(希望這是正確的,太!)

+0

它如何允許從C++代碼調用worker.ReportProgress? – sergtk 2012-08-10 21:42:59

+0

我的不好,我誤解了這些問題。我以爲你試圖從C#報告進度回到你的本地C++代碼。儘管我認爲我有一個想法,但我還沒有做過多的C++互操作。我會盡快給您回覆。 – 2012-08-10 23:41:38

+1

我編輯了我認爲是更正確的方法的答案。 – 2012-08-11 01:08:53

相關問題