Tags:
Node Thumbnail

หลักการเขียนโปรแกรมยุคใหม่ๆ ในช่วงหลังๆ มานี้นิยมที่จะลดการเขียนโปรแกรมส่วนใหญ่ในภาษาระดับต่ำๆ เช่นภาษา 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 คงคุ้นเคยกันดี

  1. เริ่มจากการก๊อปปี้ไฟล์ python24.dll จาก C:\Windows\system32 มาไว้ในที่ที่เราจะทำงาน
  2. ดาวน์โหลด pexports.exe มาไว้ในเครื่องให้พร้อม
  3. สั่ง pexports python22.dll > python22.def
  4. ใช้คำสั่ง dlltool ที่มีมาใน Cygwin เพื่อสร้าง libpython24.a ดังนี้ dlltool --dllname python24.dll --def python24.def --output-lib libpython24.a
  5. ถึงตอนนี้ก็ถึงเวลาคอมไพล์ โดยเริ่มจากการคอมไพล์ example_wrap.c ออกมาเป็น example_wrap.o ด้วยคำสั่ง gcc -c -mno-cygwin example_wrap.c -I/cygdrive/c/python24/include โดยการ สั่ง -mno-cygwin นั่นเพื่อให้ไฟล์ที่ออกมาใช้งานกับ dll ที่เป็น win32 native ได้ ส่วนการสั่ง -I ก็เพื่อดึงเอาไฟล์ header ของ Python ที่มาพร้อมอยู่ในตอนที่เราลง Python มาใช้งาน
  6. อย่าเพิ่งดีใจว่าเสร็จแล้ว เพราะเรายังไม่ได้ใช้ไฟล์ libpython24.a ที่เราสร้างขึ้นมา โดยการสั่ง gcc -shared -mno-cygwin example_wrap.o libpython24.a -o _example.dll โดยอาร์กิวเมนต์ -shared คือการสั่งให้ gcc สร้างไฟล์ dll นั่นเอง ส่วนการตั้งชื่อ dll ที่ได้นั้นสำคัญมากกว่าต้องตั้งชื่อเริ่มต้นด้วย underscore และตามด้วยชื่อโมดูลของเรา

เสร็จแล้ว!!! ในตอนนี้เราได้โค้ดที่เราเขียนในภาษาซีมาทำงาน ใน pyhton ได้ ดังตัวอย่าง


>>> import example
>>> example.toInt("10")
10

หากเราต้องการให้เรียกใช้โมดูลนี้ได้ไม่ว่าเราจะรัน Python จากที่ใดในเครื่อง ก็เพียงนำไฟล์ example.py และ _example.dll ไปวางไว้ในโฟลเดอร์ C:\Python24\Lib\site-packages เป็นอันเรียบร้อย

อ้างอิง

  1. sebsauvage.net : Writing C/C++ Python extensions without Microsoft Visual C++
  2. SWIG Document
Get latest news from Blognone

Comments

By: wd on 11 June 2006 - 14:36 #7344

เจ๋งมากครับ ชอบมาก ๆ :)

---

By: chaba_bkk
Android
on 13 June 2006 - 09:19 #7373

ผมไม่ได้ใช้ Python อ่ะ ใช้แต่ Java ไม่รู้ว่ามีตัวจัดการอย่างนี้ใช้รึเปล่า Let's play Ubuntu 5.10

By: lew
FounderJusci's WriterMEconomicsAndroid
on 13 June 2006 - 17:01 #7382
lew's picture

chaba_bkk -SWIG ใช้กับ Java ได้ครับ แต่ผมใช้ไม่เป็น...


lewcpe.com, @wasonliw

By: bow_der_kleine
WriterAndroidUbuntu
on 14 June 2006 - 06:49 #7391
bow_der_kleine's picture

ผมก็เป็นแฟน python เหมือนกันครับ ส่วนมากผมใช้ pyrex (ซึ่งไม่ใช่ extension) ในการเพิ่มความเร็วให้ python แต่เจ้า SWIG นี่ยังไม่เคยลอง อ่านแล้วน่าสนใจครับ แน่นอนเลยครับว่าต้องลอง

(ขอบ่นหน่อยครับ) การคอมไพล์พวก extension บนวินโดวส์นี่ยากมากครับ เมื่อเทียบกับบน GNU/Linux ยิ่งไม่ได้ใช้ .NET Stupido ด้วยแล้ว มันร้องจะเอานู่นเอานี่เยอะเหลือเกิน กว่าจะคอมไพล์ผ่านนี่เสียเวลาไปหลายวันเลยครับ (เอ... หรือว่าเราโง่เองหว่า)

By: lew
FounderJusci's WriterMEconomicsAndroid
on 14 June 2006 - 12:08 #7393
lew's picture

bow_der_kleine - ได้ยินชื่อ pyrex มานานเหมือนกันครับว่าเร็วกว่า SWIG พอตัวเลย แต่ผมเลือกใช้ SWIG เพราะดูเว็บแ้ล้วมันน่าเชื่อถือกว่า ระบบ Document อะไรต่างๆ ค่อนข้างเป็นรูปเป็นร่างกว่า แถมอ่านไว้ทีเดียวใช้กับภาษาอื่นได้ด้วยครับ


lewcpe.com, @wasonliw

By: sake on 21 May 2016 - 12:51 #7444

ctypes ไม่ต้อง compile

>>> import ctypes >>> toInt = ctypes.cdll.msvcrt.atoi >>> toInt("10") 10

Python 2.5 จะมี ctypes รวมมาด้วยเลย