welcom ! Handel home

2024年12月3日 星期二

PyQt5 Ui demo 範例

 顯示介面









pyqt_code.py

===================================================


import sys

from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QFileDialog, QGridLayout

from PyQt5.QtGui import QPixmap

from PyQt5.QtCore import Qt


class ImageDisplayApp(QWidget):

    def __init__(self):

        super().__init__()

        self.init_ui()


    def init_ui(self):

        # 創建按鈕

        self.button1 = QPushButton('選擇 BMP 圖片', self)

        self.button1.setFixedSize(100, 40)

        self.button1.clicked.connect(self.open_file_dialog)


        self.button2 = QPushButton('選擇 BMP 圖片 2', self)

        self.button2.setFixedSize(100, 40)

        self.button2.clicked.connect(self.open_file_dialog_2)


        self.button3 = QPushButton('按鈕 3', self)

        self.button3.setFixedSize(100, 40)


        self.button4 = QPushButton('按鈕 4', self)

        self.button4.setFixedSize(100, 40)


        self.button5 = QPushButton('按鈕 5', self)

        self.button5.setFixedSize(100, 40)


        self.button6 = QPushButton('按鈕 6', self)

        self.button6.setFixedSize(100, 40)

        

        # 創建顯示圖形的標籤

        self.image_label = QLabel('尚未選擇圖片', self)

        self.image_label.setAlignment(Qt.AlignCenter)

        self.image_label.setScaledContents(False)

        self.image_label_2 = QLabel('尚未選擇圖片 2', self)

        self.image_label_2.setAlignment(Qt.AlignCenter)

        self.image_label_2.setScaledContents(False)


        # 創建顯示檔案路徑的標籤

        self.lab1 = QLabel('尚未選擇檔案路徑', self)

        self.lab1.setAlignment(Qt.AlignLeft)

        

        # 設置按鈕佈局

        button_layout = QVBoxLayout()

        button_layout.addWidget(self.button1)

        button_layout.addWidget(self.button2)

        button_layout.addWidget(self.button3)

        button_layout.addWidget(self.button4)

        button_layout.addWidget(self.button5)

        button_layout.addWidget(self.button6)

        button_layout.addStretch()

        

        # 設置圖片佈局,兩個圖像標籤垂直顯示且不重疊

        image_layout = QVBoxLayout()

        image_layout.addWidget(self.lab1)

        image_layout.addWidget(self.image_label)

        image_layout.addWidget(self.image_label_2)

        image_layout.addStretch()

        

        # 設置主佈局

        main_layout = QHBoxLayout()

        main_layout.addLayout(button_layout)

        main_layout.addLayout(image_layout)

        

        self.setLayout(main_layout)

        

        # 設置窗口屬性

        self.setWindowTitle('PyQt5 圖片顯示範例')

        self.setGeometry(100, 100, 1000, 600)

        self.show()


    def open_file_dialog(self):

        options = QFileDialog.Options()

        file_name, _ = QFileDialog.getOpenFileName(self, '選擇 BMP 圖片', '', 'BMP Files (*.bmp);;All Files (*)', options=options)

        if file_name:

            pixmap = QPixmap(file_name)

            self.image_label.setPixmap(pixmap)

            self.image_label.setFixedSize(pixmap.size())

            self.lab1.setText(f'選擇的檔案路徑: {file_name}')


    def open_file_dialog_2(self):

        options = QFileDialog.Options()

        file_name, _ = QFileDialog.getOpenFileName(self, '選擇 BMP 圖片 2', '', 'BMP Files (*.bmp);;All Files (*)', options=options)

        if file_name:

            pixmap = QPixmap(file_name)

            self.image_label_2.setPixmap(pixmap)

            self.image_label_2.setFixedSize(pixmap.size())


# 主程序入口

if __name__ == '__main__':

    app = QApplication(sys.argv)

    window = ImageDisplayApp()

    sys.exit(app.exec_())


Ubuntu PyQt5 Design TOOL install

如何在Linux Ubuntu Desktop安裝Qt Designer,用來設計python GUI使用者介面程式

ubuntu的版本:20.04.1 (LTS) 1. 確認python3已可以work


2. 安裝PyQt5

sudo apt-get install python3-pyqt5


3. 安裝Qt Designer

sudo apt-get install qttools5-dev-tools

sudo apt-get install qttools5-dev


4. 啟動Qt Designer

cd /usr/lib/x86_64-linux-gnu/qt5/bin

./designer


5. 將透過Qt Designer設計的ui檔,轉換匯出為py檔

pyuic5 /home/userid/workfolder/helloworld.ui -o helloworld.py


如果出現 Command 'pyuic5' not found, but can be installed with:

可以使用以下指令安裝pyuic5 sudo apt install pyqt5-dev-tools

2024年12月2日 星期一

Python 載入 gcc DLL (so) Note

 

動態載入C library (DLL)

從第一次知道python這個語言時,就聽聞python的其中一項強大之處,高彈性:
直接利用python coding方便又快速,真的遇到關鍵的效能瓶頸,再以C語言改寫即可。
CPython能達到這項目的的方法,個人知道有2種:Python/C API和ctypes.

P.S. 當然,如果目的是加速或與其他語言做binding就不只這兩項了。
例如RPython, Cython, …等派生或是擴充語言,但那進來考慮實在有點消化不完。

一來目前主要是想摸熟python本身的特性,
二來由於目前個人感興趣的部分在於CPython和C如何直接溝通,因此其他方式暫時不考慮了。

ctypes

最後稍微比較過後,Python及C之間的API也決定暫時擱置。
以之前經驗來說,這類的API接口大多只要搞懂制式的規格和作用,
剩下來的就是類似將內容填入表格的繁瑣工作。
相較之下,若能在幾乎不動C source code的情形下做到這件事情,個人認為有很大的優勢。

以下是一個用C語言寫成簡單(幾乎無經過修飾)的開方根的程式:

/* File: c_sqrt.c */
doublesqrt(doublek) {
   int i;
   double x=k;
   for(i=0; i<5; i++)
      x = x - (x*x-k)/(2*x); // Newton's Iteration.
   return x;  
}

接著把這個compile成shared library.

$ gcc c_sqrt.c -shared -fPIC -O2 -oc_sqrt.so

需要注意的是,由於個人的電腦都是Linux/OS X等類Unix系的,所以動態函式庫是.so檔。
而在python中,則直接利用ctypes module來讀檔:

import  ctypes
dyn = ctype.CDLL('./c_sqrt.so')  
dyn.sqrt.argtypes = [ctypes.c_double] # default: None
dyn.sqrt.restype  =  ctypes.c_double  # default: None  
                                      # default: c_int
print( dyn.sqrt(5) )

這段程式造出一個CDLL的物件dyn。
利用dyn去讀取sqrt時,會到c_sqrt.so找到sqrt的symbol,然後加到dyn這個物件裡。

然而預設的情況下,一個由CDLL讀入的C函數是無參數、回傳值為整數的函數。
如果直接呼叫dyn.sqrt(5)也只會得到0而已。
必須修改傳入的參數的型別(如第四行那樣,有多少參數就寫進list裡),
restype則是回傳的型別。

若回傳的值是一個結構包裝的東西,
或許還得要再經過宣告一個新的類別來使用:

from ctypes import *
class foo(Structure):
   _fields_=[("val", c_int),  ("dat", POINTER(c_int))]

參考資料:

  1. Python Document: ctypes — A foreign function library for Python
  2. 程式設計遇上小提琴 - Python的進步: ctypes
  3. (參考) Dynamically Loaded (DL) Libraries ,看看C語言中怎麼做到上面的事情.

參考自  ==> 
https://www.gitbook.com/?utm_source=legacy&utm_medium=redirect&utm_campaign=close_legacy

gcc 編譯 函示庫 建立靜態 和 動態 LIB

 

靜態函式庫

靜態函式庫(static library)就是由一些物件檔案(object files)所構成的封裝檔,通常其檔案名稱都會以 lib 開頭,而副檔名則為 .a

使用靜態連結函式庫的好處就是所有的程式都包裝在執行檔中,不會因為缺少函式庫的檔案而不能執行,不過缺點就是這樣的執行檔大小會比較大,而如果函式庫有更新的話,整個執行檔也要跟著重新編譯。

以下我們示範將 sum.c 的內容製作成一個靜態函式庫的步驟。

Step 1
編譯 sum.c 的程式碼:

gcc -c -o sum.o sum.c

Step 2
使用 ar 指令將 sum.o 打包成 libsum.a 這個靜態連結函式庫:

ar -rcs libsum.a sum.o

Step 3
使用 libsum.a 這個靜態連結函式庫:

gcc main.c -L. -lsum -o main_static

也可以直接把 libsum.a 這個靜態連結函式庫檔案放進去編譯:

gcc main.c libsum.a -o main_static

這樣就可以將 main.c 這個程式編譯成 main_static 這個執行檔,而在編譯的過程中就不需要 sum.c 這個原始碼的檔案。

使用靜態連結函式庫所編譯出來的執行檔可以獨立執行,不需要原本的 libsum.a 檔:

ldd main_static
	linux-vdso.so.1 =>  (0x00007fffb0d1a000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f0411e46000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f0412220000)

共享函式庫

共享函式庫(shared library)是在程式實際開始執行時,才會被載入的函式庫,執行檔本身與共享函式庫是分離的,這樣可以讓執行檔的大小比較小,而且未來共享函式庫在更新之後,執行檔也不需要重新編譯,而缺點則是執行檔在執行時就會需要共享函式庫的檔案,如果缺少了共享函式庫的檔案,就會無法執行。

共享函式庫的檔案通常也是以 lib 開頭,但是其副檔名則為 .so,以下是建立共享函式庫的步驟。

Step 1
編譯時加入 -fPIC 參數,產生共享函式庫所需要的 position independant code:

gcc -c -fPIC -o sum.o sum.c

Step 2
使用 gcc 建立共享函式庫:

gcc -shared -Wl,-soname,libsum.so.1 -o libsum.so.1.0.0 sum.o

這裡的 -Wl 是用來將一些參數設定傳給連結器(linker),所以之後的 -soname 等參數就是傳給連結器的參數。

-soname 指定為 libsum.so.1 是代表函式庫的名稱,以 lib 開頭,接著是自己取的名稱,最後加上 .so 與 version 版本號碼,這一個 version 版本號碼所代表的是函示庫的介面版本,如果介面有改變時就會增加 version 版本號碼,以維護相容性的問題。

而最後產生的實際檔案名稱也跟 soname 類似,不過後面多了 minor 與 release 版本號碼,中間的 minor 號碼是用於標示新增加的介面,而最後面的 release 號碼則是用於程式內容的修正(介面不變的情況)。

如果程式使用到這個共享函式庫,則在執行時就會依據 soname 所指定的名稱來尋找函式庫的檔案,如果想要看共享函式庫的 soname 屬性,可以使用 objdump 指令:

objdump -p libsum.so.1.0.0 | grep SONAME
  SONAME               libsum.so.1

建立好共享函式庫之後,要建立一個不含版本號碼的 .so 連結檔,gcc 在連結時所需要的函式庫檔案是這一個:

ln -s libsum.so.1.0.0 libsum.so

另外再建立一個執行時要用的連結檔:

ln -s libsum.so.1.0.0 libsum.so.1

使用共享函式庫來編譯執行檔:

gcc main.c -L. -lsum -o main_dynamic

或是這樣編譯也可以:

gcc main.c libsum.so -o main_dynamic

編譯完成後,要執行時需要指定 LD_LIBRARY_PATH

LD_LIBRARY_PATH=. ./main_dynamic
2.6 + 4.2 = 6.8

這一個 main_dynamic 執行檔在執行時,會需要 libsum.so.1 這一個共享函式庫檔案:

LD_LIBRARY_PATH=. ldd main_dynamic
	linux-vdso.so.1 =>  (0x00007ffc5f1f7000)
	libsum.so.1 => ./libsum.so.1 (0x00007efd0148d000)
	libc.so.6 => /lib64/libc.so.6 (0x00007efd010b3000)
	/lib64/ld-linux-x86-64.so.2 (0x00007efd01690000)

動態載入函式庫

動態載入函式庫(dynamically loaded library)就類似 Windows 的 dll 檔,是等到程式真正要用到時才會載入函式庫,其實作方式是透過 DL 函式庫配合一般的共享函式庫來處理的,以下是將之前製作的 libsum.so.1 共享函式庫改為動態載入的範例。

#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
int main(int argc, char **argv) {
  void *handle;
  double (*sum)(double, double);
  char *error;

  // 動態開啟共享函式庫
  handle = dlopen ("libsum.so.1", RTLD_LAZY);
  if (!handle) {
    fputs (dlerror(), stderr);
    exit(1);
  }

  // 取得 sum 函數的位址
  sum = dlsym(handle, "sum");
  if ((error = dlerror()) != NULL)  {
    fputs(error, stderr);
    exit(1);
  }

  // 使用 sum 函數
  double a = 2.6, b = 4.2, c;
  c = sum(a, b);
  printf("%.1f + %.1f = %.1f\n", a, b, c);

  // 關閉共享函式庫
  dlclose(handle);
  return 0;
}

將這段程式碼儲存為 main_dl.c 之後,按照一般的方式進行編譯:

gcc main_dl.c -ldl -o main_dl

在使用上跟共享函式庫差不多,一樣要指定 LD_LIBRARY_PATH

LD_LIBRARY_PATH=. ./main_dl
2.6 + 4.2 = 6.8

表面上看起來跟一般的共享函式庫類似,不過這種動態載入函式庫方式與共享函式庫有很大的不同,共享函式庫是在程式一開始執行時就要載入(不管實際上有沒有使用到),而動態載入函式庫的做法則是可以在真正需要用到時才載入(如果沒用到就可以不需要載入)。

參考資料:René NyffeneggerTLDPMURMURING

ubuntu gcc 安裝 Note


# ==ubuntu 安裝 gcc ===

# 系統套件更新檢查

$sudo apt update && sudo apt upgrade

#透過 apt 裝 gcc

$sudo apt install gcc

#安装 build-essential 软件包,该软件包包含 GCC 及一系列开发工具,如 make、g++ 和dpkg-dev。

$sudo apt install build-essential

#要验证 GCC 编译器是否已成功安装,可以使用以下命令查看 GCC 版本

$gcc --version

#=== hello.c 編譯測試 gcc =====

$nano hello.c

  #include <stdio.h>


int main(void){
	printf("Hello, World!\n");
	return 0;
}


# 編譯 C 程式
gcc hello.c
# 執行編譯好的程式
./a.out
# 執行編譯好的程式
./a.out
Hello, world!

# 編譯 C 程式,指定輸出檔名
gcc -o hello hello.c

# 執行編譯好的程式
./hello

只編譯不連結

GCC 預設會將 C 的原始碼編譯並連結,產生執行檔,若想讓編譯器只進行編譯、不要連結,可加上 -c 參數,這樣就會建立一個 object 檔:

# 僅編譯、不連結,建立 obj 檔案
gcc -c hello.c

執行這行之後,就會產生一個 hello.o 檔案,後續若要進行連結,就可以使用這個 object 檔:

# 連結產生執行檔
gcc -o hello hello.o

通常在大型的專案中,都會將編譯與連結兩個動作拆開,以下是一個典型的例子:

# 編譯個別 C 檔案
gcc -c a.c
gcc -c b.c
gcc -c c.c

# 連結
gcc -o myapp a.o b.o c.o

這樣作除了可讓程式碼方便管理之外,也可以加快編譯的速度,假設我們在開發過程中,更改了 b.c 的內容,在重新編譯時就只要編譯 b.c,然後即可進連結,省去重新編譯 a.c 與 c.c 的時間。

最佳化

GCC 可以根據 CPU 的架構,進行最佳化處理,編譯出效能更好的執行檔,可用的選項有 -O(跟 -O1 相同)、-O2 與 -O3,數字越高代表最佳化的程度越高,許多專案在編譯正式版的時候,都會使用 -O2 進行最佳化。

# 進行最佳化
gcc -O2 -o hello hello.c

標頭檔與函式庫路徑

在編譯程式時,編譯器需要許多標頭檔(*.h 檔案)來編譯原始碼,而連結的時候則會需要一些函式庫(*.a*.so 等)才能進行連結,但是編譯器只會自動引入一些系統預設的檔案,在大型專案中開發者會需要指定許多額外的標頭檔與函式庫位置,這樣才能讓編譯器順利編譯與連結。

編譯器會在 include 路徑中,搜尋 C 程式碼中以 #include 所引入的標頭檔,如果需要指定額外的搜尋路徑,可以使用 -I 參數增加搜尋路徑,假設我們有一些標頭檔放在 /home/gtwang/include 目錄下,而要新增這個路徑就可以這樣寫:

# 新增標頭檔搜尋路徑
gcc -I/home/gtwang/include -o hello hello.c
由於標頭檔的名稱都寫在 C 程式碼中,所以檔名都是已知的,只需要告訴編譯器路徑即可進行編譯。

在連結時期所需要函式庫名稱與路徑,可以透過 GCC 的 -l 與 -L 參數來指定。在 Linux 中假設有一個 libsum.a 函式庫放在 /home/gtwang/lib 目錄下,若要將其納入連結,則可以這樣寫:

# 納入指定的函式庫
gcc -lsum -L/home/gtwang/lib -o hello hello.c