Category Archives: .NET

ทดลอง IKVM

IKVM เป็น run-time ที่ใช้แปลง byte code ของ Java ไปเป็น IL บน .NET รวมถึงมี Base Class Library ของ Java ที่ implement เป็น .NET ให้เรียกใช้ด้วย ตอนนี้ใช้งานได้ในสองโหมดคือ

1. เอา IKVM ไปแทน Java ไปเลย สั่งรัน class ของ Java หรือ .JAR ที่ build มาแล้วได้ทันทีบน .NET Framework

2. แปลง byte code ไปเป็น IL ให้เสร้จก่อน แล้วเรียกใช้เหมือน app ปกติ (หรือ add reference ไปในกรณ๊ที่เป็น library)

กรณีทั่วๆไปคงหาโอกาสใช้ให้เกิดประโยชน์ได้ยาก ถ้าโปรแกรมเล็กๆเขียนใหม่จาก Java –> .NET ดูจะเป็นวิธีแก้ปัญหาที่ดีกว่า ถ้าแต่โปรแกรมมันเริ่มซับซ้อนและใหญ่ และการใช้งานจริงๆเราก็มองว่าเป็น black box (give input, get output) แก้ปัญหาโดยใช้ IKVM ก็ไม่เลว เคสของผมคือมันมี logic สำหรับการแกะ format อะไรซักอย่างที่มัน proprietary มากๆ spec ก็คงหาค่อนข้างยาก ให้แกะ spec จากโค้ดลองดูแล้วนิดหน่อยก็คิดว่าคงใช้เวลามหาศาล แต่โชคดีที่มันมี implementation ที่เป็น Java อยู่แล้ว ก็ใช้ IKVM แปลงมาเป็น library ใน .NET แล้วเรียกใช้ได้เลย

ตอนแรกคิดว่าคงมีปัญหาแน่ๆ เพราะ library มันก็ใหญ่มากอยู่ แต่ปรากฎว่าผ่านฉลุยตั้งแต่รอบแรกอย่างน่าตกใจ o__o! วันนี้กลับมาบ้านเลยมาลองกับ project วิชา programming methodology ที่เคยเขียนเป็น game คู่กับแบงค์ ทิ้งไว้เมื่อตอนปีสอง ถ้าวัดในแง่ขนาดของโค้ดคงเล็กกว่าที่แปลงก่อนหน้านี้หลักร้อยเท่า แต่เนื่องจากมันเป็น game เลย depends on libraries หลายตัว อาทิ:

  • Swing ทำ GUI
  • อ่านเขียนไฟล์
  • Audio เล่นเสียง
  • คลาสพวกจัดการ Image
  • Math
  • อ่าน Image จากไฟล์ใน JAR (เทียบได้กับ Resource ล่ะมั้ง)
  • ..

รัน

ikvmc CPLander100.jar

ได้มาเป็น CPLander100.exe ผลคือ

  • Start up นานได้ใจ (เกือบ 3 วินาที)
  • เปิดเมนูหน้าแรกได้ ใช้ได้ Hooray!
  • In game ใช้ได้ แต่ออกมาเมนูข้างนอกอีกรอบไม่ได้ T_T
  • เสียงหาย
  • ช้ากว่ารันด้วย Java มาก (สมเหตุสมผล)
  • Component ของ Swing ที่ใช้ทำงานถูก
  • พวก Math มีคำนวณพลาดจน paint ภาพผิดตำแหน่ง คิดว่าน่าจะเกิดจากตอนเขียนโค้ดไม่ระวังเรื่องพวกใช้ floating point (มั้ง)

 

ถึงจะรันได้ไม่ perfect แต่โดยรวมถูกใจ IKVM มาก ว่างๆไปหาโอกาสเล่นกันดู ยกนิ้วให้คนทำ –/\-

IKVMx

ประสบการณ์ Heisenbug

รู้จักคำนี้ครั้งแรกมาพักนึงแล้ว สั้นๆคือ Heisenbug เป็น bug ที่ตรวจไม่เจอหรือ reproduce ไม่ได้ถ้าพยายามจะ debug มัน … อึ้ยยย ฟังก็งงแล้วว่ามีจริงเหรอวะของแบบนี้ ชื่อก็ตั้งตาม Heisenberg คนที่คิด “หลักความไม่แน่นอนของไฮเซนเบิร์ก” ที่เคยเรียนในฟิสิกส์แหละครับ เนิ้ดร้องอ๋อแน่นอน

นอกจากนี้ยังมี bug อื่นๆ ที่จัดว่าเป็น bug แบบ “แปลกๆ” อีก แปลมาให้ฟังเล่นๆดังนี้

Bohrbug – นานๆเป็นทีนึง แต่ condition แน่นอน มักจะเกิดกับ condition ที่ rare มากๆ ส่วนใหญ่มักจะโผล่มา say hi บน production และเราก็จะหาไม่เจอว่า condition อะไรที่มัน trigger อาการนี้ออกมา – -‘

Mandelbug – ต้นเหตุของปัญหาซับซ้อนมาก จนมองจากภายนอกแล้วเหมือนจะไม่มี pattern อะไรแน่นอนที่ทำให้เกิดปัญหานี้เลย ซึ่งปัญหาอาจเกิดจากการ interact กับระบบภายนอกระบบอื่น หรือว่ามันอาจใช้เวลานานกว่าปัญหาจะฟักตัวออกมาให้เห็นนับจากตอนที่มีการ execute อะไรบางอย่างผิดๆ

Schrödinbug – ปัญหาที่ไม่เคยปรากฎตัวเลย จนมีคนไป observe source code แล้วพบว่ามันผิด! อันนี้มันเป็นเรื่องที่เกี่ยวกับแนวคิดของ quantum นิดๆ คนสายเราน่าจะเคยอ่านเรื่อง แมวของชโรดิงเจอร์ มาบ้าง อันนี้เป็นเรื่องเดียวกัน แต่ผมนึกสถานการณ์ตัวอย่างไม่ออก .. เท่าที่คิดก็คงแบบว่าพวก hacker ที่อ่าน source code แล้วพบว่ามันผิด ก็เลยไป exploit ซะเองล่ะมั้ง ..

Phase of the Moon bug – อันนี้เกิดจากการคาดการณ์ว่า bug ตัวนึงจะเกิดขึ้นเนื่องจากปัจจัย A,B,C แล้วก็พยายาม isolate ปัญหาโดยลองกับปัจจัย A,B,C ทีละตัว … ทั้งๆที่จริงๆแล้ว A,B,C ไม่ได้เกี่ยวข้องกับปัญหาเลย ทำให้สุดท้ายแล้วหาสาเหตุไม่เจอซักที … อันนี้ลองมองเทียบกับเรื่อง ดูลายนิ้วมือ หรือ โหงวเฮ้ง ก็พวกดูดาวแล้วเอาไปเป็นสัญญาณ trade หุ้น คล้ายๆกัน

Statistical bug – เป็นประเภทที่ไม่สามารถมองเห็นปัญหาได้จากการรันทดสอบเพียงครั้งเดียว ตัวอย่างที่วิ่งขึ้นมาในใจตัวอย่างแรกคือเรื่องการการเขียนโปรแกรม shuffle ไพ่ ซึ่งเป็นโปรแกรมที่เขียนให้ผิดได้ง่ายมากเพราะบางทีผลการสับไพ่หลายๆครั้งอาจจะไม่กระจายออกมาในทุกรูปที่เป็นไปได้แบบ uniform … พูดง่ายๆคือสับออกมาแล้วออกมาแบบนี้บ่อยกว่าอีกแบบนึงอะไรแบบนั้น อ่านมาจาก http://www.codinghorror.com/blog/2007/12/the-danger-of-naivete.html

Noob bug (or Noobug) – เป็นอะไรที่ง่ายมาก แต่นู้บหาสาเหตุไม่เจอ อันนี้เจอเองบ่อย (ฮาาา) ยกตัวอย่างซะหน่อย มือใหม่อาจแปลกใจว่าทำไมโปรแกรมข้างล่างถึง print ออกมา 101 ตลอด กะจะเขียนโปรแกรม 1 + 2 + 3 + 4 + … + 100 ไปส่งคุณครูซะหน่อย Smile with tongue out

int increment, sum = 0;
for (increment = 1; increment <= 100; ++increment) ;
sum += increment;

Console.WriteLine(sum);

ส่วนเคส Heisenbug ที่เจอ โค้ดมันเป็นประมาณนี้ (C#)

public class MyEventGen { }

class Program
{
public static void Main()
{
var gen = new MyEventGen();
var wr = new WeakReference(gen);

// run the collector
GC.Collect();
while (true)
{
Thread.Sleep(1000);
Console.WriteLine(wr.IsAlive);
}
}
}

ถ้าเอาไปรันแบบ Debug หรือ Release ใต้ Visual Studio โปรแกรมจะ print True ออกมาตลอด (การใช้ WeakReference ไปชี้แล้วตรวจ IsAlive บอกได้ว่าโดน Garbage Collector เก็บไปรึยัง) … แต่ถ้ารันนอก Visual Studio (without debugging) จะกลายเป็น False ตลอดทันที ตอนแรกก็เลยไม่รู้จะ debug ยังไง

ตามความเข้าใจผมตัวแปร gen ควรจะยังมี reference อยู่ตลอดเวลาเพราะยังไม่ออกจาก scope Main เลย … ซึ่งถ้าตาม syntax มันก็เป็นแบบนั้น คือผมสามารถเขียนโค้ดเพื่อ access ตัวแปร gen ได้ภายใน scope Main … แต่ run-time behavior มันไม่ได้เป็นแบบนั้น เพราะ GC ใน Release mode มันค่อนข้าง aggressive มาก เลยเก็บตัวที่ไม่ได้ใช้เรียบ T_T อันนี้ส่วนหนึ่งโดนฝังแนวคิดมาจาก C++ ด้วย เพราะปกติพวก object ที่ alloc ไว้บน stack จะโดน destruct ตอนออกจาก scope … ซึ่งมันไม่เหมือนกับภาษาที่มี GC.

อันนี้ตอนแรกมองว่าเป็ Heisenbug แต่จริงๆมองเป็น Noob bug ก็ถูก เพราะถามปุ๊บมีคนตอบทันที!! Open-mouthed smile http://stackoverflow.com/questions/4757869/is-this-com-object-subject-to-garbage-collection

เรื่อง bug ส่วนใหญ่ก็แปลมาจาก http://en.wikipedia.org/wiki/Unusual_software_bug ใครเจอเคสอะไรแปลกๆมาแชร์ให้ฟังหน่อยก็ดี อยากฟังครับ

ตัดคำไทยด้วย C#

WARNING: Highly technical blog ahead

กาลครั้งหนึ่งนานมาแล้ว … อาจารย์สมชายแห่ง CP เคยเอารัฐธรรมนูญฉบับเก่ากับฉบับใหม่มาเทียบความถี่ในการปรากฎของคำ ตอนนั้นผมอึ้งมากที่รู้ว่า Java มันตัดคำไทยได้ด้วย แบบ built-in มาไม่ต้องหา library อะไรมาเพิ่มเลย >_<  ถ้าผมเข้าใจไม่ผิด Java ใช้ ICU ของ IBM ที่เป็น open source แต่ตัวที่อยู่ใน class library ของ Java จะเป็นเวอร์ชันเก่ากว่าหน่อย

เจ้า ICU ที่ว่านี่นอกจากตัดคำได้ (Boundary Analysis) แล้วมันยังทำอย่างอื่นที่เกี่ยวกับกับ localization & internationalization ได้อีกมากมายก่ายกองครับ ลองเข้าไปดูกันเอง IBM ทำไว้สองชุดคือ ICU4C และ ICU4J สำหรับ C, C++ และ Java (ไม่มี .NET แป่วว) ถ้าใครทันสมัยใช้ Firefox ที่ยังตัดคำไทยไม่ค่อยจะคล่องอาจจะได้ยินชื่อนี้บ่อย เพราะมีคนไทยฮาร์ดคอร์มากมายเอาโค้ด Firefox มาแก้ใส่ ICU ให้ตัดคำแล้ว build ใหม่ … แค่ฟังก็อยากจะอ้วกออกมาเป็น pointer กับไฟล์ .h แล้วว =__=’

ด้วยความที่ 3 วันนี้ผมเมาจัดหรือยังไงไม่ทราบ เลยใช้เวลาว่างอันมีอยู่น้อยนิดไปพยายามทำ binding ให้เรียกใช้ ICU จาก C# (และ .NET) ได้ครับ! ก่อนทำก็หา best practice โดยการไปถามที่แหล่งประจำเล็กน้อย ได้คำตอบมาแค่อันเดียวน่าเศร้าใจ TvT

ผมเข้าใจว่าการทำ binding มันจะต่างจาก wrapper ตรงที่ เราต้องนำเสนอ interface เดียว (หรือคล้ายๆ) กับที่ underlying C++ classes มันใช้อยู่ให้กับผู้ใช้ ส่วนการทำ wrapper มันเหมือนกับว่าเราไปสร้างอะไรซักอย่างหุ้ม C++ classes เหล่านั้นไว้ และให้ผู้ใช้เรียกใช้งานผ่าน interface ของเรา (โดยไม่รู้ว่าข้างในมีอะไรอยู่บ้าง – Facade Pattern อ่านว่า “ฟา-ซ้าด”) ซึ่งการทำ wrapper นี่มันง่ายกว่าโขเลยน่ะ

หลังจากลองถูๆไถๆ ใช้พลังกับ C++/CLI เหมือนที่เคยใช้ในซีเนียร์โปรเจค ก็ออกมาเป็นรูปเป็นร่าง อยู่ที่ ICU4NET สามารถลองดาวน์โหลดไปใช้ได้ ถึงชื่อจะบอกว่า ICU4NET แต่จริงๆแล้วตอนนี้มันมี class ที่ใช้งานได้อยู่ class เดียวคือ BreakIterator =_=” ตั้งใจไว้ว่าจะจัดการกับ class ในกลุ่ม boundary analysis ให้หมด

เอาโค้ดตัวอย่างให้ดูเล็กน้อย อันแรกเป็นแบบดั้งเดิม ลักษณะเดียวกับที่เขียนใน ICU4J และ ICU4C

private List<string> WordBreak(string text)
{
var sb = new StringBuilder();
var col = new List<string>();

using (BreakIterator bi = BreakIterator.CreateWordInstance(Locale.GetUS()))
{
bi.SetText(text);
int start = bi.First(), end = bi.Next();
while (end != BreakIterator.DONE)
{
col.Add(text.Substring(start, end - start));
start = end; end = bi.Next();
}
}

return col;
}

ส่วนอันนี้ผมเขียน Extension Method เพิ่มให้มัน return เป็น IEnumerable ได้ พวกขา 3.5 จะได้เล่นซนแบบนี้ได้ :’)

using (BreakIterator bi = BreakIterator.CreateWordInstance(Locale.GetUS()))
{
bi.SetText(uxText.Text);

// requires ICU4NETExtension to use Enumerate extension method
MessageBox.Show(string.Join(Environment.NewLine, bi.Enumerate()
.GroupBy(w => w)
.OrderBy(x => x.Count())
.Reverse()
.Select(x => x.Key + " : " + x.Count())
.Take(10)
.ToArray()));
}

ตอนแรกตั้งใจว่า ส่วนที่น่าจะเอาไปเล่นได้หลักๆคงเป็นพวก ASP.NET Web App แหละ แต่นั่งคิดๆดูแล้วมันมีส่วนประกอบที่เป็น library native ของ ICU หลายอันอยู่ ถ้าเข้าใจไม่ผิดคงมีปัญหากับเรื่อง trust level ของ IIS พอสมควร …

ผลพลอยได้จากการผลาญเวลา (แบบไร้เหตุผล เอามันส์ล้วนๆ) ครั้งนี้คือ ได้ศึกษา C# เพิ่มอีกนิดหน่อย แล้วก็ลองใช้ C++/CLI อีกนิสสสนึง หลังๆมานี่อยู่ที่ทำงานได้ใช้แต่ C++ จนรู้สึกตัวเองตามโลก C# ไม่ทัน มาอ่านโค้ดของ @chakrit (ขา 3.5 ตัวพ่อ) ทีนี่นั่งงงอยู่หลายนาที –..-‘ เออออ … น้ำหลักลดด้วยนะ ^ ^

จากประสบการณ์ ด้วยหัวข้อยบลอกประมาณนี้ ผมเชื่อว่าคงมี นิสิต/นักศึกษา ที่กำลังปั่นงานของอาจารย์แล้ว search มาเจอ และอยากนำไปใช้ได้โดยเร็ว … ผมขอร้องว่าก่อนจะทิ้งคอมเม็นต์ไว้หรือเมล์มาถาม technical issue กับผม ช่วยศึกษาเรื่องพื้นฐาน C# กับเรื่องพวกวิธี reference รวมถึงอ่าน Readme ก่อน แล้วค่อยถามมานะครับ …

ผมตั้งเป้าไว้ว่าจะมีคนเอาไปใช้ประโยชน์ได้ 2 คนขึ้นไป ใครเอาไปใช้ทำอะไรบอกด้วยๆ :’)

ICU4NET – ICU Binding for .NET

WordBreak

ปัญหาภาษาไทยกับ iTunes และ Windows

เป็นปัญหาเดียวกับเรื่องภาษาไทยเป็นตัวยึกยือ มันเกิดจากข้อมูลใน ID3 Tag (เป็นส่วนของไฟล์ที่ไว้บอกข้อมูลเกี่ยวกับเพลง) มันเก็บภาษาไทยโดยใช้ Encoding แบบนึง (มักจะเป็น Windows-874 / TIS620 ซัมติง …)

MP3TagProblem

อ่านหาวิธีแก้ตามเว็บบอร์ดชุมชนคนไทยก็พบวิธีแก้ง่ายๆอย่างนึงคือต้องไปเปลี่ยน Regional Settings ให้เป็นไทยหลายๆอัน แล้วจะดิสเพลย์ได้ถูกต้อง o__O! แล้วค่อยใช้ iTunes convert ID3 ให้กลายเป็นเวอร์ชันใหม่ๆ (ที่จะเป็น unicode รองรับหลายภาษา)

การทำแบบนี้มันก็มีปัญหาอยู่บ้าง เพราะพอเปลี่ยน Regional แล้วก็จะทำให้ไม่รู้ว่าอัลบั้มไหน convert ID3 ไปเรียบร้อยแล้วรึยัง (เพราะมันแสดงได้ถูกต้องทั้งหมด) อย่างตอนนี้ผมลง OS ใหม่เป็น Windows 7 อัลบั้มเก่าๆเน่าไปหลายอันอยู่ … ก็เลยเขียนโปรแกรม convert ดีกว่า! (นั่น …. หาเรื่องเขียนบลอกอะดิ๊ๆๆๆๆ)

ประเด็นสำคัญ

  • Tool สำหรับแปลง ID3 ที่เป็น encoding หนึ่งไปอีกอันนึงก็มีอยู่แล้วบ้าง แต่ส่วนใหญ่มักจะเป็นบน Linux กับ Mac หาของ Windows ไม่เจอ (หรือผมอาจจะตาถั่วเอง)
  • ใช้ Lib สำหรับอ่าน ID3 คือ TabLig Sharp
  • ใช้ Reflection ในการวนรอบ property ทุกอันที่เป็น string และ string[] เป็นครั้งแรกที่เริ่มเห็นคุณค่าของ relfection!
  • พอมันมี Library ภายนอกก็เลยมี DLL หลายอัน รวมให้เป็น EXE อันเดียวโดยใช้ ILMerge จะทำให้ ship ง่ายกว่า (แต่ยังไม่คิดจะ ship รอให้มี demand ก่อน ฮ่าๆ)
  • อันตรายมาก: ยังไม่มีวิธีเช็คเลยว่าไฟล์ไหนถูกแปลงไปแล้วบ้าง เพราะถ้าแปลงซ้ำกันสองครั้งมันจะทำให้เนื้อหาไฟล์พังไปเลย >_< โดนไปหนึ่งดอกเรียบร้อย
  • ตอนนี้ใช้ iTunes โดยให้มัน Consolidate Library แล้ว รู้สึกทำให้ย้ายของง่ายกว่า
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;

namespace ID3ConvertToUnicode
{
class Program
{
public static string FixThaiCodePage(string str)
{
if (str == null) return null;
byte[] raw = Encoding.Default.GetBytes(str);
string res = Encoding.GetEncoding("windows-874").GetString(raw);
return res;
}

public static string[] FixThaiCodePage(string[] strings)
{
string[] res = new string[strings.Length];
for (int i = 0; i < strings.Length; i++)
{
res[i] = FixThaiCodePage(strings[i]);
}
return res;
}

static void Main(string[] args)
{
string[] files = Directory.GetFiles(".");
foreach (string file in files)
{
if (!file.EndsWith(".mp3"))
continue;

Console.WriteLine("Converting: " + file);
TagLib.File music = TagLib.File.Create(file);

foreach (PropertyInfo pinfo in music.Tag.GetType().GetProperties())
{
if (!pinfo.CanRead || !pinfo.CanWrite)
continue;

if (pinfo.PropertyType == typeof(string))
{
string s = pinfo.GetValue(music.Tag, null) as string;
s = FixThaiCodePage(s);
pinfo.SetValue(music.Tag, s, null);
}

if (pinfo.PropertyType == typeof(string[]))
{
string[] s = pinfo.GetValue(music.Tag, null) as string[];
s = FixThaiCodePage(s);
pinfo.SetValue(music.Tag, s, null);
}
}
music.Save();
Console.WriteLine();
}

}
}
}
ilmerge /target:winexe  /out:ID3Retag.exe 
ID3ConvertToUnicode.exe policy.2.0.taglib-sharp.dll taglib-sharp.dll

iTunesThaiFixed

ยอดเยี่ยม!

ควบคุมบราวเซอร์ด้วย WatiN

วันนี้พยายามจะเขียนโปรแกรมให้ export ข้อมูลออกจาก web app ที่ทำงานตัวนึงอัตโนมัติ เขียนไปเขียนมา account โดนล็อค – -‘ แกว่งเท้าหาเสี้ยนชัดๆเลย

[Test]
public void SearchForWatiNOnGoogle()
{
using (IE ie = new IE("http://www.google.com"))
{
ie.TextField(Find.ByName("q")).TypeText("WatiN");
ie.Button(Find.ByName("btnG")).Click();

Assert.IsTrue(ie.ContainsText("WatiN"));
}
}

ใช้ง่ายเกินไปแล้ว!! สนใจเชิญที่ WatiN (สำหรับชาว .NET เท่านั้น – Web Application Testing in .NET – รุสึกจะมี WatiR ที่เป็นของ Ruby ด้วย)

มันเป็นเครื่องมือสำหรับให้พวก QA/Tester ใช้อ่ะครับ แต่คิดว่าถ้าจะเอาไปดัดแปลงทำ บอตโหวตคะแนน บอตลงทะเบียน หรืออะไรทำนองนี้ก็เอาไปทำได้ไม่ยากเลย

ข้อเสียคือใช้ได้กับ IE 6 7 8 เท่านั้น (Firefox อยู่ใน Roadmap) ถ้าอยากใช้กับ Firefox หรือ browser อื่นๆ ลองดู Selenium

ข้อเสียอีกอย่างคิดว่าคงเหมือนกับเครื่องมือทำนองนี้อื่นๆทั่วๆไป คือถ้าเมนูเป็น Flash เป็น Java Applet หรือเป็น ActiveX Object อะไรซักอย่าง มันจะทำให้เขียนโค้ดเข้าไป Automate ยาก (หรือทำไม่ได้) ขึ้นมาทันที

ปล. ต่อไปนี้จะพยายามเขียนบลอกให้สั้นๆ ได้ใจความมากขึ้น

เลือกเครื่องมือให้ถูกงาน: C++ ปะทะ C#

งานที่ผมต้องทำต่อไปใน day job มันค่อนจะเกี่ยวกับ C++/Unix เยอะหน่อย อยากมีประสบการณ์กับพวกมันมากกว่านี้ก็เลยตั้งใจลองเขียนโปรแกรมเล่นๆขึ้นมาดู

โปรแกรมที่ต้องการจะเขียนจะมีหน้าที่คือดึง Update ของคนๆหนึ่งมาจาก Twitter แสดงบนหน้าจอ (ใน Console) แล้วแยกเฉพาะส่วนที่เป็นลิงค์ใน Tweet อันนั้นมาแสดงคู่กันด้วย

นั่งทำตั้งแต่บ่ายแก่ๆ มาเรื่อยๆ เพิ่งมาเสร็จเมื่อกี้ o__o!

สรุปปัญหาที่เจอและสิ่งที่เรียนรู้ดังนี้

เลือกใช้ VIM เป็น Editor อันนี้ลองใช้มาซักระยะแล้ว รู้สึกชอบมากกว่า Emacs นะครับ ;P รู้สึกว่าตัว VIM มัน Simple ดี แล้วแทบจะมีอยู่ทุกที่ หัดทีนึงคุ้มเลยทีเดียว มันก็มีพวก script มาให้ลงเพิ่มได้เยอะแยะนะครับ เช่นตัวช่วย browse โค้ด หรือ theme หรือตัวช่วยทำ auto-complete ที่ใน VIM จะมี feature ชื่อ omni-completion ให้ด้วย (เป็น auto-com แบบ context-sensitive พูดง่ายๆ คือฉลาด) แต่ก็ยังไม่รู้สึก “เหมือนอยู่บ้าน” เท่า Visual Studio

Build โดยใช้ Makefile อันนี้เล่นเอาปวดหัว ตอนแรกคิดว่าพวก automake autoconf จะช่วยทำ Makefile ให้ได้ง่ายๆแบบไม่เปลืองแรง แต่จริงๆพวกนั้นก็ต้องเขียนไฟล์ขึ้นมาเพิ่มเหมือนกัน สุดท้ายเลยลงมือหัดเขียน Makefile เอง (และก็ได้แบบพิการๆแต่ทำงานได้ออกมา)

เรียนรู้ option ของ g++ ปกติแล้วใช้ Visual Studio ก็เอาไฟล์ยัดเข้าโปรเจค แล้วก็กด F5 ก็เอาโปรแกรมมารันเล่นได้แล้ว แต่พอต้องใช้คำสั่ง cmd ในการ compile – link เล่นเอาเหนื่อยเหมือนกัน สรุปว่าใช้ดังนี้

g++ –L path/to/lib1 –L path/to/lib2 –ldynamclib –I path/to/include –o execname source1.o source2.o staticlib.a

หรือถ้าจะสร้าง object ไฟล์อย่างเดียว

g++ –c main.cpp main.o

การใช้ Libraries ภายนอก C++ มันแทบไม่มีอะไรมาให้ใช้เลยนอกจาก STL (Standard Template Library) อยากได้อะไรต้องหาเองหมด ในทีนี้ต้องหา

  • Lib สำหรับการอ่าน XML เลือกใช้ TinyXML
  • Lib สำหรับการทำ HTTP Request เลือกใช้ libcurl
  • Lib สำหรับใช้ Regular Expression อันนี้จริงๆมัน overkill ไปหน่อย แต่อยากใช้ดู เอาส่วนหนึ่งของ Boost มาใช้

การเอาแต่ละอันมารวมกับโปรเจคก็เป็นเรื่องน่าปวดหัวมาก เนื่องจากด้อยประสบการณ์เรื่องพวกนี้เหลือเกิน กว่าจะรวมได้ก็แทบหัวแตก ปัญหาส่วนใหญ่เกิดตอน link

สรุปได้โปรแกรมออกมาสมใจ เสียเวลาไปมหาศาล ภูมิใจมาก T__T แทบจะเป็นโปรแกรม C++ โปรแกรมแรกบน Linux ที่ไม่ใช่ Hello World! (อ่อ ผมดึง update มาจากคุณ @tamir เพราะของตัวเองมีแต่อัพเดทภาษาไทย)

termtweet

อันนี้เป็น Makefile .. คนเขียนเป็นมาเห็นคงดูออกว่านุ้บมาก 55+

CXXFLAGS= -I ~/boost/include/boost-1_38 -I lib/curl/include -lrt

SRCS=main.cpp

OBJS=main.o

TINYOBJS=lib/tinyxml/tinystr.o lib/tinyxml/tinyxml.o lib/tinyxml/tinyxmlerror.o lib/tinyxml/tinyxmlparser.o

LIBS=~/boost/lib/libboost_regex-gcc43-mt-1_38.a /usr/local/lib/libcurl.a

all: termtweet

termtweet: $(OBJS) $(TINYOBJS)
g++ $(CXXFLAGS) -o $@ $(OBJS) $(TINYOBJS) $(LIBS)

tinyxml: $(TINYOBJS)

depend: $(SRCS)
makedepend $(SRCS)

clean:
-rm termtweet *.o *.bak *.swp

อันนี้ Source Code ในภาษา C++

#include <cstdio>
#include <iostream>
#include "lib/tinyxml/tinyxml.h"
#include <curl/curl.h>
#include <string>

#include <boost/regex.hpp>

using namespace std;

static int writer(char *data, size_t size, size_t nmemb,
string *buffer)
{
int result = 0;
if(buffer == NULL) return result;

buffer->append(data, size * nmemb);
result = size * nmemb;
return result;
}

void extract_link(const char *s){
string line(s);
boost::regex pat(".* (http://.*) .*");
boost::smatch matches;
if(boost::regex_match(line, matches, pat))
cout << "link: " << matches[1] << endl;
}

int main(void){
cout << "TermTweet 0.1" << endl;
cout << "Fetching items ..." << endl;

CURL *curl;
CURLcode res;
string buffer;

curl = curl_easy_init();
if(curl){
curl_easy_setopt(curl, CURLOPT_URL, "http://twitter.com/statuses/user_timeline/6380022.rss");
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writer);

res = curl_easy_perform(curl);

curl_easy_cleanup(curl);
}

if(res != CURLE_OK)
{
cout << "Can't get the RSS feed" << endl;
exit(-1);
}
cout << "Page load completed. Parsing XML .." << endl;

TiXmlElement *root;
TiXmlDocument doc;
doc.Parse(buffer.c_str());
root = doc.FirstChildElement("rss");
if(root != NULL){
TiXmlElement *channel;
channel = root->FirstChildElement("channel");
if(channel != NULL){
TiXmlElement *item;
item = channel->FirstChildElement("item");
while(item != NULL){
TiXmlElement *title;
title = item->FirstChildElement("title");
if(title != NULL){
cout << title->GetText() << endl;
extract_link(title->GetText());
}

item = item->NextSiblingElement("item");
cout << "----------------------------------" << endl;
}
}else{
cout << "Cannot find 'channel'" << endl;
exit(-1);
}
}else{
cout << "Not an RSS" << endl;
exit(-1);
}
cout << "Parse OK" << endl;
return 0;
}


ได้อะไรมาเยอะเหมือนกัน จากการหัดทำโปรเจคแสนถึกอันนี้ (แต่ก็เสียไปเยอะเช่นกัน T_T) ต่อไป … ไฮไลท์ … โปรแกรมคล้ายๆกันใน C# >.< เพิ่งเขียนเมื่อกี้ ไม่ต้องลง Lib เพิ่ม ไม่ต้องทำเบื้อกใดๆทั้งสิ้น ! (เชื่อว่า Java ก็คงใกล้ๆกัน)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Xml;
using System.IO;
using System.Text.RegularExpressions;

namespace SharpTermTweet
{
class Program
{
static void Main(string[] args)
{
string address = "http://twitter.com/statuses/user_timeline/6380022.rss";
WebRequest req = WebRequest.Create(address);
WebResponse res = req.GetResponse();

StreamReader sr = new StreamReader(res.GetResponseStream());
string content = sr.ReadToEnd();

XmlDocument doc = new XmlDocument();
doc.LoadXml(content);
XmlNodeList list = doc.GetElementsByTagName("item");
foreach (XmlNode node in list)
{
Console.WriteLine(node["title"].InnerText);
ExtractLink(node["title"].InnerText);
Console.WriteLine("-----------------------------");
}

}

static void ExtractLink(string line)
{
Regex regex = new Regex(".* (http://[^\\s]*) .*");
Match match = regex.Match(line);
if(match.Groups.Count >= 2)
Console.WriteLine("link: {0}", match.Groups[1]);
}
}
}

สรุปว่า เลือกเครื่องมือให้ถูกงานครับ 🙂

ปล. เพิ่งจะรู้ว่า Put the Right man to the Right job เป็นสำนวนที่โคดไทยเลย ไม่เชื่อลองไปหาใน Google ดูสิ ส่วนประโยคต้นฉบับจริงๆน่าจะเป็น Right man for the job มากกว่าจากการวิจัยของผม ..

วิธีการใช้ Subversion (SVN) – Subversion กับ Visual Studio

โพสต์นี้ไม่ค่อยมีอะไรหรอกครับ แค่จะบอกว่า ถ้าจะใช้กับ Visual Studio มันมีบางโฟลเดอร์ที่ไม่ควรใส่เข้าไปในตัวซอฟแวร์ Version Control ด้วย อันได้แก่

    1. ไฟล์ .csproject.user และ ไฟล์ .suo ไฟล์พวกนี้มันเก็บ configuration ของแต่ละ user ครับ ดังนั้นไม่ต้องใส่เข้าไปด้วย เพราะมันจะเปลี่ยนทุกครั้งที่บันทึก project
    2. โฟลเดอร์ bin อันนี้เป็นผลจากการ build ไม่ต้องใส่เข้าไปเช่นกัน เพราะเดี๋ยวก็ต้อง build ใหม่อยู่ดี
    3. โฟลเดอร์ obj ไม่ต้องใส่เช่นกันครับ

After First Commit

บางคนอาจจะชอบเอาไฟล์ Input บางอย่างไปโปะไว้ใน bin/Debug หรือ bin/Release แบบนี้จะทำให้ไฟล์พวกนี้ไม่เข้าไปใน VC ด้วย ถ้าเป็นกรณีนี้ลองใช้วิธีเอาไฟล์ดังกล่าวใส่เข้าไปใน project แล้วตั้ง property ชื่อ “Copy to Output Directory” ให้เป็น Copy if newer แทน จะเป็น practice ที่ดีกว่า

จบแล้ว ง่ายจัง 🙂

ตัวอย่างโค้ด C# เปรียบเทียบกับโค้ดภาษาเชิง functional อื่นๆ

วันก่อนอ่านเจอกระทู้คล้ายๆ Code Golf ที่ Stackoverflow จาก delicious ของ Jemmy ครับ เป็นกระทู้ในเว็บ Narisa.com

ลองพยายามแปลงโค้ดจากภาษา Scala ของพี่ข้าวโพดหวานให้เป็น C# ดู เพราะพอรู้มาว่า C# 3.5 มันทำอะไรแปลกๆได้มากขึ้น ได้ผลเปรียบเทียบดังนี้ครับ

แก้ไข: เพิ่งไปอ่านมาว่า Scala มันเป็นภาษาแบบ multi-paradigm ไปบอกว่าของเค้าเป็น functional อย่างเดียวเด๋วโดนว่า ฮ่าๆ

อันนี้เป็น Scala

def findPrimeFactors(number : Int) : List[Int] = {
val first_prime_or_none = (2 until Math.sqrt(number)).inclusive.find { i => number % i == 0 }
if (first_prime_or_none != None) {
return first_prime_or_none.get :: findPrimeFactors(number / first_prime_or_none.get)
}
number :: Nil
}

(2 until 100).inclusive.foreach {
number => println( "Prime factors of " + number + " is " +
findPrimeFactors(number).removeDuplicates.mkString("[", ", ", "]") )
}

อันนี้เป็น C#

static void Main(string[] args)
{
for (int number = 2; number <= 33; number++)
Console.WriteLine("Prime factors of {0} is [{1}]",
number, String.Join(", ", FindPrimeFactors(number)
.ToList().ConvertAll<string>(x => x.ToString())
.ToArray())
);
}

static HashSet<int> FindPrimeFactors(int number)
{
var firstPrimeOrNone = Enumerable.Range(2, (int)Math.Ceiling(Math.Sqrt(number)))
.ToList().Find(x => number % x == 0);

if (firstPrimeOrNone != 0)
{
var ret = FindPrimeFactors(number / firstPrimeOrNone);
ret.Add(firstPrimeOrNone);
return ret;
}

return new HashSet<int>(new int[] { number }); ;
}

C# เวอร์ชันลอกการบ้านชาคริตมาแก้นิดแก้หน่อย

อันนี้ลองใช้ IEnumerable กับ yield ทำ state machine ง่ายๆครับ อ่านง่ายกว่าอันข้างบนแยะ

static void Main(string[] args)
{
for (int number = 2; number <= 33; number++)
Console.WriteLine("Prime factors of {0} is [{1}]",
number, String.Join(", ", FindPrimeFactors(number)
.Distinct().Select(x => x.ToString()).ToArray())
);
}

static IEnumerable<int> FindPrimeFactors(int number)
{
while (true)
{
var firstPrimeOrNone = Enumerable.Range(2, (int)Math.Ceiling(Math.Sqrt(number)))
.ToList().Find(x => number % x == 0);

if (firstPrimeOrNone != 0)
{
yield return firstPrimeOrNone; number /= firstPrimeOrNone;
}
else
{
yield return number; break;
}
}
}

ทดลองใช้ ANTS Profiler

เหมือนที่ชื่อบอกครับว่ามันคงเป็นผลิตภัณฑ์ในตระกูล Profiler นั่นแหละ

Profiler เป็นโปรแกรมที่ไว้วัดประสิทธิภาพในการทำงานของโปรแกรม โดยอาจจะวัดในแง่เวลาการทำงาน หรือหน่วยความจำที่ใช้ โดยวัดกันได้ถึงระดับ Class, Method เลยทีเดียว ตัวที่เอามาลองวันนี้เป็นของ Redgate ทดลองใช้แบบ Evaluation ได้ 14 วันครับ

โปรเจคที่เอามาลองเป็นโปรเจค C# ในวิชา Game Programming ซึ่งเป็นเกม 3D ที่ใช้ Graphics Engine ตัวหนึ่งชื่อ OGRE3D (หรือสั้นๆว่า OGRE : Object-Oriented Graphics Rendering Engine) ตัวเดียวกับที่ใช้ทำ Senior Project

ตอนแรกก็สงสัยเหมือนกันว่าแล้วเราใช้ Library เสริมในลักษณะนี้ แล้วตอนทำ Profile โปรแกรมมันจะไม่อืดน่าดูเหรอ (ยิ่งเป็น Application ประเภทเกมด้วย) ปรากฎว่าตอนลองทำ Profile ดูแทบไม่เห็นความแตกต่างเลยครับ ตัวโปรแกรมก็ใช้ง่ายมาก มีการทำ Profile ได้หลายแบบ ทำไปสองแบบคือ Profile Performance และ Profile Memory เอามาให้ดูเฉพาะ Profile Performance

ส่วนอันนี้เป็นการดูรายละเอียดแต่ละ method ดูได้ว่าเข้ามาจากทางไหนบ้าง กระโดดไปทำงานต่อที่ไหนบ้าง แต่ละส่วนใช้เวลาเท่าไหร่

สรุปว่าก็เป็น Tool ที่มีประโยชน์ครับ ไว้หาคอขวดของโปรแกรม คือส่วนที่ทำงานช้าที่สุด (ในเคมีเรียกว่าขั้นตอนกำหนดปฎิกริยาสินะ) และ Profile Memory ก็เอามาไว้ช่วยหา Memory Leak ได้ (ยังใช้ไม่เป็น – -‘a)

วิธีการใช้ regular expression เพื่อ parse ไฟล์ข้อความใน C#

เขียนมาหลายครั้งแล้วไม่เคยจำซักที T_T เอามาลงไว้เตือนความจำซักหน่อย

หน้าตาของบรรทัดใน Text File มันคั่นด้วย Space หลายตัวติดกัน ใช้ string.Split(‘ ‘) อาจจะไม่เวิร์คเท่าไหร่ เพราะจะได้ empty string ติดมาด้วย

:
1.0000000e+000  2.4924448e+000  2.6646541e+000  ..
1.0000000e+000 -2.6678309e+000 -1.0957386e+000  ..
1.0000000e+000 -2.6132891e+000 -1.6032095e+000  ..
:

ส่วนอันนี้เป็นโค้ดสำหรับ parse ตัดมาเฉพาะใจความสำคัญ

[code:c#]

                 string line;
                int count = 0;
                Regex regex = new Regex("[0-9e+-.]+");
                while (count < maxLines && (line = tr.ReadLine()) != null)
                {
                    int type = -1;
                    List<double> values = new List<double>();
                    MatchCollection matches = regex.Matches(line);
                    foreach (Match m in matches)
                    {
                        double d = double.Parse(m.Groups[0].Value);
                        if (type == -1) type = (int)d;
                        else values.Add(d);
                    }
                    count++;

                    Entry e = new Entry()
                    {
                        Type = type,
                        Data = values.ToArray()
                    };

                    list.Add(e);
                }

[/code]