Hi [IMG]images/smilies/2.gif[/IMG] ,
Sau một thời gian ngâm cứu khá lâu, hôm nay, Fox sẽ trình bày cho các bạn cách lập trình Plug-In trong C#. Hy vọng những bài
viết về đề tài này có thể giúp các bạn có cái nhìn rộng hơn về công nghệ lập trình.
Bài viết này xuất phát từ CỘNG ĐỒNG C VIỆT; các bạn có thể tùy ý sao chép, tham khảo; nhưng xin ghi rõ nguồn. CÁM ƠN
SEASON 1: BASIC PLUGGABLE APPLICATION
<font color="DarkGreen">I. GIỚI THIỆU
Plug-In cưng là ai [IMG]images/smilies/biggrin.png[/IMG] ? Plug-In là cách lập trình viên có thể mở rộng thêm một vài chức năng của một ứng dụng nào đó, mà
không cần phải re-compile lại toàn bộ ứng dụng; Nói cách khác lập trình viên có thể mở rộng ứng dụng mà không cần phải thao
tác bất cứ gì vào source code nguyên thủy.
Một số ứng dụng plug-in phổ biến hiện nay: Mozilla FireFox, Microsoft Visual Studio, SharpDevelop, ứng dụng của Fox nữa [IMG]images/smilies/biggrin.png[/IMG]…
II. Ý TƯỞNG
_ Thông qua bài viết của anh Zcoder87
Kỹ thuật lập trình Plugin trong lập trình VC++ . Do không rành lắm về C++, Fox bắt đầu tìm hiểu những bài viết về Plugin trên C#.
_ Bài viết này sẽ được chia làm nhiều kỳ (từ cách xây dựng một ứng dụng Plug-In đơn giản đến những cách mới phức tạp hơn -
Fox hy vọng sẽ có những bạn khác cùng tham gia chia sẽ [IMG]images/smilies/smile.png[/IMG] )
III. CODING
1. Lên kế hoạch
_ Xây dựng một thư viện BasicArrayProcessorSDK để làm nền plug-in cho ứng dụng.
_ Xây dựng một chuơng trình thao tác với một mảng các số nguyên.
_ Xây dựng 2 thư viện sẽ lf 2 default plug-ins để sắp xếp và tìm kiếm trên mảng số nguyên này.
2. Coding
_ Trong kỳ 1 này chúng ta sẽ xây dựng một nền plug-in theo style Interfaces, Assembly và AppDomain vì đây là cách được ưa
dùng nhất.
_ Hệ thống thư mục output dành cho ứng dụng
Mã:
+ Output Folder // thư mục output của ứng dụng.
+ PlugIns Folder // thư mục chứa các plug-in.
_ HostApplication.exe // file chạy chương trình chính.
_ PluggableLibrary.dll // thư viện tạo nền plug-in.
a. BasicArrayProcessorSDK
_ Mở C# IDE của bạn lên, tạo một Project Class Library, đặt tên tùy ý... (ở đây Fox đặt tên là BasicArrayProcessorSDK). Chỉnh
đường dẫn output theo hệ thống thư mục.
_ Chúng ta sẽ xây dựng 2 Interface, một Interface để sau này nếu ứng dụng của bạn cần một chức năng nào khác để thao tác
với một mảng số nguyên thì chỉ cần implements Interface này.
Mã:
using System; namespace BasicArrayProcessorSDK{ /// <summary> /// Interface to build an Array-Processor plug-in. /// </summary> public interface IArrayProcessor { /// <summary> /// Do what you want to an integer array. /// </summary> /// /// <param name="array">given integer array.</param> /// /// <returns>an integer array after having some fun.</returns> int[] Process(int[] array); /// <summary> /// Chooses a manager. /// </summary> /// /// <param name="host">an object to manage this plug-in.</param> void Initialize(IHostProcessor host); /// <summary> /// Releases all resources. /// </summary> void Dispose(); /// <summary> /// Gets or sets plug-in attributes. /// </summary> ArrayProcessorPlugInAttribute Attributes { get; set; } }}
_ Một Interface khác tượng trưng cho một host quản lý việc thực thi các plug-in(s).
Mã:
using System; namespace BasicArrayProcessorSDK{ /// <summary> /// Interface to build a plug-in manager. /// </summary> public interface IHostProcessor { /// <summary> /// Report to client a message. /// </summary> /// /// <param name="message">sending message.</param> void Report(string message); /// <summary> /// Releases all resources. /// </summary> void Dispose(); }}
Để có thể đảm bảo rằng host sẽ load đúng những plug-in(s) hợp lệ, chúng ta cần một Attribute để đánh dấu đồng thời cũng
quản lý những thông tin liên quan đến mỗi plug-in.
Mã:
using System; namespace BasicArrayProcessorSDK{ /// <summary> /// Object represents a plug-in's attributes. /// </summary> [AttributeUsage(AttributeTargets.Class)] public class ArrayProcessorPlugInAttribute : Attribute { #region - field(s) - /// <summary> /// Name of plug-in. /// </summary> string mName; /// <summary> /// publisher of plug-in. /// </summary> string mPublisher; /// <summary> /// Description of plug-in. /// </summary> string mDescription; /// <summary> /// Version of plug-in. /// </summary> string mVersion; #endregion #region - constructor(s) - /// <summary> /// Initialize a plug-in attribute with given name, publisher, description and version. /// </summary> /// /// <param name="name">given name.</param> /// <param name="publisher">given publisher.</param> /// <param name="description">given description.</param> /// <param name="version">given version.</param> public ArrayProcessorPlugInAttribute(string name, string publisher, string description, string version) { mName = name; mPublisher = publisher; mDescription = description; mVersion = version; } #endregion #region - property(ies) - /// <summary> /// Gets the name of plug-in; /// </summary> public string Name { get { return mName; } } /// <summary> /// Gets the publisher of plug-in; /// </summary> public string Publisher { get { return mPublisher; } } /// <summary> /// Gets the description of plug-in; /// </summary> public string Description { get { return mDescription; } } /// <summary> /// Gets the version of plug-in; /// </summary> public string Version { get { return mVersion; } } #endregion #region - method(s) - public override string ToString() { return "Name: " + mName + "; Publisher: " + mPublisher + "; Description: " + mDescription + "; Version: " + mVersion + "."; } #endregion }}
b. Xây dựng một ứng dụng đảm đưong trách nhiệm là một host quản lý plug-in.
_ Tạo mới một Project Windows Application trong cùng Solution (Fox đặt tên là HostViewer). Project này sẽ được Add
References đến thư viện nền plug-in là BasicArrayProcessorSDK.dll; Các bạn chọn thuộc tính Copy Local cho thư viện
BasicArrayProcessorSDK.dll thành false, vì chúng ta đã set output path cho thư viện này ở phần "a" rồi. Chúng ta cũng không
quên set output path cho ứng dụng host này theo hệ thống thư mục.
_ Chúng ta tạo một đối tượng implements từ IHostProcessor có nhiệm vụ quản lý các plug-in(s)
Mã:
using System;using System.Collections;using System.Collections.Generic;using System.IO;using System.Reflection;using System.Windows.Forms; using BasicArrayProcessorSDK; namespace HostViewer{ /// <summary> /// Object represents a plug-in manager. /// </summary> public class HostService : IHostProcessor { #region - field(s) - /// <summary> /// Initialize a collection to store all available plug-in. /// </summary> private Types.ArrayProcessorCollection mPlugInCollection = new Types.ArrayProcessorCollection(); /// <summary> /// Control to display message from this host. /// </summary> private Label mLabel; #endregion #region - constructor(s) - /// <summary> /// Initialize this host with a message-sender. /// </summary> /// /// <param name="control">host's message-sender.</param> public HostService(Label control) { mLabel = control; } #endregion #region - method(s) - #region - public member(s) - /// <summary> /// Finds all library(ies) in the plug-in folder. /// </summary> /// /// <param name="folderPath">folder contains all plug-in library.</param> public void FindPlugIns(string folderPath) { mPlugInCollection.Clear(); // reloads them all. // goes through all library-file(s) in the plug-in folder... DirectoryInfo container = new DirectoryInfo(folderPath); foreach (FileInfo file in container.GetFiles("*.dll")) this.LoadPlugIns(file.FullName); } /// <summary> /// Loads available plug-in(s) to this host. /// </summary> /// /// <param name="filePath">path of plug-in library.</param> private void LoadPlugIns(string filePath) { Assembly pluginAssembly = Assembly.LoadFrom(filePath); object[] pluginAttributes; // loops through all the Types found in the assembly... foreach (Type pluginType in pluginAssembly.GetTypes()) { pluginAttributes = pluginType.GetCustomAttributes(typeof(ArrayProcessorPlugInAttribute), true); // only looks for a public, non-abstract and having attribute type(s). if (pluginType.IsPublic && !pluginType.IsAbstract && pluginAttributes.Length != 0) { Type interfaceType = pluginType.GetInterface("BasicArrayProcessorSDK.IArrayProcessor", true); if (null != interfaceType) // gets the right interface-type. { Types.AvailableArrayProcessor plugin = new Types.AvailableArrayProcessor(); plugin.AssemblyPath = filePath; plugin.Instance = (IArrayProcessor)Activator.CreateInstance(pluginAssembly.GetType(pluginType.ToString())); plugin.Instance.Attributes = pluginAttributes[0] as ArrayProcessorPlugInAttribute; plugin.Instance.Initialize(this); mPlugInCollection.Add(plugin); // loaded done. plugin = null; // clean up... } interfaceType = null; // clean up... } } pluginAssembly = null; // clean up... } #endregion #region - implements IHostProcessor member(s) - public void Report(string message) { mLabel.Text = message; } public void Dispose() { foreach (Types.AvailableArrayProcessor plugin in mPlugInCollection) { plugin.Instance.Dispose(); plugin.Instance = null; } mPlugInCollection.Clear(); } #endregion #endregion #region - property(ies) - /// <summary> /// Gets the plug-in collection. /// </summary> public Types.ArrayProcessorCollection PlugIns { get { return mPlugInCollection; } } #endregion } namespace Types { /// <summary> /// Represents a plug-in collection. /// </summary> public class ArrayProcessorCollection : CollectionBase { #region - method(s) - /// <summary> /// Adds new plug-in to the collection. /// </summary> /// /// <param name="plugin">new plug-in.</param> public void Add(AvailableArrayProcessor plugin) { this.List.Add(plugin); } /// <summary> /// Removes a plug-in from collection. /// </summary> /// /// <param name="plugin">removing plug-in.</param> public void Remove(AvailableArrayProcessor plugin) { this.List.Remove(plugin); } /// <summary> /// Removes all plug-in from collection. /// </summary> public void Clear() { this.List.Clear(); } #endregion #region - property(ies) - /// <summary> /// Gets numbre of available plug-in(s). /// </summary> public int PlugInCounter { get { return base.List.Count; } } #endregion } /// <summary> /// Represents an available plug-in. /// </summary> public class AvailableArrayProcessor { #region - field(s) - /// <summary> /// Plug-in instance. /// </summary> private IArrayProcessor mInstance; /// <summary> /// Assembly-path of plug-in. /// </summary> private string mAssemblyPath; #endregion #region - constructor(s) - /// <summary> /// Initialize a default instance. /// </summary> public AvailableArrayProcessor() : this(null, "") { } /// <summary> /// Initialize a instance with given value. /// </summary> /// /// <param name="plugin">instance of a plug-in.</param> /// <param name="path">the path of plug-in assembly.</param> public AvailableArrayProcessor(IArrayProcessor plugin, string path) { mInstance = plugin; mAssemblyPath = path; } #endregion #region - property(ies) - /// <summary> /// Gets, sets the instance of plug-in. /// </summary> public IArrayProcessor Instance { get { return mInstance; } set { mInstance = value; } } /// <summary> /// Gets, sets the path of plug-in assembly. /// </summary> public string AssemblyPath { get { return mAssemblyPath; } set { mAssemblyPath = value; } } #endregion #region - method(s) - public override int GetHashCode() { int hashcode = 0; unchecked { hashcode += 1000034 * mInstance.GetHashCode(); } return hashcode; } public override bool Equals(object other) { if (null == other) return false; AvailableArrayProcessor anotherOne = other as AvailableArrayProcessor; return this.mInstance.Equals(anotherOne.mInstance); } public override string ToString() { return mInstance.ToString(); } #endregion } }}
Sau khi đã chuẩn bị nền móng đầy đủ, chúng ta sẽ xây dựng một FormMain để chạy chương trình.
Trong MainForm này chúng ta sẽ khai báo đối tượng HostService vừa tạo ở trên và sẽ cấp vùng nhớ new cho host này cùng với MainForm.
Mã:
.../// <summary>/// Adapter the host uses to talk back to this./// </summary>private HostService mHostAdapter;...
Khai báo một mảng số nguyên Random khoãng 10 phần từ để các plug-in có chỗ mà làm việc [IMG]images/smilies/biggrin.png[/IMG]
Mã:
.../// <summary>/// Array to process./// </summary>private int[] mArray = new int[10];...
Đăng ký sự kiện FormLoad cho MainForm để load các plug-in thao tác với mảng (các plug-in sẽ được load dưới dạng các button;
khi click các button sẽ làm việc với mãng ngẫu nhiên đã tạo với chức năng riêng của mỗi plug-in).
Mã:
/// <summary>/// Loads all available plug-in(s) and disguises them as a <c>Button</c>./// </summary>private void MainFormOnLoad(object sender, EventArgs e){ mHostAdapter.FindPlugIns(Application.StartupPath + @"\PlugIns"); foreach (Types.AvailableArrayProcessor plugin in mHostAdapter.PlugIns) { // disguising... Button button = new Button(); button.Text = plugin.Instance.Attributes.Name; button.Tag = plugin; button.Padding = new Padding(6, 6, 6, 6); button.Width = pluginsFlowLayoutPanel.Width - 10; button.Height = pluginsFlowLayoutPanel.Height / 5; button.TextAlign = ContentAlignment.MiddleCenter; button.Click += new EventHandler(ArrayProcessorsPicking); // registers a click event... this.mainToolTip.SetToolTip(button, plugin.Instance.Attributes.Description); pluginsFlowLayoutPanel.Controls.Add(button); } mHostAdapter.Report(mHostAdapter.PlugIns.Count + " Plug-In(s) Loaded."); // reports to clients...}
Sự kiện click trên các plug-in button(s)
Mã:
/// <summary>/// Clicking on a plug-in button will process the array by the way the plug-in is for./// </summary>private void ArrayProcessorsPicking(object sender, EventArgs e){ resultFlowLayoutPanel.Controls.Clear(); Label elementResult = null; Types.AvailableArrayProcessor processor = ((Button)sender).Tag as Types.AvailableArrayProcessor; int[] result = processor.Instance.Process(mArray); // processing... for (int i = 0; i < result.Length; i++) { elementResult = new Label(); resultFlowLayoutPanel.Controls.Add(elementResult); this.StyleLabel(elementResult, Color.Red, result[i].ToString()); } processor = null; // release plugin...}
Chúng ta cũng nên đăng ký sự kiện FormClosing để có thể release những Resource có thể làm leak memory... [IMG]images/smilies/biggrin.png[/IMG]
Mã:
/// <summary>/// When application is about to close, release the host's resources./// </summary>private void MainFormOnClosing(object sender, FormClosingEventArgs e){ mHostAdapter.Dispose();}
c. Xây dựng các plug-in(s)
_ Để xây dựng một plug-in chúng ta chỉ cần tạo từng Project Class Library, Add Reference đến BasicArrayProcessorSDK.dll
(BasicArrayProcessorSDK.dll này sẽ được set CopyLocal = false). Nhớ set ouput path cho plug-in theo "Hệ thống thư mục" nhé [IMG]images/smilies/smile.png[/IMG]
_ Mỗi một đối tượng được tạo trong Plug-In Library chỉ cần implements đến IArrayProcessor là có thể trở thành một Plug-In để
Host có thể load lên MainForm (dưới dạng một button).
Chúng ta sẽ trở lại thời "cấu trúc dữ liệu và giải thuật" để xây dựng các plug-in sắp xếp và tìm kiếm nhé [IMG]images/smilies/biggrin.png[/IMG] .
+ Plug-in sắp xếp
Ở đây Fox dùng thuật toán InsertionSort [IMG]images/smilies/biggrin.png[/IMG]
Mã:
using System; using BasicArrayProcessorSDK; namespace ArraySorting{ [ArrayProcessorPlugIn("Ascending Insertion Sort Plug-In", "O'Wicked Fox", "Sorting an integer array with ascending insertion-sort style", "1.0.0")] // Khai báo Attribute để Host nhận ra các thông tin của Plug-in, các plug-in xài giấy tờ giả là không được load đâu :D public class InsertionSortAscending : IArrayProcessor { private IHostProcessor mHostViewer; private ArrayProcessorPlugInAttribute mAttributes; #region IArrayProcessor Members public int[] Process(int[] array) { // Sắp xếp InsertionSort theo thứ tự tăng dần int k, postition, temp; for (k = 1; k < array.Length; k++) { temp = array[k]; postition = k; while ((postition > 0) && (array[postition - 1] > temp)) { array[postition] = array[postition - 1]; postition--; } array[postition] = temp; } mHostViewer.Report("Ascending Array Sorted."); return ArrayUtils.CopyArray(array); } public void Initialize(IHostProcessor host) { mHostViewer = host; } public void Dispose() { mAttributes = null; } public ArrayProcessorPlugInAttribute Attributes { get { return mAttributes; } set { mAttributes = value; } } #endregion }
+ Để tìm kiếm, Fox sẽ dùng cách kiểm tra trong mãng ngẫu nhiên có số nào là số nguyên tố hay không...
Mã:
using System;using System.Collections.Generic; using BasicArrayProcessorSDK; namespace PrimeNumberFinder{ [ArrayProcessorPlugIn("Prime-Number Finder Plug-In", "O'Wicked Fox", "Looking through a given integer array and collects all element(s) which has value a prime-number.", "1.0.0")] public class PrimeNumberProcessor : IArrayProcessor { #region - field(s) - IHostProcessor mHostSideAdapter; private ArrayProcessorPlugInAttribute mAttributes; #endregion #region IArrayProcessor Members public int[] Process(int[] array) { List<int> storage = new List<int>(); bool flag; for (int i = 0; i < array.Length; i++) { flag = this.CheckPrimeNumber(array[i]); if (flag) storage.Add(array[i]); } int count = storage.Count; if (count == 0) { mHostSideAdapter.Report("No Prime Number Found."); return array; } else { mHostSideAdapter.Report(count.ToString() + " Prime-Number(s) Found."); int[] result = new int[count]; storage.CopyTo(result); return result; } } public void Initialize(IHostProcessor host) { mHostSideAdapter = host; } public void Dispose() { mAttributes = null; } public ArrayProcessorPlugInAttribute Attributes { get { return mAttributes; } set { mAttributes = value; } } #endregion private bool CheckPrimeNumber(int input) { if (input == 2) return true; bool result = false; int count = 0; for (int j = 1; j <= input; j++) { if (input % j == 0) { count++; if (count > 2) // số có nhìu hơn 2 thừa số nguyên tố thì không phải là số nguyên tố { result = false; break; } else result = true; } } return result; } }}
Nhấn Build nào [IMG]images/smilies/biggrin.png[/IMG] , sau khi build, các bạn mở thư mục output ra, nếu nó có cấu trúc sau là đúng chuẩn [IMG]images/smilies/smile.png[/IMG]
Vậy là xong rồi [IMG]images/smilies/biggrin.png[/IMG] . Nếu sau này các bạn muốn tạo một vài plug-in để chương trình có những tính năng mới, chỉ cần thực hiện
theo cách tạo plug-in của Fox, biên dịch plug-in ra file dll; copy vào thư mục "Plugins" của ứng dụng; restart ứng dụng thì chúng ta sẽ có một nút plug-in mới với chức năng do chúng ta tự tạo. [IMG]images/smilies/smile.png[/IMG]
IV. KẾT
Kỳ 1 đã hết, Để đảm bảo các bạn có thể hiểu được chút ít về bài viết của Fox, Fox sẽ yêu cầu một bài tập sau cho các bạn [IMG]images/smilies/biggrin.png[/IMG] :
Viết một plug-in để sắp xếp các phần tử trong mảng số tự nhiên theo thuật toán BubbleSort hoặc MergeSort.
Fox có yêu cầu thế này, nếu các bạn muốn viết bài hồi đáp sau bài hướng dẫn của Fox (Đặt câu hỏi hoặc góp ý kiến thì Fox rất hoan nghênh [IMG]images/smilies/smile.png[/IMG] ), xin các bạn hãy đính kèm theo một file .ZIP bài tập đã làm, hoặc gửi mail tới địa chỉ mail owickedfox@gmail.com.
Mời các bạn download sourcecode cho kỳ 1 và đón xem kỳ 2 về lập trình plug-in trên một ứng dụng MDI [IMG]images/smilies/smile.png[/IMG]
Tạm biệt [IMG]images/smilies/smile.png[/IMG]
V. VERSION
1. Ngày 06 tháng 04
_ Viết bài kỳ 1.
2. Ngày 07 tháng 04
_ Fix hàm LoadPlugIns(), check nếu mảng attribute lấy được có chiều dài khác = 0 khi đó plug-in hợp lệ mới cho load. (check = null chương trình sẽ báo lỗi Null Exception).</font>
View more random threads:
Ngọc Bích xanh bản chất thuộc dòng đá đá hoa (jade). Và cẩm thạch là tên gọi chung của ngọc bích. Vì thực chất chúng thuộc dòng đá đa khoáng được hình thành từ chất Silicat dưới dạng dioxy. Ngọc bích...
Chia sẻ Vòng tay ngọc bích xanh là...