Google prettify簡單又好用,只是在Blogger內貼程式碼時,還要手動轉到HTML頁籤在落落長的HTML元素裡找到正確排版位置、透過HTML Escape跳脫字元、自己標記<pre class="prettyprint">...</pre>...有點麻煩
2017年9月25日 星期一
2017年9月21日 星期四
[VS Code][C/C++] 基本開發環境設定
這次輪到在VS Code上開發C/C++程式,被宇宙最強IDE Visual Studio寵壞的關係,一開始搞不清楚到底在設定什麼東西,最後終於搞懂幾件關於VS Code的機理:
2017年9月20日 星期三
[VS Code][Bash Shell Script] Debug環境設定
Shell Script也是可以Debug的,下斷點、看變數內容...都可以,VS Code配來寫Shell Script也是很下飯,記錄一下環境設定
2017年9月18日 星期一
[LINUX] 跨網域連接NFS伺服器 - Part2 - 透過ssh tunnel連接NFS
透過Part1,example.ddns.net:22已經開通,通過這個連接點再配合Virtual Server連接到內網伺服器進行ssh登入,可以開始在異地登入ssh替伺服器做設定
2017年9月17日 星期日
[LINUX] 垮網域連接NFS伺服器 - Part1 - 設定DDNS與Virtual Server
目前有某台CentOS主機放在老家作為個人小伺服,硬碟裡面收藏了無數家珍(嘿嘿),只是就算開了NFS服務(Network File Systme)也只能在老家的區域網路內使用,回到租屋處該怎麼透過網際網路連回去呢?
2017年8月2日 星期三
[.NET] 流(Stream)的改變輸出入導向
衍生(Dervied)/聚合(Composited)流(Stream)的類別,具有流的輸出入能力,透過這個基礎介面,我們可以用來銜接不同類型的輸出入裝置,不需要實作任何轉換細節,對呼叫端而言只管將字串資料輸出入,可以把流看成某種程度的抽象化硬體
2017年7月30日 星期日
[.NET][Python] 集合處理筆記
Wiki:
MapReduce is a programming model and an associated implementation for processing and generating big data sets with a parallel, distributed algorithm on a cluster.
集合之間的轉換,與元素操作是資料處理的日常,第一次認識MapReduce概念其實是從Python來,但發現.NET也有等價的處理(從Stackoverflow發現,搜尋"Python Map Equipvalent in c#"):
2017年7月26日 星期三
2017年7月22日 星期六
[.NET] 流(Stream)的理解心得
Wiki說得好:
「流」是一連串從I/O設備讀寫的字符(Byte)。
流(Stream)是.NET中非常基礎的型態,存取方式是很單純的位元組依序寫入/讀出(Byte by Byte Writing/Reading),會有一個內藏Counter紀錄住目前存取位置(Position,有點類似座標),存取時都是從目前位置開始存取.
若將整個讀取完畢的Stream攤平開來,也是可以想像成時域信號圖,橫軸是位置,只是要確定好座標原點(Origin)在何處
跟位元組陣列有著類似的概念,只是位元組陣列可以隨機存取:給定INDEX存取,座標原點就是第一個元素,只需一個存取動作:給定INDEX後返回該位置的資料;若要做類似隨機存取操作,Stream必須先透過Seek改變目前位置,才能開始存取,因此需要兩個存取動作:改變目前位置、存取資料
Stream存的個別元素是位元組,也就是0-255之間的一個數值,可以說是資訊系統內超級底層的資料型態之一,也就是說Stream是一種極為基礎的資料儲存結構;任何的高階資料型態(物件、字串)都能轉換成位元組形式去表達(某種映射):
- 物件-位元組轉換(物件與位元組之間的映射)
- 序列化與反序列化(Serialization/Deserialization)
- 而反序列化/序列化則是互為反函式(Inverse)
- 字元-位元組轉換(字元與位元組之間的映射)
- 按字碼表Encoding/Decoding
- 線性映射?
後續應用就視情況看是要透過反序列化還是Decoding,轉換成更高階的資料表達型態
.NET的類別規劃上,可以在相關的硬體抽象物件找到Stream的身影,好比說以下衍生自Stream的型別:
- FileStream (檔案輸出入
- MemoryStream (我當它是模擬I/O裝置,基礎資料流儲存在主記憶內
- NetworkStream(透過網路介面輸出入
- PipeStream(程序間溝通應用
參考:這系列對於Stream介紹蠻不錯
2017年7月20日 星期四
[無聊的心得] Overhead
Overhead,名詞
https://en.wikipedia.org/wiki/Overhead_(computing)
中文叫做經常開支,為了達成某種目的所必須付出的與目標無關的代價
講到Overhead我就想到酬載(Payload),用貨車載貨來聯想:想像一台20噸重的卡車載了一顆20克的蘋果, 此時20克的蘋果就是酬載(Payload),如果送到目的地可以賺10塊錢,而推動20噸車體到目的地所需的油料消耗是3000塊,屬於經常開支(Overhead),恩...好一門美麗的賠錢生意。
思緒回到資訊世界,標頭會帶來Overhead,以XML格式資料為例,角括號裡面的是屬性(Property)名稱,而屬性間的包覆關係是階層格式描述,無論是角括號本身、頭尾的包覆這些都是經常開支(Overhead),因為其實你只在乎<></>之間的值(Value)資訊
看看以下酬載少於經常開支:
那麼傳輸時所花費的時間、硬體損耗都是花在經常開支而不是酬載(花在傳輸PropertyName)大部分資源都花在傳輸Header,沒意義的Overhead導致效率不彰,就要開始動腦筋改善這問題嚕
比方說每次傳輸時格式都是固定,我能不能不要傳Header,以隱含的方式表達資訊?還是縮短PropertyName?好像都不錯
https://en.wikipedia.org/wiki/Overhead_(computing)
中文叫做經常開支,為了達成某種目的所必須付出的與目標無關的代價
講到Overhead我就想到酬載(Payload),用貨車載貨來聯想:想像一台20噸重的卡車載了一顆20克的蘋果, 此時20克的蘋果就是酬載(Payload),如果送到目的地可以賺10塊錢,而推動20噸車體到目的地所需的油料消耗是3000塊,屬於經常開支(Overhead),恩...好一門美麗的賠錢生意。
思緒回到資訊世界,標頭會帶來Overhead,以XML格式資料為例,角括號裡面的是屬性(Property)名稱,而屬性間的包覆關係是階層格式描述,無論是角括號本身、頭尾的包覆這些都是經常開支(Overhead),因為其實你只在乎<></>之間的值(Value)資訊
看看以下酬載少於經常開支:
<themotherfuckerextremelongpropertyname> 1 </themotherfuckerextremelongpropertyname>
那麼傳輸時所花費的時間、硬體損耗都是花在經常開支而不是酬載(花在傳輸PropertyName)大部分資源都花在傳輸Header,沒意義的Overhead導致效率不彰,就要開始動腦筋改善這問題嚕
比方說每次傳輸時格式都是固定,我能不能不要傳Header,以隱含的方式表達資訊?還是縮短PropertyName?好像都不錯
[.NET] 執行序同步慣用Pattern心得 - I
MSDN說:
需要使用者互動的軟體必須儘快回應使用者的活動,以便提供豐富的使用者經驗。不過,同時它也必須執行需要的計算以儘快提供使用者資料。在個人目前的工作領域,使用者體驗並非放在第一(甚至是最後一名),大部分時間都花在Coding工作處理常式(Routines),主要實現概念為狀態機(State-Machine)與多工處理(Multi-Task),在Main Thread執行
常式內的多工工作是純粹的商業邏輯,少有牽涉I/O存取,處理頻率穩定近乎週期性,但來源資訊難免要透過外部裝置取得,如網路介面、PCI裝置、使用者輸入...等,幾乎是慢速、頻率較低的資料源,為了避免影響Main Thread的多工輪詢,此時就可考慮用其他Thread分擔低速工作。
整理三個與狀態機搭配的常用分緒招,共通點是在Main Thread輪詢某資料狀態,該輪詢函式必須不能阻塞(Blocking)住Main Thread,否則會影響到其他多工作業
(沒實測過這些方法的具體效能,比較在乎應用上的設計模式(Pattern)是否容易嵌入既有架構)
- Task
- 發動:
- 將要執行的Method裝載到Task物件中後,呼喚Start()開始執行緒作業
- 也可以利用工廠模式,呼叫共用方法Task.Run()開始執行緒作業,同步狀態透過傳回的Task實例來操作
- 同步:
- Worker Thread輪詢Task.IsComplete至狀態為True
- 心得:
- Task封裝了Thread與若干執行緒同步元件(如Mutex),因此包含了執行緒操作與同步操作
- 據說是在執行緒池內作業(待確認
- ThreadPool
- 發動:
- 將要執行的Method透過QueueUserWorkItem傳入執行緒池
- 同步:
- 無法得知方法最終被放到哪個具體執行緒,也沒封裝同步操作方法,所以必須在Method裡自行設計同步物件來辨識是否方法執行完成
- 個人慣用Concurrent執行緒安全元件,如ConcurrentQueue、ConcurrentDictionary傳遞資訊到Worker Thread
- Worker Thread輪詢Concurrent元件
- 心得:
- 最簡單易用赤裸的異步編成模式,不含任何同步方法的純粹執行緒池操作最?似乎懂了些什麼了?
- MulticastDelegate
- 發動:
- 將要執行的Method裝載到Delegate中
- 呼叫該Delegate的BeginInvoke(),必須帶入回呼方法(Callback)
- 必須呼叫EndInvoke()作為確認該執行緒執行完成的辨識?
- 同步:
- 輪詢呼叫該Delegate時傳回的IAsyncResult.IsCompleted
- 或是在Main Thread呼叫EndInvoke()(但是可能阻塞)
- 心得:
- 與Task一樣,封裝了執行緒操作與同步操作,只是設計模式上透過IAsyncResult或EndInvoke達成同步
- 據說是在執行緒池內作業(待確認
class Program { static void Main(string[] args) { int state = 0; Action __delegate = null; Task __task = null; IAsyncResult handle = null; ConcurrentQueueexchange = new ConcurrentQueue (); int outValue = 0; //the main state machine Program.method(); while (state != 500) { switch (state) { case 0: // use Task __task = new Task(new Action(Program.method)); __task.Start(); state +=10; break; case 10: // wait until task done if (__task.IsCompleted) state +=10; break; case 20: // use thread pool ThreadPool.QueueUserWorkItem((object foo)=>{ Program.method(); exchange.Enqueue(0); // enqueue some meaningfll result },null); state+=10; break; case 30: //wait until method had been scheduled and executed if (exchange.TryDequeue(out outValue)) state +=10; break; case 40: // use delegate/IAsyncResult __delegate = new Action(method); handle = __delegate.BeginInvoke((IAsyncResult ar)=> { method(); __delegate.EndInvoke(ar); // you had to call this to release ar },null); break; case 50: // wait until method had been executed if (handle.IsCompleted) state = 500; break; default: break; } Thread.Sleep(0);// yield to other thread to run }//while } static void method() { //reflect the current thread Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString()); } }
後記1:MSDN的異步編程模式概述,對於非同步作業可有概略且完整的認識,其中APM(
Asynchronous Programming Model,就是Delegate/IAyncResult)已經漸漸式微變成不太推薦的Pattern
2017年7月17日 星期一
[.NET] REGEX 利用 Group 捕捉(Capture)重複字串(Repetitive Pattern)
Regular Expression(正規表達式),是一種抽象語言,用來表達字串的結構,比方[A-Za-z]+能表達所有的英文單字,應用上常用來驗證輸入,或是捕捉(Capture)具有特徵的字串片段,利用Regular Expression可以大幅簡化字串處理的繁瑣作業
這次的問題如下,假設某種字串格式的具體內容是以前綴(prefix)後綴(post-fix)框住,怎麼利用正規表示式驗證並捕捉(Capture)重複的片段?
好比以下類似座標的紀錄檔:
Model:M1
Coordinates:
X1.62 Y1.01
X9.53 Y2.3
X4.94 Y2.1
X8.55 Y3.15
首先分析一下文本的結構:
其中"[-+]?[0-9]*\.?[0-9]+"就是浮點數的表達
這樣丟下去REGEX跑字串,模式字串中的()可以透過Match.Group(Index)存取,在這裡的模式字串我們共用到三個(),按照()在模式字串裡的順序,Group依序是:
Model:[\\w]+[\\s]+Coordinates:[\\s]+(?<coord>X(?<X>[-+]?[0-9]*\\.?[0-9]+)[\\s]*Y(?<Y>[-+]?[0-9]*\\.?[0-9]+)[\\s]*)+
具名化後,便可以用Match.Group(Name)方式存取群組了
這次的問題如下,假設某種字串格式的具體內容是以前綴(prefix)後綴(post-fix)框住,怎麼利用正規表示式驗證並捕捉(Capture)重複的片段?
好比以下類似座標的紀錄檔:
Model:M1
Coordinates:
X1.62 Y1.01
X9.53 Y2.3
X4.94 Y2.1
X8.55 Y3.15
X2.56 Y1.25
X5.57 Y2.72
首先分析一下文本的結構:
- 標頭如同前兩行,形式與內容固定
- 重複資料部分按行表達
- 單筆座標以X<浮點數> Y<浮點數>表示
- 座標值是我們最在意的部分
- 希望可以一組一組捕捉分離,才好批次後處理解析數值
模式字串(Pattern)的設計想法如下:
- 浮點數的表達不用傷腦筋,網路上很容易查到
- 座標值的部分用()群組住單組X,Y座標(也是一種Symbol),後面以+表達此組Symbol會重複1到N次
- 單組座標內又再次以()群組住浮點數字串表達
- 考慮強健性,安插些泛空白字元在各個單元交接處
於是我們交出了以下的模式字串 :
"Model:[\\w]+[\\s]+Coordinates:[\\s]+(X([-+]?[0-9]*\\.?[0-9]+)[\\s]*Y([-+]?[0-9]*\\.?[0-9]+)[\\s]*)+"
其中"[-+]?[0-9]*\.?[0-9]+"就是浮點數的表達
這樣丟下去REGEX跑字串,模式字串中的()可以透過Match.Group(Index)存取,在這裡的模式字串我們共用到三個(),按照()在模式字串裡的順序,Group依序是:
- Index-0:原始字串
- Index-1:單組座標的群組
- Index-2:X座標浮點數字串
- Index-3:Y座標浮點數字串
String __input = "Model:M1 Coordinates: X1.62 Y1.01 X9.53 Y2.3 X4.94 Y2.1 X8.55 Y3.15 X2.56 Y1.25 X5.57 Y2.72"; String __patternUnnamed = "Model:[\\\\w]+[\\\\s]+Coordinates:[\\\\s]+(X([-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*Y([-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*)+"; List若模式字串裡()太多,或是覺得用INDEX標定群組可讀性太差,此時可以套入具名群組(Named Group)的概念,把()所圈住的Symbol具名化:__coords = new List (); Match __matchUnnamed = Regex.Match(__input,__patternUnnamed); //----------------------------------- // Unnamed //----------------------------------- __coords.Clear(); for (int i = 0; i < __matchUnnamed.Groups[1].Captures.Count;i++) { // use X,Y group restore each coordinates float[] eachCoord = new float[]{0,0}; eachCoord[0] = float.Parse(__matchUnnamed.Groups[2].Captures[i].Value); eachCoord[1] = float.Parse(__matchUnnamed.Groups[3].Captures[i].Value); __coords.Add(eachCoord); } //check all coordinates Console.WriteLine(\"Unnamed\"); foreach (float[] item in __coords) { Console.WriteLine(String.Format(\"{0},{1}\", item[0], item[1])); }
Model:[\\w]+[\\s]+Coordinates:[\\s]+(?<coord>X(?<X>[-+]?[0-9]*\\.?[0-9]+)[\\s]*Y(?<Y>[-+]?[0-9]*\\.?[0-9]+)[\\s]*)+
具名化後,便可以用Match.Group(Name)方式存取群組了
String __patternNamed = "Model:[\\\\w]+[\\\\s]+Coordinates:[\\\\s]+(?X(? [-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*Y(? [-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*)+"; Match __matchNamed = Regex.Match(__input,__patternNamed); //----------------------------------- // Named //----------------------------------- //now we get the \"coord\" group __coords.Clear(); for (int i = 0; i < __matchNamed.Groups[\"coord\"].Captures.Count; i++) { // use X,Y group restore each coordinates float[] eachCoord = new float[]{0,0}; eachCoord[0] = float.Parse(__matchNamed.Groups[\"X\"].Captures[i].Value); eachCoord[1] = float.Parse(__matchNamed.Groups[\"Y\"].Captures[i].Value); __coords.Add(eachCoord); }
後記1:.NET的正規表達式相容於Perl5,也與這裡相容,可以先快速的先驗表達式
後記2:據說.NET的Regular Expression模組是難得可以幫你Capture住群組值(Value)的函式庫
程式碼Repo
後記2:據說.NET的Regular Expression模組是難得可以幫你Capture住群組值(Value)的函式庫
程式碼Repo
2017年7月16日 星期日
[.NET] 記憶體洩漏(Memory Leak)可能原因 - XML Serializer
承接此篇
.NET最爽的就是背後有GC(Garbage Collection)罩著,想配置物件就配置物件(NEW),用完拍拍屁股就走了,等下就會有服務生來收碗盤了,多爽!
可是如果你點到的菜是XML Serializer,可能就沒辦法那麼幸運了,GC是不收這道菜的,亂點到最後...整個餐廳桌上都會是你之前吃剩的XML Serializer
GC無法自動回收XML Serializer,(原因,Dynamically Generated Assemblies段落),過於任性大量配置Serializer的話,記憶體可是會爆的
既然如此只能讓自己勤勞點,MSDN推薦的作法是,快取(Cache)住使用過的XML Serializer,每次欲利用XML Serializer時,統一向管理函式索取,若快取庫內沒有,則重新配置一個,若有,則取出既有的參考傳回,如此一來再利用時便不用重新配置,也就避免了潛在的記憶體洩漏問題了
參考以下程式碼:
- 函式原型:
- 引數 - 要序列化的物件型別(若物件是衍生自某基底型別,則透過陣列傳入具體衍生型別)
- 傳回值 - XML Serializer實例
- 視野(Scope): public static,視為工具函式(Utility)
class utilities { /// <summary> /// Used to store key-serializer /// </summary> /// <returns></returns> private static Hashtable __dictionary = new Hashtable(); /// <summary> /// Generate key according to contents of type list /// </summary> /// <param name="typeList"></param> /// <returns></returns> protected static int generateKey(List<Type> typeList) { //hash code summarize return typeList.Sum(__type => __type.GetHashCode()); } /// <summary> /// Always fetch serializer from this function /// (Never allocate by yourself) /// </summary> /// <param name="mainType"></param> /// <param name="extraTypes"></param> /// <returns></returns> public static XmlSerializer getSerializer(Type mainType,Type[] extraTypes){ //merge the list List<Type> __typeList = new List<Type>(); __typeList.Add(mainType); __typeList.AddRange(extraTypes); var key = generateKey(__typeList); if (__dictionary.ContainsKey(key)) { // this kind of serializer had been cached/used before , // get the cached serializer return (XmlSerializer)__dictionary[key]; } else { // never been used , need to create a new one and push into cache XmlSerializer __serializer = new XmlSerializer(mainType,extraTypes); __dictionary.Add(key,__serializer); return __serializer; } }//getserializer }//utilities
參考程式碼:https://github.com/smandyscom/cached_xmlSerializer
2017年7月8日 星期六
[VS Code x C#] VS Code加入參考
最近用起了VS Code,只是想單純地做一些C#程式範例(Snippet),起初照了美女Demo
(她真的很甜)安裝設定,簡要如下:
- 安裝VS Code(只是一個Open Architecture文字編輯器
- 安裝.NET Core(就是.NET Framework 核心肌群
- 在VS Code下安裝種種必須Plugin
- 下指令初始化專案資料結構
- $dotnet new
- 開寫C#
可是呢,當我嘗試想用一些命名空間組件時就開始鬼打牆,好比說System.Collections.Hashtable,明明已經在最前面寫Using System.Collections
可VS Code不會讓你Build過,原因是 xxx Type or name could not be found
原因很簡單,你沒替專案加入參考
如同在Visual Studio下,要引用其他組件除了要在程式碼最開頭帶入Namespace(Using xxxxx),也必須在專案下加入參考(歐~想起來了)
以下紀錄一下如何替VS Code C# Project加入參考
首先打開下面的 終端機(Terminal)以以下指令下載Package,好像是可以想成下載函式庫
#dotnet add package <AssemblyName>
為什麼是System.Collections.NonGeneric?參考的Assembly(.dll/動態函式庫)怎麼查詢?
可看API Reference,裡面的Assembly就是組件名稱,以Hashtable為例,必須要加入System.Collections.NonGeneric,所以下
#dotnet add package System.Collections.NonGeneric
系統會跑出一些訊息表示正在Download,最終會處理完成,如果來了一些Error,比較大可能性是"打錯字",好比System.Collections打成System.Collection(少一個s)
下完以後,再下
#dotnet restore
會自動幫你在.csproj內引入dependencies
你的.csproj會多出dependencies片段:
<project sdk="Microsoft.NET.Sdk"> <propertygroup> <outputtype>Exe</outputtype> <targetframework>netcoreapp1.1</targetframework> </propertygroup> <itemgroup> <packagereference include="System.Collections" version="4.3.0"> <packagereference include="System.Collections.NonGeneric" version="4.3.0"> <packagereference include="System.Runtime.Extensions" version="4.3.0"> <packagereference include="System.Xml.XmlSerializer" version="4.3.0"> </packagereference></packagereference></packagereference></packagereference></itemgroup> </project>
這些是原先新專案結構初始化後所沒有的片段
加入組件到此就沒問題了
咦,若是要加入其他參考專案呢?
可以參考這個指令,dot add reference
後記:網路上會查到很多 手動改project.json以加入dependencies的建議,看起來是比較過去的作法,目前版本的.NET Core SDK似乎要嘗試以.csproj做統整(與Visual Studio一致),捨棄project.json作為專案結構的描述,也許是如此一來專案比較好在VS Studio與VS Code兩個平台上操作而不會有轉換的困擾
2017年6月30日 星期五
[Windows Form] Property Grid 使用心得 - Part III(EditorAttribute)
(承接此篇)
EditorAttibute
在該欄位點下"編輯"後,系統會找尋該欄位的是否有EditorAttribute,若有,則根據你指定的Editor型別配置(Allocate)一個新實例(Instance),並呼叫該實例的EditValue函式,最後將EditValue傳回的值Set回該Property
實作方式是宣告一Class,繼承UITypeEditor,並複寫(override)以下函式:
- GetEditStyle
- 傳回值型別為UITypeEditorEditStyle,傳回不同值可改變欄位編輯入口樣式
- Model:該欄位會提供(...)讓使用者點擊,通常要另外提供表單(Form)讓使用者操作
- DropDown:顧名思義是提供下拉式選單
- None:沒得編輯
- EditValue
- 就是Editor最主要的函式,函式執行完畢後傳回值即為編輯完成的值
- 慣用起手式:
- 另外配置(Allocate)一個表單(Form)
- 帶入(Controls.Add)自己的UserControl,i.e TextBox.Text
- 顯示該表單,通常使用ShowDialog,使EditValue被Block在此行
- 在使用者關閉表單後,ShowDialog返回
- 從剛剛的UserControl取得使用者輸入的值,i.e TextBox.Text
- 將輸入值傳回(Return)
class stringUIEditor : UITypeEditor { public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context) { return UITypeEditorEditStyle.Modal; } public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value) { Form __form = new Form(); // allocating a form , used to show-up editable facility TextBox __tb = new TextBox(); __form.Controls.Add(__tb); __form.AutoSize=true; __form.ShowDialog(); // show-up the form return __tb.Text; //return the text value the user just input. } }
註:EditValue的輸入引數有許多妙用,可以用來判斷呼叫對象的特性,若要打造比較萬能的Editor就得要深入了解
從(...)呼喚的UIEditor |
程式碼參考
[Windows Form] Property Grid 使用心得 - Part II(TypeConvertor)
再稍微整理一下Property Grid的一些概念:
TypeConvertor
概念上就像是"顯示樣式轉換器",把該欄位的值丟進去對應的Convertor,根據你自訂的邏輯處理後將傳回的值顯示,實作方式為,宣告一Class,繼承TypeConvertor,並複寫
- 顯示的欄位名稱及內容是依SelectedObject內宣告的Properties展開
- 顯示樣式則由各Property所擁有的屬性(Attribute)所定義
- 值(Value)以外的顯示樣式由以下屬性定義
- Display - 控制顯示名稱
- Category - 控制群組分類
- Description - 控制下方描述
- Browsable - 控制是否隱藏
- 值(Value)的樣式控制有以下幾方面
- Readonly - 控制值可否給使用者輸入
- 顯示樣式的控制,由TypeConvertor屬性決定
- 編輯器的控制,由EditorAttribute屬性決定
PropertyGrid各顯示樣式控制對應屬性 |
TypeConvertor
概念上就像是"顯示樣式轉換器",把該欄位的值丟進去對應的Convertor,根據你自訂的邏輯處理後將傳回的值顯示,實作方式為,宣告一Class,繼承TypeConvertor,並複寫
- ConvertTo
- 將Object轉為顯示值,通常是String
- ConvertFrom
- 將顯示值轉回Object
class optionEnumConvertor : EnumConverter { /// <summary> /// Convert From String To Enum Type /// </summary> /// <param name="context" /> /// <param name="culture" /> /// <param name="value" /> /// <returns></returns> public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { switch (value.ToString()) { case "選項1": return MyEnum.OPTION1; case "選項2": return MyEnum.OPTION2; default: return null; } } /// <summary> /// Convert From Enum Type To String Type /// </summary> /// <param name="context" /> /// <param name="culture" /> /// <param name="value" /> /// <param name="destinationType" /> /// <returns></returns> public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { switch ((MyEnum)value) { case MyEnum.OPTION1: return "選項1"; case MyEnum.OPTION2: return "選項2"; default: return null; } } public optionEnumConvertor(Type type):base(type) { } }
最後就是在對應的Property上加註TypeConvertor屬性,PropertyGrid就可以根據其指定的TypeConvertor進行樣式轉換了
[DisplayName("想要選什麼?")] // much more human-readable? [TypeConverter(typeof(optionEnumConvertor))] public MyEnum Property3 { get; set; }
註: Enum比較特別的是,可以加註在屬性上,也可以加註在宣告上
[TypeConverter(typeof(optionEnumConvertor))] enum MyEnum { OPTION1, OPTION2, }
Enum轉String |
2016年5月7日 星期六
[Raspberry Pi]DHT11溫濕度模組 x Node.js = DHT11 Server
這次要來嘗試自DHT11取得的溫溼度資訊以網頁(Web)的形式公布,也就是HTML Server
最後要突破路由器的限制逆風高灰,讓任何的客戶端都能從網際網路連上伺服器,上面就寫著DHT11讀到的溫濕度資訊
整個架構概念如下:
- 將DHT11定時(1分鐘一次)讀出的溫濕度寫到某個檔案(th.out)裡
- 開啟一個HTML Server,在客戶端連入時,讀取th.out內的資訊,秀在主頁面上
- 為了能穿透路由器,讓客戶端能從網際網路連入,我們需要某種類似DDNS的穿牆服務
OK,戰鬥開始!第一步,將DHT11的讀出值定時寫入特定檔案
從先前的Adafruit提供的python程式,我們已經可以很輕鬆的讀出資訊顯示在Console上,只要再利用Bash Script做點小小加工就可以讓DHT11全天候運轉偵測溫濕度:
開啟新Script
#vim outputTh.sh
設定為可執行
#chmod u+x outputTh.sh
在Script寫下:
主要功能是開啟一個無限迴圈,每60秒讀出目前時間以及DHT11資訊,並輸出(覆蓋)th.out
#vim outputTh.sh
設定為可執行
#chmod u+x outputTh.sh
在Script寫下:
#!/bin/bash
while :
do
date > th.out #先輸出當前時間
$(./Adafruit_Python_DHT/examples/AdafruitDHT.py 11 4) >> th.out #輸出DHT11資訊,注意相對路徑
sleep 60 #在此暫停60秒
done
主要功能是開啟一個無限迴圈,每60秒讀出目前時間以及DHT11資訊,並輸出(覆蓋)th.out
執行此Script!(因為牽涉到底層存取,請給他sudo下去)
#sudo ./outputTh.sh
在此先做個確認,理論上輸出檔案內容應該會長這樣:
20160507 14:14:03
Temp=29.0*C Humidity=62.0%
最後讓此Script在背景以nohup執行
$nohup sudo ./outputTh.sh &
$nohup sudo ./outputTh.sh &
第二步,設定DHT11 Server(HTML Server)
這裡選擇最好上手的Node.js,這是個整合的很好的伺服器端執行環境,可以把他想像成事件驅動架構,對開發者而言,只要搞懂各事件(Event)的發出時機,並且寫下相對應的處理常式(Event Handler)即可
首先是安裝Node.js
#sudo apt-get install -y nodejs
接著開啟一個Java Script:
#vim server.js
內容寫這樣:
以上Script要這樣解釋:
#vim server.js
內容寫這樣:
var http = require("http") '引入http模組
http.createServer(function(request,response){
response.writeHead(200 , {"Content-type":"text/plain"});
response.write(fs.readFileSync('./date.out'));
response.end();
}).listen(8888);
以上Script要這樣解釋:
- 程式走到第三行時,會從http模組呼叫createServer函式
- createServer()引數1是客戶端連上時要做的回應動作,既然是一連串的動作所以是一個函式而不是變數,就是所謂的回呼啦(Call-back)
- 在這裡的回應動作是,讀出th.out的內容在主畫面上輸出
- 而createServer()會吐回一個伺服器物件,呼叫此物件的listen(),表示啟動監聽連入需求,這裡使用的監聽埠口為8888
值得一提的是,node.js的任何函式幾乎都有同步/非同步兩個版本,一開始使用了fs.readfile,(註:讀取檔案的非同步版本),結果實際讀取檔案的時機不見得在函式呼叫後,所以可能會在response.end()前都沒讀出buffer,常常以exception收場
所以這裡讀取檔案的函式必須要用同步呼叫(Blocking IO),即為fs.readFileSync()的return值就是讀出的字串
最後以Node.js啟動剛剛寫好的Script
#nodejs server.js
#nodejs server.js
此時來做個小小測試,看看是否能在區域網路內看到網頁,開瀏覽器連上:
http://192.168.1.1:8888
http://192.168.1.1:8888
好的,區域網路內連上DHT11 Server沒問題了,最後記得讓DHT11 Server登出後也能在背景執行:
$nohup nodejs server.js &
$nohup nodejs server.js &
第三步,先別管DHT11 Server了,你聽過ngrok嗎?看看他們官網的圖示,他們就是有辦法讓你的伺服器暴露在路由器之外,
首先是安裝,在apt-get上沒有此套件,我們直接下載執行檔解壓縮即可
下載:
$wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
解壓縮:
$unzip ngrok.zip
下載:
$wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
解壓縮:
$unzip ngrok.zip
取得!接著的動作十分簡單,可以想像這個軟體做的動作就是左耳進右耳出,把封包轉送出/ngrok伺服器,啟動ngrok,引數打入:
$ngrok http 8888
用手機從3G網路測試看看…嗯…沒問題!
以後回家前就可以先知道家裡溫濕度如何了(那又怎樣
2016年1月19日 星期二
[Raspberry Pi] Ad Hoc
Ad Hoc,拉丁原文有"將就的、隨意的"的語意,在Wifi協定中代表"無線隨意網路",也就是不需要中央基地台,通訊端之間即能夠構成的網絡
相較於Ad Hoc模式,Infrastructure就是日常生活中熟悉的中心輻射式網路拓樸,網路內通訊統一透過中央基地台分發
在Linux下設定Ad Hoc模式的步驟非常簡單,只要改/etc/network/interface內設定再重開裝置即可!
參考:http://spin.atomicobject.com/2013/04/22/raspberry-pi-wireless-communication/
步驟:
- $sudo vim /etc/network/interface #改網路設定
- wlan0設定如下
iface wlan0 inet static #靜態IP
address 192.168.1.1 #設定IP
netmask 255.255.255.0 #設定子網路遮罩
wireless-channel 1 #訊號通道
wireless-essid RPiAdHocNetwork #暴露在可用的Wifi清單中的名稱
wireless-mode ad-hoc #模式:ad-hoc
- 存檔
- $sudo ifdown --force wlan0 #先將wlan0關閉(強制下架)
- $sudo ifup wlan0 #再將wlan0重新打開,套用方才設定的設定值
如此一來就可以了,果真隨意簡單
原先以為通訊上不用經過HUB或許傳輸上會變快,殊不知ad hoc是比Infrastructure更為古老的架構,許多針對Wifi傳輸最佳化的方法可能都不支援,而且還帶有一些毛病,參考此篇:Limitations of Ad Hoc Mode Wireless Networking
實際用iperf測速,使用Ad Hoc相較於Infrastructure,傳輸速度完全沒有提昇,該是嘗試Wifi Direct看看
2016年1月10日 星期日
[.NET] Memory Leaks
前陣子在工作上遇到了"OutOfMemory"的例外,深感驚訝,以為在GC環境下,這種像是慢性病的東西可以從此與我絕緣,真要下手查BUG時,突然理解到,關於GC,關於記憶體分配模型,我理解的太模糊,才會感覺每一行指令好像都值得懷疑似的(最後發現是TextBox內容控制機制有問題)
.NET的記憶體分配模型:
- 堆疊(Stack):
- 何時被分配?
- 進入函式,配置實值物件,或宣告實例參考時
- 依序連續分配,像是盤子一層層疊起來所以稱為Stack
- 什麼東西會被分配?
- 實值型別(Value Type)物件
- 註:可以從物件是否繼承自System.ValueType來確定是否為實值型別
- 例如:Int , Float , 使用Structure宣告的物件
- 參考型別(Reference Type)參考(Reference),可稱為Handle,概念上可以想成指標
- 何時被回收?
- 當函式退出(Return)時,按照分配順序一層層移除,後進先出
- 註:所以若在此函式範圍做了 Object a = new Object,在退出此函式時參考a會被沒收,此時跟a鏈結的物件,就成了無主遊魂,很快地會被牛頭馬面GC(Garbage Collector)回收,帶到陰曹地府等待投胎轉世(輪迴?)
- 託管堆積(Managed Heap):
- 何時被分配?
- 隨時隨地,在你使用New配置物件時
- 什麼東西會被分配?
- 參考型別物件,可稱為Instance
- 註:String是參考型別物件,分配在堆積上,只是其屬性為不可修改(Immutable)
- 何時會被回收?
- 當GC掃描到此Instance並無任何參考指向他,便會標記起來,接著自動回收
- 非託管堆積(Unmanaged Heap):
- 何謂非託管?
- 就是非在CLR上執行的程式,不受.NET管理,好比你用C語言基於Standard Library寫出來的程式
- 何時被分配?
- 隨時隨地,在你使用New/malloc配置物件時
- 什麼東西會被分配?
- 任何你用New/malloc配置的東西
- 何時會被回收?
- 必須手動明確(Explicit)地呼叫Delete/Free,此物件所佔據的記憶體空間才會被釋放
可能的記憶體洩漏原因:
- 有問題的非託管程式碼
- 物件被分配(New/malloc)出來了,但忘記寫下Delete(Free),典型的錯誤
- 最可怕的是第三方供應商的函式庫有問題,你沒程式碼他又不想理你
- 託管程式碼:
- 對於IDisposable物件,使用過後未呼叫Dispose()
- 非託管程式碼或是運用到獨佔資源(Resource)的物件可能會實作此介面,作為提供呼叫端明確呼叫釋放時機的可能性
- 例如:Filestream、SqlConnection、Usercontrol
- 需要手動控制釋放時機的物件才實做IDisposable(程式結束時釋放的那種不算)
- 對於所有IDisposable物件,盡量使用Using,減少人為失誤造成的困擾
- 物件已失去參考,但隸屬的EventHandler仍與活動中的Event連結
- 由於成員函式隱含實例作為參考,可能會使得EventHandler對應的物件無法被釋放(這點相當有趣,物件成員函式預設至少有一引數為自己)
- 其他,自己應用上的失誤
- Queue:Enqueue沒有Dequeue的快,結果就是一直累積
- 傾盡CPU全力,狂寫檔案(StreamWriter.Write()),緩衝區被玩爆(這種問題很快會爆發,好查)
- 記憶體控制機制設計有問題
- 案例分析:瀑布式Textbox表現行數控制
分析記憶體洩漏的好工具,perfmon.exe
- 心得:
- 帶出的數據非常完整,而且可以設定固定採集時間長度,記錄在不同的採集檔內,比起自己在程式碼內寫LOG,用這工具比較能確保實驗在固定的資料搜集條件下進行
- 抓漏心法:
- 找出可能的嫌疑點,開perfmon設定長度N分鐘週期M秒採樣,時間到後觀察增長趨勢
- 沒改善,表示找錯方向,改動其他嫌疑點,重複上面過程,再看一遍
- 詳細使用方法:
- 非常好的評論:
- http://www.codeproject.com/Articles/42721/Best-Practices-No-Detecting-NET-application-memo#Avoidtaskmanagertodetectmemoryleak
- 摘要:
- 不要用Task Manager觀察是否洩漏,因為其表現的是總分配空間而非實際使用空間,且涵蓋了與其他行程共用的Shared Memory大小
- 應該還是用Perfmon.exe作為分析工具
- Private Byte增長:表示Unmanaged Code洩漏
- ByteInAllHeap增長:表示Managed Code洩漏
參考資料:
經常性的字串處理你該用StringBuilder
Using using as grantee (even for the filestream):
Close and Dispose
Heap、Stack、Reference Type、Value Type、Boxing、Unboxing
2016年1月4日 星期一
[Raspberry PI] PWM
PWM,Pulse Width Modulation,以數位方式控制高低電位週期比例來達到模擬類比電壓的效果,比方說控制馬達速度、LED燈亮度...
值得一提的是,因為是模擬的方式所以PWM訊號頻率其實也會出現在輸出訊號的頻譜裏,當然也要輸出裝置頻寬夠高才能夠被察覺.
樹莓派身為一塊萬用開發版當然有辦法輸出PWM,目前查到有下面幾個方法:
- RPi.GPIO
- 指令下達方式:Python
- PWM生成方式:軟體模擬
- 安裝:
- #sudo apt-get install python-dev python-rpi.gpio
- 用法:
- #sudo python #進入Python
- >>import RPi.GPIO as GPIO
- >>GPIO.setmode(GPIO.BCM)
- >>GPIO.setup(18, GPIO.OUT) #設定GPIO18為輸出腳
- >>pwm = GPIO.PWM(18, 500) #建立PWM實例,在GPIO18輸出,頻率為500Hz
- >>pwm.start(100) #開始輸出DUTY=100%的脈波
- 心得:
- 用在伺服馬達(Servo)上會感覺到顫振,確實是用軟體方式來輸出,持續輸出脈寬的重複性不佳,大概只適合做LED燈控制等人機應用.
- 軟體輸出方式大概是以系統內建計時器,去計時控制輸出腳位為高位/低位,由於Linux並非即時系統,各行程(process)無法保證被定時呼喚,因此即便系統計時器是可靠的,在上位做控制的行程也沒辦法總是在計時到達的當下去處理.
- RPIO
- 指令下達方式:Python
- PWM生成方式:半軟體模擬?(文件上寫著透過DMA通道,不太清楚背後機制)
- 安裝:
- #sudo apt-get install python-setuptools
- #sudo easy_install -U RPIO
- 用法:
- #sudo python #進Python
- >> from RPIO import PWM
- >> servo = PWM.Servo() #建立一個Servo實例,預設週期為50Hz
- >> servo.set_servo(18,1500) #在GPIO18上持續輸出脈寬為1500us的脈波
- >> servo.set_servo(19,10000) #在GPIO19上持續輸出脈寬為10000us的脈波
- 心得:
- 發現如果將其中一隻Pin輸出關閉,所有輸出都會被關閉,不知道是不是BUG?
- 可以多通道輸出(但不曉得通道代表什麼意思),比方說
- >> servo0 = PWM.Servo(0)
- >> servo1 = PWM.Servo(1)
- 如此就建立了兩個Servo物件對應到不同的DMA通道,但操作起來仍發現會相互干擾
- 顫陣比GPIO小,但仍能感受到.
- pigpio
- 指令下達方式:pipe 、c-interface、python
- PWM生成方式:軟體模擬(p指令)、硬體輸出(hp指令)
- 安裝:
- #git clone https://github.com/joan2937/pigpio #跟上最新版
- cd PIGPIO
- make
- sudo make install
- 用法(pipe方式):
- #sudo pigpiod #喚起掌管輸出入的專用daemon
- #pigs p 19 255 #在GPIO19上輸出DUTY 100%的PWM信號(軟體模擬輸出)
- #pigs hp 18 100 500000#在GPIO18上輸出頻率為100Hz DUTY為50%的PWM信號(硬體直接輸出)
- 心得:
- "硬"PWM,直接用樹莓派BCM2835晶片內建兩個底層PWM輸出單元生成PWM信號,直接以IC輸出信號重複性一定是最穩定的,也就是脈寬不會有忽大忽小的問題
- pigpio只是作為寫入晶片上特定registor的API罷了
- BCM2835只有兩個PWM通道(Channel),甚至在Pi2上只有一隻引腳被接到介面IO上,而Raspberry PI B+則有GPIO18,GPIO19可用.
- 操作起來真的是顫陣最低的(滿意)!
- 另外要說pigpio的shell指令很豐富,初期拿來測試硬體相當順手,文件也建立的很完整(滿意)!
- 各型Raspberry PI GPIO表:http://elinux.org/RPi_BCM2835_GPIOs
後記:
即便是用了Hardware PWM,功率電路之間的相互干擾也是另一令人頭痛的因素....尚待努力
其他參考:
BCM2835硬體Sheet
訂閱:
文章 (Atom)