CMake/external libraries

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา

แม้ปัจจุบันมีไลบรารีภาษา C/C++ ที่สามารถใช้ได้บนแพลตฟอร์มพลายแพลตฟอร์มอยู่มากมาย แต่การใช้ไลบรารีเหล่านั้นพัฒนาโปรแกรมยังเป็นเรื่องที่ยุ่งยาก เนื่องจากแพลตฟอร์มต่างๆ เก็บไลบรารีเหล่านี้ไว้ที่ไดเรคต่างๆ กัน CMake มีสคริปต์สำหรับค้นหาแตำแหน่งของไลบรารีที่ได้รับความนิยมอยู่หลายๆ ไลบรารี ทำให้การใช้ไลบรารีเหล่านี้เขียนโปรแกรมที่ใช้ได้หลายๆ แพลตฟอร์มเป็นเรื่องง่าย (แต่ก็ยังมีรายละเอียดมากพอสมควร) ในบทความนี้เราจะมาศึกษาวิธีการเรียกใช้สคริปต์เหล่านี้โดยใช้ไลบรารี OpenGL และ GLUT เป็นตัวอย่าง

ติดตั้ง GLUT บน Windows

สร้างไดเรคทอรีสำหรับเก็บไลบรารี

หากผู้อ่านใช้ Linux หรือ Mac OS X ก็ไม่มีความจำเป็นจะต้องติดตั้งไลบรารีสองไลบรารีข้างต้นแต่อย่างใด เนื่องจากมันติดมากับระบบปฏิบัติการแล้ว อย่างไรก็ดีผู้อ่านที่ใช้ Windows จะต้องมีการติดตั้ง GLUT ให้ CMake สามารถค้นหาเจอได้ วิธีการติดตั้งนี้ความจริงแล้วมีหลายวิธี แต่ผู้เขียนจะนำเสนอวิธีการติดตั้งของตัวเองไว้ ณ ที่นี้

ผู้เขียนได้สร้างไดเรคทอรีหนึ่งในฮาร์ดดิสก์ไว้สำหรับเก็บไลบรารีภาษา C/C++ โดยเฉพาะ ในบทความนี้ขอสมมติให้เป็นไดเรคทอรี C:\usr\local เพื่อให้คล้ายๆ กับไดเรคทอรีใน Unix ในไดเรคทอรีนี้จะได้เรคทอรีย่อดังสองไดเรคทอรีที่สำคัญคือ

  • C:\usr\local\include สำหรับเก็บ header file
  • C:\usr\local\lib สำหรับเก็บไลบรารีที่อยู่ในรูปไฟล์ .lib
  • C:\usr\local\bin สำหรับเก็บไลบรารีที่อยู่ในรูปไฟล์ .dll

หลังจากนั้นผู้เขียนจะกำหนด environmental variable ของ Windows สองตัวดังต่อไปนี้ (คุณสามารถดูวิธีกำหนด environmental variable ได้ที่ เวบไซต์นี้)

  • เพิ่ม C:\usr\local\bin เข้าในตัวแปร PATH ซึ่งมีอยู่แล้ว
  • สร้างตัวแปร CMAKE_PREFIX_PATH ใหม่และให้มันมีค่าเท่ากับ C:\usr\local

CMake จะดูค่าของตัวแปร CMAKE_PREFIX_PATH เวลามันค้นหาไดเรคทอรีของไลบรารีต่างๆ การกำหนด CMAKE_PREFIX_PATH ไม่มีความจำเป็นใน Linux หรือ Mac OS เนื่องจากแพลตฟอร์มเหล่านั้นมีมาตรฐานโครงสร้างไดเรคทอรีกำหนดอยู่ตายตัว ไม่เหมือนกับ Windows

ลง GLUT

คุณสามารถดาวน์โหลด GLUT สำหรับ Windows ได้ที่เวบไซต์ของ Nate Robins เราแนะนำให้คุณดาวน์โหลด glut-3.7.6-bin.zip มาแล้วขยายมันออก แล้วให้คัดลอกไฟล์เหล่านี้ใส่ไดเรคทอรีดังต่อไปนี้

  • glut32.dll ลงใน C:\usr\local\bin
  • glut32.lib ลงใน C:\usr\local\lib
  • glut.h ลงใน C:\usr\local\include\GL โดยที่คุณต้องสร้างไดเรคทอรี C:\usr\local\include\GL ขึ้นมาก่อน
  1. โปรแกรมวาดรูปสี่เหลี่ยมจัตุรัส

เราจะเขียนโปรแกรม square สำหรับวาดรูปสี่เหลี่ยมจัตุรัสโดยใช้ OpenGL และ GLUT อันดับแรกเราจะสร้างไดเรคทอรีของโปรเจคซึ่งมีไฟล์อยู่ดังต่อไปนี้

   sample/
       build/
       src/
           CMakeLists.txt
           config.h.in
           square/
               CMakeLists.txt
               square.cpp
               

ซึ่งเหมือนกับโครงสร้างไดเรคทอรีในบทความเกี่ยวกับการตรวจสอบแพลตฟอร์มก่อนหน้านี้ ไฟล์ config.h.in นั้นมีเนื้อหาเหมือนเดิม ที่เปลี่ยนคือไฟล์ CMakeLists.txt ทั้งสองไฟล์ แต่เราจะพูดถึงมันทีหลัง

ไฟล์ square.cpp มีเนื้อหาดังนี้

<geshi lang="c">

  1. include "../config.h"
  1. ifdef __WIN_PLATFORM__
  2. include <windows.h>
  3. endif
  1. ifdef __MAC_PLATFORM__
  2. include <OpenGL/gl.h>
  3. include <OpenGL/glu.h>
  4. include <GLUT/glut.h>
  5. else
  6. include <GL/gl.h>
  7. include <GL/glu.h>
  8. include <GL/glut.h>
  9. endif

void display() {

   glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
   glClearDepth(1.0f);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glColor3f(1.0f, 1.0f, 1.0f);
   glBegin(GL_QUADS);
   glVertex2f(-0.5f, -0.5f);
   glVertex2f( 0.5f, -0.5f);
   glVertex2f( 0.5f,  0.5f);
   glVertex2f(-0.5f,  0.5f);
   glEnd();
   glutSwapBuffers();

}

int main(int argc, char **argv) {

   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
   glutCreateWindow("CMake with OpenGL and GLUT");
   glutInitWindowSize(512, 512);
   glutDisplayFunc(display);
   glutMainLoop();
   return 0;

} </geshi>

สังเกตว่าในโค้ดข้างบน ก่อนที่ถึงโค้ดที่ทำงานจริง มีโค้ดส่วนหนึ่งที่ใช้ include ไฟล็ที่เหมาะสมซึ่งค่อนข้างยาว (13 บรรทัด) แสดงให้เห็นถึงความยุ่งยากในการเขียนโปรแกรมภาษา C++ ให้ทำงานได้หลายแพลตฟอร์ม ถึงแม้ว่าเราจะมีตัวช่วยอย่าง CMake อยู่ก็ตาม (ดังนั้นจึงไม่แปลกดอกที่คนหันไปใช้ Java หรือ C# มากกว่า)

การใช้ OpenGL และ GLUT มีรายละเอียดพอสรุปได้ดังนี้

  • แพลตฟอร์มต่างๆ เก็บ OpenGL อยู่คนละที่กัน

ดังนั้นเราจึงต้องมีการใช้ preprocessor macro ใน config.h สำหรับตรวจสอบแพลตฟอร์ม และนี่เป็นเหตุผลที่เราต้อง include ../config.h

  • ใน Windows ก่อนที่เราจะ include GL/gl.h เราจะต้องทำการ

include windows.h ก่อนเสมอ แต่ในแพลตฟอร์มอื่นเราไม่ต้องทำเช่นนั้น

  • หากผู้อ่านติดตั้ง GLUT ตามที่ผู้เขียนบอกไว้แล้ว

วิธีการ include ไฟล์ของ OpenGL และ GLUT ใน Windows และใน Linux จะมีลักษณะเหมือนกัน กล่าวคือทั้งสามไฟล์อยู่ในไดเรคทอรี GL ซึ่งอยู่ในไดเรคทอรีที่ CMake ไปหา OpenGL และ GLUT เจออีกที

  • ในทางกลับกัน Mac OS เก็บ OpenGL และ GLUT แยกกัน

แถมยังเก็บไม่เหมือนของระบบอื่นๆ เลย ทำให้ชื่อไฟล์ header ของ OpenGL ต้องมี OpenGL/ นำหน้า ส่วน header ของ GLUT ต้องมี GLUT/ นำหน้า

คำสั่ง FIND_PACKAGE

CMake เรียกไลบรารีภายนอกว่า "package" และมีคำสั่ง FIND_PACKAGE ไว้สำหรับค้นหาที่อยู่และข้อมูลจำเป็นอื่นๆ ของ package ที่ผู้ใช้กำหนด คำสั่ง FIND_PACKAGE มีรูปแบบการใช้ดังต่อไปนี้

   FIND_PACKAGE(<ชื่อ package>)
   

และถ้าหากการคอมไพล์โปรเจคต้องใช้ package ที่เราต้องการหา เราอาจสั่ง

   FIND_PACKAGE(<ชื่อ package>) REQUIRED)
   

เพื่อบอกให้ CMake พิมพ์ข้อความแสดงความผิดพลาดหากหา package ไม่เจอ

ในกรณีของโปรเจคในบทความนี้ เราจะใช้ package สองตัวคือ OpenGL และ GLUT (คุณสามารถดู package อื่นๆ ที่ CMake ค้นหาเป็นได้จาก documentation ของ CMake) ฉะนั้นเราจะต้องมีการเพิ่มสคริปต์สองบรรทัดข้างล่างนี้

   FIND_PACKAGE(OpenGL REQUIRED)
   FIND_PACKAGE(GLUT REQUIRED)
   

ลงในไฟล์ src/CMakeLists.txt ดังต่อไปนี้

   CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
   PROJECT(sample)

   IF(WIN32)
       SET(__WIN_PLATFORM__ "ON")
   ELSE(WIN32)
       SET(__WIN_PLATFORM__ "OFF")
   ENDIF(WIN32)

   IF(UNIX)
       IF(APPLE)
           SET(__MAC_PLATFORM__ "ON")
           SET(__UNIX_PLATFORM__ "OFF")
       ELSE(APPLE)
           SET(__MAC_PLATFORM__ "OFF")
           SET(__UNIX_PLATFORM__ "ON")
       ENDIF(APPLE)
   ELSE(UNIX)
       SET(__MAC_PLATFORM__ "OFF")
       SET(__UNIX_PLATFORM__ "OFF")
   ENDIF(UNIX)

   FIND_PACKAGE(OpenGL REQUIRED)
   FIND_PACKAGE(GLUT REQUIRED)

   ADD_SUBDIRECTORY(square)

   CONFIGURE_FILE( config.h.in ${CMAKE_SOURCE_DIR}/config.h )

สังเกตเราใส่คำสั่ง FIND_PACKAGE ไว้ก่อนการประกาศ target ต่างๆ ทุก target เนื่องจาก target ที่เราประกาศอาจต้องการใช้ package ที่เราหาในภายหลัง

  1. ให้ target เรียกใช้ package

ก่อนที่เราจะคอมไพล์ target หนึ่งที่เรียกใช้ package หนึ่งได้นั้น เราจะต้องให้ข้อมูลสองประการต่อไปนี้กับคอมไพเลอร์

1. ไดเรคทอรีที่ให้คอมไพเลอร์ไปหาไฟล์ header ของ package

2. ไฟล์ไลบรารีที่คอมไพเลอร์จะต้องลิงก์เข้ากับผลลัพธ์ที่ได้จากการคอมไพล์ไฟล์ของ target

หลังจากใช้ FIND_PACKAGE หาที่อยู่ของ package แล้ว โดยมากข้อมูลไดเรคทอรีที่อยู่ของไฟล์ header นั้นจะอยู่ในตัวแปรชื่อ <ชือ่ package ตัวพิมพ์ใหญ่>_INCLUDE_DIR และข้อมูลไลบรารีที่จะต้องลิงก์จะอยู่ตัวแปรชื่อ <ชื่อ package ตัวพิมพ์ใหญ่>_LIBRARIES ฉะนั้นชื่อตัวแปรที่เก็บข้อมูลเหล่านี้ของ OpenGL และ GLUT ได้แก่ OPENGL_INCLUDE_DIR, OPENGL_LIBRARIES, GLUT_INCLUDE_DIR, และ GLUT_LIBRARIES

เราควรจะบอกข้อมูลข้างต้น**เป็นราย target ไป** เนื่องจาก target แต่ละตัวอาจใช้ package ไม่เหมือนกัน ดังนั้นคำสั่งที่ใช้กำหนดข้อมูลเหล่านี้จึงควรอยู่ใน CMakeLists.txt ของ target แต่ละตัว ในกรณีของโปรแกรม square ไฟล์ CMakeLists.txt ของมันมีเนื้อหาดังต่อไปนี้

   INCLUDE_DIRECTORIES(${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIR})

   ADD_EXECUTABLE(square square.cpp)

   TARGET_LINK_LIBRARIES(square ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES})

คำสั่ง INCLUDE_DIRECTORIES

มีรูปแบบ

   INCLUDE_DIRECTORIES(<ไดเรคทอรี 1> <ไดเรคทอรี 2> ...)

โดยที่เราสามารถใส่ไดเรคทอรีกี่ไดเรคทอรีก็ได้ในวงเล็บ หลังจากสั่งคำสั่งนี้แล้ว คอมไพเลอร์จะคอมไพล์ target ทุก target โดยใช้ไดเรคทอรีที่กำหนดในการค้นหาไฟล์ header (_หมายเหตุ:_ จริงๆ แล้วไม่ใช่ target ทุก target เสียทีเดียว แต่เป็นทุก target ในไฟล์ CMakeLists.txt เดียวกัน หรือไฟล์ CMakeLists.txt อื่นๆ ที่ปรากฏอยู่ในไดเรคทอรีย่อยที่ถูกเพิ่มผ่าน ADD_SUBDIRECTORY กล่าวคือ คำสั่ง INCLUDE_DIRECTORIES ที่ปรากฏใน src/square/CMakeLists.txt จะไม่มีผลต่อ target ที่ประกาศใน src/cube/CMakeLists.txt หรือ src/cube/star/CMakeLists.txt เลย)

คำสั่ง TARGET_LINK_LIBRARIES

มีรูปแบบ

   TARGET_LINK_LIBRARIES(<ชื่อ target> <ไลบรารี 1> <ไลบรารี 2> ...)

โดยที่เราสามารถใส่ไลบรารีลงไปกี่ไลบรารีก็ได้หลังชื่อ target คำสั่ง TARGET_LINK_LIBRARIES นี้เนื่องจากมันต้องใช้ชื่อ target มันจึงถูกเรียกอยู่หลังคำสั่ง ADD_EXECUTABLE หรือ ADD_LIBRARY (ที่เรายังไม่ได้พูดถึง) ซึ่งเป็นตัวสร้าง target เสมอ เมื่อสั่ง TARGET_LINK_LIBRARIES แล้ว target ที่กำหนดจะถูกลิงก์เข้ากับไลบรารีที่เรากำหนดให้

สรุป

หากต้องการใช้ไลบรารี (package) ภายนอกให้

1. สั่ง FIND_PACKAGE(<package>) ใน CMakeLists.txt ของโปรเจค

2. สั่ง INCLUDE_DIRECTORIES(<PACKAGE>_INCLUDE_DIR) ใน CMakeLists.txt ของ target ที่จะต้องใช้ package ก่อนการประกาศ target นั้นด้วย ADD_EXECUTABLE หรือ ADD_LIBRARY

3. สั่ง TARGET_LINK_LIBRARIES(<target> <PACKAGE>_LIBRARIES) ใน CMakeLists.txt ของ target หลังประกาศ target แล้ว