การสร้าง Python Extension ด้วย SWIG+Cygwin

tags:

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

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

---

chaba_bkk's picture

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

lew's picture

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

bow_der_kleine's picture

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

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

lew's picture

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

sake's picture

ctypes ก็ใช้ง่ายดีนะครับ ไม่ต้อง compile อะไรทั้งสิ้น เร็วเหมือนกัน

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

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

(กำลังเขียน database module ติดต่อ python กับ odbc ด้วย ctypes อยู่)

Site Search

 
Web blognone.com

Poll