======Shared Library====== =====Shared Library คืออะไร===== Shared Library คือ แหล่งเก็บ subprogram หรือ Procedure/function ที่ได้ถูก compiled เรียบร้อยแล้วและพร้อมให้ถูกเรียกใช้งาน\\ นามสกุลของ Shared Library จะแตกต่างกันไป ขึ้นอยู่กับ Operating System สรุปได้ตามตารางต่อไปนี้\\ ^ Operating System (OS) ^ Library Extension ^ | Window | .dll | | Linux | .so | | MacOS X | .dylib | \\ =====การสร้าง Shared Library===== ใน Lazarus IDE เราสามารถสร้าง Shared Library ไว้ใช้งานสำหรับ Window, Linux และ MacOS X ได้ทั้งหมดครับ โดยการเลือกเมนู \\ File >> New >> Library {{wiki:library_create.png}} นามสกุลของ Shared Library ที่ถูก Compiled แล้วจะเป็นไปตาม OS ที่เราใช้งานในขณะนั้น\\ อย่างไรก็ตาม ในส่วนของรายละเอียดการสร้างและใช้งาน library ในที่นี้จะขอพูดถึงแต่ .dll ละกันครับ\\ \\ **__ข้อควรระวัง__** - เนื่องจาก Shared Library เป็น Code ที่ถูก Complied เรียบร้อยแล้ว ดังนั้นหากท่านใช้ Lazarus IDE เวอร์ชั่น 32bit สร้าง library ขึ้นมาสักอัน Library ดังกล่าวจะไม่สามารถเรียกใช้งานบน Lazarus IDE - 64bit ได้ครับ สำหรับ Lazarus IDE บน Window หลังจากสร้างไฟล์ใหม่ที่เป็น Library ขึ้นมาแล้ว จะมีหน้าต่าง text editor ขึ้นมาให้พิมพ์ code ได้เหมือนกับการสร้างโปรเจคต์แบบ Program หรือ Simple Program ครับ แต่ต่างกันคือจะมีคำว่า Library ขึ้นมาแทนคำว่า Program \\ \\ การเขียน Library ไม่ยากครับ ให้ยึดรูปแบบดังนี้ครับ library LibraryName; {$mode objfpc}{$H+} uses SysUtils, Classes; function FunctionName(Arg1, Arg2:real):real; {Calling Convention} begin result := Arg1 + Ag2; end; exports FunctionName; begin end. หลังจากที่เราเขียน code ลงไปในไฟล์ Library แล้ว ก็ให้ไปที่เมนูแล้วเลือก Run >> Compile เมื่อทำการ compile เสร็จเรียบร้อย เราจะได้ไฟล์ .dll ที่พร้อมใช้งานครับ ให้นำไฟล์ .dll ไปไว้ใน folder เดียวกับไฟล์ Project เราได้เลย **ตัวอย่าง** การสร้าง Library ชื่อว่า Pipe เพื่อเก็บฟังก์ชั่นคำนวณหาพื้นที่หน้าตัดท่อ\\ library Pipe; {$mode objfpc}{$H+} uses SysUtils, Classes, Math; function PipeArea1(const DOut,DIn : double) : double; {Calling;} begin result := (PI/4)*(DOut*DOut - DIn*DIn); end; function PipeArea2(const DOut,tw : double) : double; {Calling;} begin result := (PI/4)*(DOut*DOut - (DOut-2*tw)*(DOut-2*tw)); end; exports PipeArea1,PipeArea2; begin end. \\ \\ =====การเรียกใช้งาน Shared Library===== การเรียกใช้งาน Shared Library สามารถเรียกได้ 2 แบบ ดังนี้ *Static Calling คือการเรียกทุก function จาก Library มาเก็บไว้ในโปรแกรมหลักตั้งแต่ต้น ยกตัวอย่าง เช่น เราต้องการใช้งาน function ทั้งหมด 5 ตัว ทุกครั้งที่มีการรัน .exe function ทั้ง 5 ตัวดังกล่าว ก็จะถูกเรียกมาเก็บในโปรแกรมเราตั้งแต่ต้น *Dynamic Calling คือการเรียกใช้ function จาก Library เฉพาะตัวที่ถูกใช้งานจริง ไม่ได้เรียกมาใช้ทั้งหมดแต่แรก อย่างไรก็ตาม วิธีนี้จะยุ่งยากกว่าวิธีแรกพอสมควร เพราะต้องเขียน procedure/function ขึ้นมาเป็นพิเศษเพื่อเรียกใช้งาน function เหล่านั้นทีละตัว ของใครของมัน\\ หมายเหตุ: ทั้งสองแบบ ต้องการไฟล์ .dll แนบไปใช้งานกับไฟล์ .exe ด้วยทุกครั้ง **ตัวอย่าง** การเรียก Library ชื่อว่า Pipe (จาก Example-1) เพื่อเก็บฟังก์ชั่นคำนวณหาพื้นที่หน้าตัดท่อ\\ __ข้อสังเกต__: ชื่อตัวแปร Argument ของในโปรแกรมนี้ ไม่ตรงกับใน .dll \\ *ไฟล์นี้ ใช้ PipeArea1(const D,Di:double):double \\ *แต่ในไฟล์ .dll ใช้ PipeArea1(const DOut,DIn : double) : double; \\ จะเห็นว่าชื่อตัวแปรสามารถตั้งให้ต่างกันได้ เพียงแต่ต้องเป็นชนิดเดียวกัน program StaticCalling_PIPEDLL; //This example demonstrates the simple use of static calling .dll library. //For calling convention, register is used by default. {$mode objfpc}{$H+} uses Classes; function PipeArea1(const D,Di:double):double;{calling;} external 'Pipe.dll'; function PipeArea2(const D,t:double):double;{calling;} external 'Pipe.dll'; Var D,Di,t:double; begin writeln('Example for calling .dll library named Pipe.dll'); D:=0.25; Di:=0.23; t:=0.013; writeln('Calling PipeArea1(D,Di): ',PipeArea1(D,Di)); writeln('Calling PipeArea2(D,t): ',PipeArea2(D,t)); readln; end. **ตัวอย่าง** การเรียก Library ชื่อว่า Pipe (จาก Example-1) เพื่อเก็บฟังก์ชั่นคำนวณหาพื้นที่หน้าตัดท่อ\\ __ข้อสังเกต__: มีดังนี้ \\ *จะต้องมี unit ชื่อ Dynlibs\\ *จะต้องมีการเรียกใช้ function เป็นตัวแปร หรือที่เรียกว่า Procedural Type \\ program DynamicCalling_PIPEDLL; //This example demonstrates the simple use of static/Dynamic calling. //For calling convention, register is used by default. {$mode objfpc}{$H+} uses Classes,Dynlibs; function PipeArea1(const D,Di:double):double;{calling;} external 'Pipe.dll'; //Define procedural type for Dynamic Calling type TMyDLL = function(const D,Di:double):double;{calling;} Var D,Di:double; function CallDLib(const D,Di:double):double; var MyHandle: TLibHandle; MyDLLFunc: TMyDLL; begin MyHandle := SafeLoadLibrary('Pipe.dll'); if MyHandle<>0 then begin MyDLLFunc := TMyDLL(GetProcedureAddress(MyHandle,'PipeArea1')); if Assigned(MyDLLFunc) then result:=MyDLLFunc(D,Di); end else result:=10; FreeLibrary(MyHandle); end; begin writeln('Example for calling .dll library named Pipe.dll'); D:=0.25; Di:=0.23; writeln('Static Calling PipeArea1(D,Di): ',PipeArea1(D,Di)); writeln('Dynamic Calling PipeArea1(D,Di): ',CallDLib(D,Di)); readln; end. \\ \\ =====การระบุ Calling Convention===== ในทุกๆการใช้งาน Shared Library จะต้องมีการระบุรูปแบบการเรียกใช้งาน หรือ Calling Convention เสมอ โดย Calling Convention สำหรับ FPC สรุปได้ดังนี้ ^ Keyword ^ Variable Push Order ^ Stack Cleaned by ^ | default | LTR | Callee | | register | LTR | Callee | | cdecl | RTR | Caller | | interupt | RTR | Callee | | pascal | LTR | Callee | | safecall | RTR | Callee | | stdcall | RTR | Callee | | oldfpccall | RTR | Callee | จากตารางข้างบนอธิบายถึงความต่างของแต่ละ Calling Convention มีทั้งเรื่องทิศทางการใส่ค่าตัวแปร และการกำจัดค่าของตัวแปรใน Ram จะเห็นได้ว่า default, register และ pascal จะมีการใส่ค่าตัวแปรแบบ "ซ้ายไปขวา" หรือ LTR (Left-to-Right) ในขณะที่ cdecl, interupt, safecall, stdcall และ oldfpccall จะใส่ค่าตัวแปรแบบ "ขวาไปซ้าย" หรือ RTL (Right-to-Left) \\ \\ **__ข้อควรระวัง__** - หากเราระบุ Calling Convention ใน .dll ไม่ตรงกับการเรียกใช้ในไฟล์ .exe ก็อาจทำให้เกิดการใส่ตัวแปรในแต่ละ Argument ของ Procedure/Function สลับกันได้ จะนำไปสู่การคำนวณที่ผิดพลาดอย่างแน่นอน \\ \\ **__หมายเหตุ__** - การไม่ระบุ Calling Convention นั้น จะหมายถึงการเรียกใช้งานแบบ default ซึ่ง default calling convention สำหรับ FPC คือ register **ตัวอย่าง** การเรียก Library ชื่อว่า Pipe เพื่อเก็บฟังก์ชั่นคำนวณหาพื้นที่หน้าตัดท่อ\\ __ข้อสังเกต__: เราจะใช้ Calling ที่ชื่อว่า stdcall ซึ่งจะต้องมีการระบุ Calling Convention ทั้งใน .dll และโปรแกรมหลัก \\ library Pipe; {$mode objfpc}{$H+} uses SysUtils, Classes, Math; function PipeArea1(const DOut,DIn : double) : double; stdcall; begin result := (PI/4)*(DOut*DOut - DIn*DIn); end; function PipeArea2(const DOut,tw : double) : double; stdcall; begin result := (PI/4)*(DOut*DOut - (DOut-2*tw)*(DOut-2*tw)); end; exports PipeArea1, PipeArea2; begin end. program StaticCalling_PIPEDLL; //This example demonstrates the simple use of static calling .dll library. //For calling convention, register is used by default. {$mode objfpc}{$H+} uses Classes; function PipeArea1(const D,Di:double):double; stdcall; external 'Pipe.dll'; function PipeArea2(const D,t:double):double; stdcall; external 'Pipe.dll'; Var D,Di,t:double; begin writeln('Example for calling .dll library named Pipe.dll'); D:=0.25; Di:=0.23; t:=0.013; writeln('Calling PipeArea1(D,Di): ',PipeArea1(D,Di)); writeln('Calling PipeArea2(D,t): ',PipeArea2(D,t)); readln; end. \\ \\ =====References===== https://www.freepascal.org/docs-html/prog/progsu7.html \\ https://www.freepascal.org/docs-html/prog/progse22.html#x173-1760006.3