หลักการเขียนโปรแกรมยุคใหม่ๆ ในช่วงหลังๆ มานี้นิยมที่จะลดการเขียนโปรแกรมส่วนใหญ่ในภาษาระดับต่ำๆ เช่นภาษา C/C++ เนื่องจากเสี่ยงต่อการมีบั๊กค่อนข้างมาก และการพัฒนาที่ช้า เพื่อความเร็วในการพัฒนาแล้ว จึงมักนิยมใช้การพัฒนาในภาษาระดับสูงๆ เช่น Python, Ruby, PHP ฯลฯ แล้วทดสอบประสิทธิภาพ หากมีส่วนไหนทำงานช้าเกินยอมรับได้ จึงลงมือพัฒนาส่วนนั้นๆ เป็นภาษา C/C++ เพื่อความเร็ว แล้วจึงสร้างอินเทอร์เฟช เพื่อโมดูล C/C++ นั้นเข้ากับโปรแกรมหลัก
ปัญหาคือการเรียนรู้ และการเขียนโค้ดเพื่อเชื่อมต่อโมดูลต่างๆ เข้ากับภาษาระดับสูงนั้นเป็นงานที่สิ้นเปลืองพลังงานไม่ใช่น้อย และส่วนใหญ่กลับเป็นงานที่ต้องทำซ้ำไปมา สร้างความน่าเบื่อหน่าย ทางเลือกที่ดีกว่าคือการใช้โปรแกรมสร้างอินเทอร์เฟซอัตโนมัติ เช่น SWIG เพื่อทำงานส่วนที่น่าเบื่อทั้งหลายแทนที่เรา โดยใช้การคอนฟิกค่าเพียงเล็กน้อย
บทความนี้จะสมมติเหตุการณ์ที่เราต้องการสร้างฟังก์ชั่นที่เราทำเองฟังก์ชั่นหนึ่งที่ชื่อว่า toInt ที่แปลงค่าจากสตริงมาเป็นค่าเลขจำนวนเต็ม โดยในบทความจะถือว่าผู้อ่านมี Cygwin และ Python Win32 อยู่ในเครื่องอยู่ก่อนแล้ว
int toInt(char *s){
return atoi(s);
}
ก่อนอื่นที่เราต้องการคือโปรแกรม SWIG (ดาวน์โหลดที่นี่) หรืออาจเลือกลงตอนลง Cygwin เลยก็ได้ ถึงตอนนี้ก็แอบดูกันก่อนว่าเราจะเขียนให้ SWIG มันรู้จักฟัังก์ชั่นของเราได้ยังไง
%module example
%typemap(in) (char*s){
$1 = PyString_AsString($input);
}
%inline{
int toInt(char* s){
return atoi(s);
}
}
สั้นและง่ายอย่างไม่น่าเชื่อ โดยบรรทัดแรกนั้นคือชื่อโมดูลที่เราต้องการสร้าง ซึ่งจะไปมีผลเอาตอนที่เรา import เข้าโปรแกรม Python ของเรา
บรรทัดที่สองเป็น typemap ที่แปลงอาร์กิวเมนต์ข้ามภาษากัน ส่วนของ (in) นั้นคือระบุว่าเราต้องการสร้างส่วนแปลงข้อมูลอาร์กิวเมนต์ขาเข้าฟังก์ชั่นของเรา เนื่องจากฟังก์ชั่นของเราให้ค่าผลลัพธ์เป็น int ซึ่ง SWIG สามารถแปลงค่าได้เอง จึงไม่ต้องทำอะไรกับส่วนการส่งค่าออก (out) ส่วนต่อมาคือชื่อและ type ของฟังก์ชั่น ในกรณีนี้คือ (char*) ในกรณีที่เรามีฟังก์ชั่นที่ต้องการหลายอาร์กิวเมนต์ แต่สามารถแทนที่ได้ด้วยอาร์กิวเมนต์ในภาษา Python เพียงอาร์กิวเมนต์เดียว เราสามา่รถระบุได้ โดยการใส่หลา่ยอาร์กิวเมนต์ในวงเล็บ แบบเดียวกับการประกาศฟังก์ชั่นในภาษา C/C++
บรรทัดที่สามคือการประกาศการแปลงอาร์กิวเมนต์จากที่ได้รับมาจากภาษา Python โดยหนึ่งอาิร์กิวเมนต์ที่ได้รับจะมาในรูปของตัวแปรที่ชื่อว่า $input ซึ่งในส่วนนี้ SWIG จะนำสตริง $input ไปแปลงเป็นชื่อตัวแรกที่เป็น (PyObject *) ให้เอง ส่วน $1 นั้นหมายถึงอาร์กิวเมนต์ตัวแรกในรายการที่เราระบุ หากเรามีการใช้หลายอาร์กิวเมนต์ที่สร้างจาก อาร์กิวเมนต์ในภาษา Python เพีัยงหนึ่งอาร์กิวเมนต์ได้ เราก็อาจจะใช้ $2 หรือมากกว่านี้ขึ้นไปเรื่อยๆ ได้
ส่วนบรรทัดที่ 5 เรื่อยมานั้น คือการประกาศฟังก์ชั่นของเรา โดยการประกาศแบบ inline นั้นคือการประกาศทั้งฟังก์ชั่นมาอยู่ในไฟล์เดียวกัน แต่ถ้าต้องการใช้ฟังก์ชั่นจากไฟล์ภายนอกก็สามารถทำได้เช่นกัน
ถึงตอนนี้ ใ้ห้เราเซฟไฟล์นี้ในชื่อว่า example.i แล้วเรียก SWIG
swig -python example.i
เราจะได้ไฟล์ออกมาสองไฟล์คือ example_wrap.c และ example.py ต่อไปจะเป็นขั้นตอนการคอมไพล์
เนื่องจากเราต้องการใช้ Cygwin ในการคอมไพล์ทั้งหมด จึงทำให้เราไม่สามารถลิงก์เข้ากับ python24.dll ได้โดยตรง แต่ต้องการไฟล์ *.a ซึ่งคนใช้คอมไพล์เลอร์ตระกูล gcc คงคุ้นเคยกันดี
เสร็จแล้ว!!! ในตอนนี้เราได้โค้ดที่เราเขียนในภาษาซีมาทำงาน ใน pyhton ได้ ดังตัวอย่าง
>>> import example
>>> example.toInt("10")
10
หากเราต้องการให้เรียกใช้โมดูลนี้ได้ไม่ว่าเราจะรัน Python จากที่ใดในเครื่อง ก็เพียงนำไฟล์ example.py และ _example.dll ไปวางไว้ในโฟลเดอร์ C:\Python24\Lib\site-packages เป็นอันเรียบร้อย
อ้างอิง
Comments
เจ๋งมากครับ ชอบมาก ๆ :)
---
ผมไม่ได้ใช้ Python อ่ะ ใช้แต่ Java ไม่รู้ว่ามีตัวจัดการอย่างนี้ใช้รึเปล่า Let's play Ubuntu 5.10
chaba_bkk -SWIG ใช้กับ Java ได้ครับ แต่ผมใช้ไม่เป็น...
lewcpe.com, @wasonliw
ผมก็เป็นแฟน python เหมือนกันครับ ส่วนมากผมใช้ pyrex (ซึ่งไม่ใช่ extension) ในการเพิ่มความเร็วให้ python แต่เจ้า SWIG นี่ยังไม่เคยลอง อ่านแล้วน่าสนใจครับ แน่นอนเลยครับว่าต้องลอง
(ขอบ่นหน่อยครับ) การคอมไพล์พวก extension บนวินโดวส์นี่ยากมากครับ เมื่อเทียบกับบน GNU/Linux ยิ่งไม่ได้ใช้ .NET Stupido ด้วยแล้ว มันร้องจะเอานู่นเอานี่เยอะเหลือเกิน กว่าจะคอมไพล์ผ่านนี่เสียเวลาไปหลายวันเลยครับ (เอ... หรือว่าเราโง่เองหว่า)
bow_der_kleine - ได้ยินชื่อ pyrex มานานเหมือนกันครับว่าเร็วกว่า SWIG พอตัวเลย แต่ผมเลือกใช้ SWIG เพราะดูเว็บแ้ล้วมันน่าเชื่อถือกว่า ระบบ Document อะไรต่างๆ ค่อนข้างเป็นรูปเป็นร่างกว่า แถมอ่านไว้ทีเดียวใช้กับภาษาอื่นได้ด้วยครับ
lewcpe.com, @wasonliw
ctypes ไม่ต้อง compile
>>> import ctypes >>> toInt = ctypes.cdll.msvcrt.atoi >>> toInt("10") 10
Python 2.5 จะมี ctypes รวมมาด้วยเลย