Category Archives: Native

ตัดคำไทยด้วย 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

เลือกเครื่องมือให้ถูกงาน: 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 มากกว่าจากการวิจัยของผม ..

แนวปฎิบัติในการเขียนโค้ด C++ จาก Google

ไปแอบอ่านมานิดหน่อยครับ เค้าว่าเป็นแนวปฎิบัติที่ใช้กันใน Google คนเขียน C++ ลองไปอ่านดู

Google C++ Style Guide

เรื่องที่น่าสังเกตหลังอ่านเรื่องหนึ่งคือ “Do not use Hungarian notation” ตอนสมัยผมสนใจ C++ ใหม่ๆ เมื่อนานมากแล้วสมัย VB6 กำลัง Boom จำได้ว่าเค้า Encourage นักหนา ให้เอา prefix แปลกๆมานำหน้าชื่อตัวแปล ไม่ว่าจะเป็น lpstrXxxx, szXxxx, hWndXxxx, … blah blah อ่านทีนึงแทบจะเป็นลม ประกอบกับตอนนั้นอ่าน Code C++ ที่ใช้สร้างหน้าต่างโง่ๆมาอันนึง ไม่มีอะไรเลย ใช้ Code ประมาณ 60 บรรทัด (มารู้ตอนแก่ว่ามันคือการใช้ Win32 API ล้วนๆ) แต่ VB6 แค่ New Project ก็เสร็จแล้ว ทำให้ตอนนั้นผมเลิกสนใจ C++ ไปเลย

กลับมาเรื่อง Hungarian Notation ชื่อตัวแปรที่ Google แนะนำให้ตั้งจะเป็นแบบ lower case หมด แล้วใช้ underscore คั่นระหว่างคำแทน ก็ดูอ่านง่ายดี เหมือนภาษา C

How to Name

Give as descriptive a name as possible, within reason. Do not worry about saving horizontal space as it is far more important to make your code immediately understandable by a new reader. Examples of well-chosen names:

int num_errors;                  // Good.
int num_completed_connections;   // Good.

Poorly-chosen names use ambiguous abbreviations or arbitrary characters that do not convey meaning:

int n;                           // Bad - meaningless.
int nerr;                        // Bad - ambiguous abbreviation.
int n_comp_conns;                // Bad - ambiguous abbreviation.

Type and variable names should typically be nouns: e.g., FileOpener, num_errors.

Function names should typically be imperative (that is they should be commands): e.g., OpenFile(), set_num_errors(). There is an exception for accessors, which, described more completely in Function Names, should be named the same as the variable they access.

จริงๆแล้วภาษา C++ มันเปิดมาก เปิดจนมีวิธีการหลายอย่างในการทำเรื่องๆเดียว ตัวเลือกก็เลยมีมากมาย เอาโค้ดคนอื่นมาทำต่อก็เลยต้องใช้เวลาหน่อย มีเรื่องน่าสนใจอีกหลายอย่างเกี่ยวกับการเลือกแนวปฎิบัติของ Google ลองไปอ่านกันดู 🙂

เปรียบเทียบการเท่ากัน 3 ค่าใน C++

วันนี้มีคำถามครับ

เรื่องนี้มีอยู่ว่า จะต้องเปรียบเทียบค่า 3 ค่า ว่ามันเท่ากันรึเปล่า

ผมเขียนอะไรประมาณนี้ลงไป

[code:c#]

if(a == b == c){

// Statements

}

[/code]

ปรากฎว่า Compile ผ่าน และรันได้แฮะ (VC++) แต่ผลลัพธ์ไม่ตรงตามที่ต้องการ ก็นั่งหาอยู่นานว่าเกิดอะไรขึ้น

ให้เวลาคิด 3 บรรทัด

มานั่งคิดดูดีๆ ได้ว่า C++  มัน evaluate boolean condition ข้างบนแบบนี้ครับ

จาก a == b == c เป็น (a == b) == c

กลายเป็นว่า ถ้า a == b อยู่แล้ว จะกลายเป็น true == c แทน!

ตายล่ะ .. แล้วทำไม C++ ไม่แจ้งเตือนเลยล่ะ

ก็เพราะว่า C++ มันใช้พวกจำนวนเต็มแทน condition คือ 0 แทน false นอกนั้นแทน true

แต่ถ้าเป็นภาษาพวก Managed Langauge เช่น C# หรือ Java จะไม่ยอมให้ประโยคข้างบน Compile ผ่าน เพราะซ้ายกับขวามันคนละ Type กัน

เป็นเช่นนี้แล ..

การใช้ Precompiled Headers ใน Visual C++

หลังจากทรมานจนทนไม่ไหวกับการ Build ที่ช้ามากจนแทบทนไม่ได้ของ Visual C++ ทำให้ต้องไปหาวิธีเพิ่มความเร็วมาใช้ หนึ่งในนั้นคือการใช้ Precompiled Headers ของตัว Visual C++ เอง ทำให้ build กินเวลาน้อยลงมากอย่างไม่น่าเชื่อ

เริ่มจากต้องไปตั้งใน Project Property -> Configuration -> C/C++ -> Precompiled Headers ให้เป็น Use Precompiled Headers (/Yu) เสียก่อน

หลังจากนั้นสร้างไฟล์ stdafx.h กับ stdafx.cpp ขึ้นมา (หรือ copy มาจาก project ที่ทำไว้แล้วก็ได้) ข้างในมันจะคล้ายๆแบบนี้

stdafx.h

[code:c#]

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>

// TODO: reference additional headers your program requires here

#include <Ogre.h>
#include <OIS/OIS.h>
#include <OgreFont.h>
#include <OgreFontManager.h>
#include <OIS/OIS.h>
#include <iostream>
#include <string>
#include <vector>
#include <windows.h>
#include <stdio.h>
#include "bass.h"

[/code]

stdafx.cpp

[code:c#]

// stdafx.cpp : source file that includes just the standard includes
// TestPrecompiledHeaders.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

[/code]

ในไฟล์ stdafx.h เราจะใส่ library ที่เราต้องการทำ precompiled headers ลงไป

ยังไม่จบ หลังจากนั้นเลือกเฉพาะไฟล์ sfdafx.cpp เท่านั้น  (ย้ำว่าเลือก .cpp ไม่ใช่ .h) ดู Property -> Configuration -> C/C++ -> Create Precompiled Headers (/Yc)

เสร็จแล้วในไฟล์ source code และ header อื่นๆนอกจาก 2 file ข้างบนที่ต้องการใช้ precompiled header ก็ใส่ #include "stdafx.h" เข้าไปแทน

เสร็จสมบูรณ์

ความบัดซบของวิธีนี้ก็คือว่า เนื่องจากมันเป็น Specific Feature ของ Visual C++ ดังนั้นถ้าจะพอร์ตไปแพลตฟอร์มอื่น ก็คงต้องเสียเวลาแก้กันหน่อย