當前位置:
首頁 > 知識 > 關於C插件編程和插件宿主數據傳遞的一些方法

關於C插件編程和插件宿主數據傳遞的一些方法

最近做了一個C#的插件式編程的項目,涉及到了在winform下插件和宿主的數據傳遞。希望實現插件主動向宿主傳遞數據而不是宿主通過運行一個方法去索要數據。

具體實現是Form1為宿主,在textbox里輸入字元串可以傳到插件里。

另外是可以在插件里通過滑鼠繪圖,單擊確定將圖傳遞到宿主。

考慮到插件編程,必然涉及到介面,這裡我定義介面

[csharp] view plain copy

  1. namespace

    interclass
  2. {
  3. public

    delegate

    void

    ChangeDataHandler(List<List<Point>> ptlists); //定義委託
  4. public

    interface

    inter
  5. {
  6. DataTransfer DataTransfer {

    get

    ;

    set

    ; }
  7. string

    WhoAmI(DataTransfer DT); //傳遞DataTransfer進入插件同時獲得插件的名字
  8. void

    Action(

    object

    sender, EventArgs e); //插件程序入口
  9. List<List<Point>> PointLists {

    get

    ; }
  10. event

    ChangeDataHandler ChangeData; //定義事件
  11. }
  12. public

    class

    DataTransfer
  13. {
  14. private

    string

    testdata;
  15. public

    string

    Testdata {

    get

    => testdata;

    set

    => testdata = value; }
  16. }
  17. }

參考跨線程和跨窗體傳遞數據的相關資料,通過委託可以實現插件向宿主主動傳遞。

在宿主插件中添加動態菜單,綁定單擊事件到Action,以開始插件的運行。

下面是宿主窗體載入插件的代碼,包含動態菜單的操作。

[csharp] view plain copy

  1. private

    List<inter> _plugIns =

    new

    List<inter>(); //插件列表

[csharp] view plain copy

  1. int

    _countPlugAdded = 0; //插件成功載入計數

[csharp] view plain copy

  1. int

    _countPlugAddError = 0; //插件載入失敗計數
  2. DataTransfer MainDT; //數據傳遞的實例

[csharp] view plain copy

[csharp] view plain copy

[csharp] view plain copy

  1. private

    void

    Form1_Load(

    object

    sender, EventArgs e)
  2. {
  3. List<

    string

    > list =

    new

    List<

    string

    >();

  4. MainDT =

    new

    DataTransfer();
  5. //讀取插件
  6. string

    path = System.IO.Path.Combine(System.Environment.CurrentDirectory + @"dlls");
  7. DirectoryInfo dir =

    new

    DirectoryInfo(path);
  8. FileInfo[] fil = dir.GetFiles();
  9. foreach

    (FileInfo f

    in

    fil)
  10. {
  11. if

    (f.Extension.Equals(".dll"))
  12. {
  13. list.Add(f.FullName);
  14. }
  15. }
  16. //載入插件
  17. if

    (list.Count != 0)
  18. {
  19. foreach

    (

    string

    tp

    in

    list)

  20. {
  21. try

  22. {
  23. inter tempI;
  24. Assembly asd = Assembly.LoadFile(tp);
  25. Type[] t = asd.GetTypes();
  26. foreach

    (Type tin

    in

    t)
  27. {
  28. if

    (tin.GetInterface("inter") !=

    null

    )
  29. {
  30. tempI = (inter)Activator.CreateInstance(asd.GetType(tin.FullName));
  31. _plugIns.Add(tempI);
  32. tempI.ChangeData +=

    new

    ChangeDataHandler(myDataChanged);
  33. //創建菜單
  34. ToolStripMenuItem newItem =

    new

    ToolStripMenuItem();
  35. newItem.Text = tempI.WhoAmI(MainDT);
  36. newItem.Click += tempI.Action;
  37. ((ToolStripMenuItem)(mainMenu.Items[1])).DropDownItems.Add(newItem);
  38. //計數累加
  39. _countPlugAdded++;
  40. //break;
  41. }
  42. }
  43. }
  44. catch

    (Exception)
  45. {
  46. _countPlugAddError++;
  47. }
  48. }
  49. }
  50. else

  51. {
  52. ToolStripMenuItem newItem =

    new

    ToolStripMenuItem();

  53. newItem.Text = "No Plug-In";
  54. newItem.Enabled =

    false

    ;
  55. ((ToolStripMenuItem)(mainMenu.Items[1])).DropDownItems.Add(newItem);
  56. }
  57. MessageBox.Show(_countPlugAdded.ToString() +
  58. "個插件成功載入," + _countPlugAddError.ToString() +
  59. "個插件載入失敗。");
  60. }

然後在插件里實現介面。首先說接收宿主數據的插件。

宿主將數據傳遞到插件

大致思路是傳遞DataTransfer實例進入插件(在載入插件並實例化的時候已經傳遞進去了),然後在插件里通過定時器掃描DataTransfer實例。當然也可以通過事件進行,我這裡偷懶使用了定時器。
因為介面定義並沒有和Form有關聯,所以在Action,也就是插件程序的入口點,要實例化子窗體顯示出來。

[csharp] view plain copy

  1. namespace

    plugin1
  2. {
  3. public

    class

    Class1 : inter
  4. {
  5. private

    DataTransfer dataTransfer;
  6. public

    DataTransfer DataTransfer {

    get

    => dataTransfer;

    set

    => dataTransfer = value; }

  7. public

    List<List<Point>> PointLists =>

    throw

    new

    NotImplementedException();//因為這個插件不向宿主傳遞數據所以不實現這個。
  8. public

    event

    ChangeDataHandler ChangeData;//因為這個插件不會產生向宿主傳遞數據所以不實現這個。
  9. public

    void

    Action(

    object

    sender, EventArgs e)
  10. {
  11. MessageBox.Show("Plugin1");//測試用,表示在Class1裡面,Action已經開始運行。
  12. FormT formT =

    new

    FormT();//實例化窗體
  13. formT.SetDT(dataTransfer);//SetDT是在FormT裡面定義的方法,用於把DataTransfer傳遞進FormT裡面。
  14. formT.Show();//顯示插件的子窗體
  15. }
  16. public

    string

    WhoAmI(DataTransfer DT)
  17. {
  18. dataTransfer =

    new

    DataTransfer();
  19. dataTransfer = DT;//把宿主的DataTransfer傳入插件
  20. return

    "plugin1";//返回自己的名字
  21. }
  22. }
  23. }

這樣在宿主里單機菜單里的plugin1就可以運行這個插件的Action方法並顯示子窗體。
在插件的窗體裡面寫:

[csharp] view plain copy

  1. namespace

    plugin1
  2. {
  3. public

    partial

    class

    FormT : Form
  4. {
  5. private

    int

    _counter = 0;
  6. DataTransfer _dataTransfer;//傳入數據用
  7. public

    void

    SetDT(DataTransfer inputDT) => _dataTransfer = inputDT;
  8. public

    FormT()
  9. {
  10. InitializeComponent();
  11. }
  12. private

    void

    timer1_Tick(

    object

    sender, EventArgs e)//通過定時器掃描DataTransfer實現宿主數據的同步顯示。
  13. {
  14. this

    .Text = "這是一個插件創建的窗體,由插件控制。" + _counter.ToString();
  15. _counter++;
  16. Monitor.Enter(_dataTransfer);
  17. label1.Text = _dataTransfer.Testdata;
  18. Monitor.Pulse(_dataTransfer);
  19. Monitor.Exit(_dataTransfer);
  20. }
  21. private

    void

    FormT_Load(

    object

    sender, EventArgs e)
  22. {
  23. this

    .Text = "這是一個插件創建的窗體,由插件控制。" + _counter.ToString();
  24. timer1.Enabled =

    true

    ;
  25. }
  26. private

    void

    button1_Click(

    object

    sender, EventArgs e)
  27. {
  28. MessageBox.Show(_dataTransfer.Testdata,"我是插件創建的窗體");//通過點擊按鈕實現數據的傳入。
  29. }
  30. }
  31. }

[csharp] view plain copy

  1. 這裡我嘗試了兩種方法, 一個是通過定時器定時查看,另外是藉助button_click實現對DataTransfer掃描。

插件主動傳遞數據到宿主

宿主運行介面定義的方法確實可以實現讀取插件的信息,但是若插件需要主動傳遞數據到宿主這樣便不好用。假設現在需要在插件里通過滑鼠繪圖,單擊按鈕將這個圖傳遞到宿主里進行顯示。
需要在宿主里定義一個成員

[csharp] view plain copy

  1. List<List<Point>> _myPointLists =

    new

    List<List<Point>>();

記錄滑鼠的位置並依次連線。若抬起滑鼠則另外新建一個表。實現在插件里的繪圖的保存。
插件里實現介面:

[csharp] view plain copy

  1. namespace

    Plugin3
  2. {
  3. public

    class

    Class1 : inter
  4. {
  5. private

    DataTransfer myDT;
  6. public

    DataTransfer DataTransfer {

    get

    => myDT;

    set

    => myDT = value; }
  7. private

    List<List<Point>> _pointLists;
  8. public

    List<List<Point>> PointLists {

    get

    => _pointLists; }
  9. public

    event

    ChangeDataHandler ChangeData;
  10. public

    void

    Action(

    object

    sender, EventArgs e)
  11. {
  12. FormE formE =

    new

    FormE();
  13. formE.ChangePic +=

    new

    ChangePicHandler(picChanged);
  14. formE.Show();
  15. }
  16. public

    string

    WhoAmI(DataTransfer DT)
  17. {
  18. _pointLists =

    new

    List<List<Point>>();
  19. myDT = DT;
  20. return

    "JustForFun";
  21. }
  22. private

    void

    picChanged(List<List<Point>> ptlists)
  23. {
  24. _pointLists.Clear();
  25. foreach

    (List<Point> pl

    in

    ptlists)
  26. {
  27. _pointLists.Add(

    new

    List<Point>());
  28. foreach

    (Point p

    in

    pl)
  29. {
  30. _pointLists[_pointLists.Count-1].Add(

    new

    Point());
  31. _pointLists[_pointLists.Count - 1][_pointLists[_pointLists.Count - 1].Count-1] = p;
  32. }
  33. }
  34. ChangeData?.Invoke(_pointLists);//執行委託實例 將數據從Class1傳遞到宿主
  35. }
  36. }
  37. }

和之前一樣,同樣使用Action作為入口,在Action中將formE裡面的ChangePic事件加上picChanged事件處理。

因為我們是在formE裡面繪圖,而不是在Class1中,所以我們要定義另外一個事件,也就是ChangePic來將數據從窗體傳遞到Class1中。這個和跨窗體傳遞數據的方法相同。

最後執行ChangeData委託實例。介面定義了ChangeData事件,在上面的宿主載入插件的代碼中,通過

[csharp] view plain copy

  1. tempI = (inter)Activator.CreateInstance(asd.GetType(tin.FullName));
  2. _plugIns.Add(tempI);
  3. tempI.ChangeData +=

    new

    ChangeDataHandler(myDataChanged);

實現了ChangeData事件和myDataChanged的事件處理。其中myDataChanged處理方法由宿主定義,而事件的發生由插件定義,實現了宿主和插件的分離。

在formE中,除了繪圖的相關方法外,在單擊「確定」按鈕中產生了ChangePic事件

[csharp] view plain copy

  1. namespace

    Plugin3
  2. {
  3. public

    delegate

    void

    ChangePicHandler(List<List<Point>> ptlist); //定義委託
  4. public

    partial

    class

    FormE : Form
  5. {
  6. bool

    _mouseLeftDown =

    false

    ;
  7. List<List<Point>> _drawPoints;
  8. int

    _drawCount = 0;
  9. int

    pointNumber = 0;
  10. Point myMousePosition;
  11. public

    event

    ChangePicHandler ChangePic; //定義事件
  12. public

    FormE()
  13. {
  14. InitializeComponent();
  15. }
  16. public

    void

    SetForm()
  17. {
  18. }
  19. private

    void

    FormE_Load(

    object

    sender, EventArgs e)
  20. {
  21. }
  22. private

    void

    FormE_MouseDown(

    object

    sender, MouseEventArgs e)
  23. {
  24. }
  25. private

    void

    FormE_MouseUp(

    object

    sender, MouseEventArgs e)
  26. {
  27. }
  28. private

    void

    FormE_MouseMove(

    object

    sender, MouseEventArgs e)
  29. {
  30. }
  31. private

    void

    FormE_Paint(

    object

    sender, PaintEventArgs e)
  32. {
  33. }
  34. private

    void

    FormE_FormClosed(

    object

    sender, FormClosedEventArgs e)
  35. {
  36. }
  37. //Clear
  38. private

    void

    button1_Click(

    object

    sender, EventArgs e)
  39. {
  40. _drawPoints.Clear();
  41. GC.Collect();
  42. _drawCount = 0;
  43. pointNumber = 0;
  44. Text = "點數:" + pointNumber.ToString();
  45. Invalidate();
  46. }
  47. private

    void

    button2_Click(

    object

    sender, EventArgs e)
  48. {
  49. ChangePic?.Invoke(_drawPoints);//執行委託實例 將_drawPoints傳遞到上一級的Class1中
  50. }
  51. }
  52. }

FormE中的_drawPoints即是隨滑鼠移動不斷記錄當前滑鼠位置產生的列表。用於記錄在插件窗體裡面的繪圖。

由此便通過兩個事件將數據主動傳遞到了宿主中。這個過程里,只有宿主通過介面定義的ChangeData事件和宿主內實現的這個事件的處理方法是由宿主定義,至於DataChanged事件的發生和其他操作由插件定義,這也就是前面第一個只接收宿主數據不用實現這個事件的原因。宿主可以不關心插件是否要向宿主傳遞數據,何時傳遞數據。

關於C插件編程和插件宿主數據傳遞的一些方法

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

程序員如何快速搭建個性化主頁
spring+mybatis 實現多數據源切換

TAG:程序員小新人學習 |