Hướng dẫn python run c file - python chạy tập tin c

Nếu đã từng dùng qua các thư viện liên quan đến toán học của Python như

extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
5, các bạn sẽ để ý thấy các nó có tốc độ xử lý rất nhanh. Điều này là do một phần của package này được viết bằng C/C++ và phần code Python gọi những phần đó để chạy nên tốc độ chạy khi xử lý của
extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
5 khá là nhanh.

Hướng dẫn python run c file - python chạy tập tin c

Vậy làm sao để có thể gọi được mã C++ từ trong Python? Trong Python, có khá nhiều thư viện có thể giúp bạn đạt được điều này. Bài viết sau đây sẽ hướng dẫn bạn dùng thư viện

extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
7 của Python để có thể call function được code bằng C++.

Lợi ích của việc dùng hàm C++ trong Python

Trước hết, đây là những lợi ích của việc call function được viết bằng C++ trong Python:

  • Tận dụng code C++ đã có sẵn: Nếu bạn đã có sẵn một hàm/thư viện được viết bằng C++ và đã được test kỹ càng, bạn có thể tận dụng chúng được luôn thay vì phải viết toàn bộ sang Python. Nó sẽ giúp bạn tiết kiệm được đáng kể thời gian để code.: Nếu bạn đã có sẵn một hàm/thư viện được viết bằng C++ và đã được test kỹ càng, bạn có thể tận dụng chúng được luôn thay vì phải viết toàn bộ sang Python. Nó sẽ giúp bạn tiết kiệm được đáng kể thời gian để code.
  • Tăng tốc độ xử lý: Python là ngôn ngữ thông dịch, còn C++ là ngôn ngữ biên dịch, do đó, C++ sẽ có tốc độ nhanh hơn rất nhiều so với Python. Ngoài ra, GIL (Global Interpreter Lock) của Python chỉ cho phép chạy duy nhất một thread tại một thời điểm nên việc dùng thread sẽ không thể tận dụng được các core của CPU. Việc dùng C++ còn giúp loại bỏ được hạn chế này.: Python là ngôn ngữ thông dịch, còn C++ là ngôn ngữ biên dịch, do đó, C++ sẽ có tốc độ nhanh hơn rất nhiều so với Python. Ngoài ra, GIL (Global Interpreter Lock) của Python chỉ cho phép chạy duy nhất một thread tại một thời điểm nên việc dùng thread sẽ không thể tận dụng được các core của CPU. Việc dùng C++ còn giúp loại bỏ được hạn chế này.

1. Chuẩn bị shared library

Để dùng được code C++ trong Python, bạn sẽ cần phải compile nó ra dưới dạng shared library. Trong Windows, đây sẽ là một file có đuôi

extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
8, còn với Linux, nó sẽ có đuôi
extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
9. Trong bài viết này, mình sẽ dùng Windows và compiler MSVC để làm ví dụ.

Trong ví dụ này, mình sẽ xuất một function in ra dãy số Fibonacci đến số thứ

cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
0. Code mẫu này mình sẽ save với đường dẫn
cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
1:

#include 
#include 

extern "C" {
    __declspec(dllexport) int test_func(int n) {
        std::vector f;
        int i;

        f.push_back(0);  // f[0] = 0
        f.push_back(1);  // f[1] = 1

        for (i = 2; i <= n; i++) {
            f.push_back(f[i - 1] + f[i - 2]);
        }
        
        std::cout << "Fibonacci up until " << n << "-th digit: ";
        for (i = 1; i <= n; i++) {
            std::cout << f[i] << " ";
        }
        return f[n];
    }
}

Trong đoạn code trên, block lệnh

cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
2 đơn giản được dùng để tắt cơ chế name mangling của C++ cho các hàm được định nghĩa ở trong. Cụ thể, C++ có hỗ trợ function overloading, nghĩa là ta có thể có nhiều function cùng một tên mà chỉ cần khác kiểu và số lượng tham số được định nghĩa. Tuy nhiên, ngôn ngữ lập trình C lại không hỗ trợ cơ chế này nên cần phải sử dụng nó để code C++ có thể tương thích được với C. Khi compile đoạn code dưới đây, compile sẽ báo lỗi
cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
3.

extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}

Còn đoạn

cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
4 đơn giản được dùng để báo cho compiler biết rằng nó cần export function (với ví dụ trên là function
cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
5) ra một file DLL.

Sau khi có được hàm C++, ta cần compile code ra một file obj. Với MSVC, cú pháp để compile là:

cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"

Sau khi compiler biên dịch ra file

cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
6, ta cần link nó ra file DLL với lệnh sau:

link /DLL /OUT:fibo.dll obj\fibo.obj

Kết quả, ta sẽ được một

cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
7 có hàm
cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
5 để dùng ở ngoài.

2. Dùng shared library trong Python với extern "C" { int test_func(double a) { ... } int test_func(int n) { ... } } 7

Python cung cấp sẵn cho ta thư viện

extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
7 cho phép chúng ta tạo các kiểu dữ liệu tương thích với C và gọi các hàm từ shared library. Chi tiết về các kiểu dữ liệu
extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
7 cho phép tạo có thể xem tại đây.

Đầu tiên, ta cần import

extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
7 để có thể dùng nó. Sau đó, ta sẽ dùng
link /DLL /OUT:fibo.dll obj\fibo.obj
3 để load file
cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
7 ở ví dụ bên trên.
link /DLL /OUT:fibo.dll obj\fibo.obj
3 sẽ đại diện cho library mà chúng ta load và ta có thể dùng nó để gọi các hàm trong file DLL.

from ctypes import *

external_lib = CDLL("fibo.dll")
...

Sau đó, ta có thể gọi luôn hàm

cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
5 của
link /DLL /OUT:fibo.dll obj\fibo.obj
7. Trong ví dụ này, ta có thể pass thẳng một vài kiểu dữ liệu (ví dụ như
link /DLL /OUT:fibo.dll obj\fibo.obj
8 làm tham số của
cl /EHsc /c src\test.cpp /Fo"obj\\fibo.obj"
5) mà không cần phải qua data type của
extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
7. Tuy nhiên, bạn vẫn nên dùng các data type mà
extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
7 cung cấp.

a = external_lib.test_func(6)
print(a)

hoặc

n = c_int(6)
a = external_lib.test_func(6)
print(a)

Đều cho ra output là:

Fibonacci up until 6-th digit: 1 1 2 3 5 8
8

Mặc định, kiểu dữ liệu trả về sẽ là

link /DLL /OUT:fibo.dll obj\fibo.obj
8. Với ví dụ trên, ta sẽ không cần phải code thêm gì cả. Tuy nhiên, với trường hợp dưới đây, ta sẽ cần phải sửa lại code một chút.

// test.cpp
#include 
#include 

extern "C" {
    __declspec(dllexport) float test_func(float a) {
        if (97 <= a && a <= 122) {
            return a - 32;
        }
        return 1000000;
    }
}
# main.py
from ctypes import *

try:
    external_lib = CDLL("test.dll")

    a = external_lib.test_func(6.0)
    print(a)
    b = external_lib.test_func(100.0)
    print(b)
except Exception as e:
    print(e)

Nếu như ta chạy đoạn code Python trên, nó sẽ báo lỗi:

extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
0

Mặc dù giá trị

from ctypes import *

external_lib = CDLL("fibo.dll")
...
3 trong Python có kiểu dữ liệu là
from ctypes import *

external_lib = CDLL("fibo.dll")
...
4, tuy nhiên,
from ctypes import *

external_lib = CDLL("fibo.dll")
...
4 của Python hoàn toàn khác so với
from ctypes import *

external_lib = CDLL("fibo.dll")
...
4 của C++. Khi đó, ta sẽ phải dùng đến
from ctypes import *

external_lib = CDLL("fibo.dll")
...
7 của
extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
7 để chuyển kiểu dữ liệu.

extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
1

Lúc này, đoạn code trên sẽ không bị lỗi convert parameter nữa. Tuy nhiên, kết quả mà hàm trả ra lại không hề đúng chút nào.

extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
2

Như đã nhắc ở trên, việc gọi hàm ngoài sẽ luôn trả về một giá trị

link /DLL /OUT:fibo.dll obj\fibo.obj
8. Việc dùng sai kiểu dữ liệu sẽ ảnh hưởng đến giá trị trả về. Do đó, ta cần định nghĩa lại kiểu dữ liệu trả về của hàm. Việc này được thực hiện bằng cách assign attribute
a = external_lib.test_func(6)
print(a)
0 của function sang một dạng mà
extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
7 hỗ trợ. Với ví dụ trên, đoạn code sau sẽ giải quyết vấn đề đó:

extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
3

Bằng cách đổi data type mà function return về sang

from ctypes import *

external_lib = CDLL("fibo.dll")
...
7, ta sẽ có được kết quả đúng:

extern "C" {
    int test_func(double a) {
        ...
    }

    int test_func(int n) {
        ...
    }
}
4

Như vậy, bài viết trên đã hướng dẫn các bạn gọi được function được viết bằng C++ ở trong Python. Hy vọng bài viết này sẽ hữu ích cho các bạn.