1. Tải bản cài đặt AutoIT mới nhất

    Chào Khách. Nếu bạn mới tham gia và chưa cài đặt AutoIT.
    Vui lòng vào topic trên để tải bản AutoIT mới nhất nhé
    Dismiss Notice
  2. Quy định và nội quy

    Chào Khách. Vui lòng đọc kỹ nội quy và quy định của diễn đàn
    Để tránh bị ban một cách đáng tiếc nhé!
    Dismiss Notice
  3. Hướng dẫn chèn mã AutoIT trong diễn đàn

    Chào Khách. Vui lòng xem qua bài viết này
    Để biết cách chèn mã AutoIT trong diễn đàn bạn nhé :)
    Dismiss Notice

Hướng dẫn Build DLL & hàm DllCall

Thảo luận trong 'Hướng dẫn - Bài tập nâng cao' bắt đầu bởi wuuyi123, 15/6/18.

  1. wuuyi123

    wuuyi123 Thành viên
    • 18/23

    Tham gia ngày:
    18/6/16
    Bài viết:
    54
    Đã được thích:
    98
    [​IMG]

    Trong post này, mình sẽ hướng dẫn các nội dung sau:
    - Cấu trúc dữ liệu C trong AutoIt
    - Xây dựng một file DLL(*) đơn giản bằng C (bạn phải biết C nhé)
    - Cách sử dụng hàm DllCall trong AutoIt

    (*) DLL này thuộc loại binary đã qua biên dịch, không phải DLL bên .NET nhé.


    1. Cấu trúc dữ liệu C trong AutoIt

    - Có thể nói AutoIt là ngôn ngữ kịch bản sánh ngang với Python hay Ruby về độ tương tác với CAPI (ở đây chỉ nói về WinAPI thui) hay sử dụng C ngay bên trong nó. =))

    - Hiểu đơn giản hơn thì AutoIt có hỗ trợ Struct (cấu trúc) và Pointer (con trỏ). Nếu một chương trình AutoIt sử dụng pointer thay cho biến thông thường thì sẽ nhanh & tiện lợi hơn. Nhưng sẽ rất nguy hiểm nếu như sử dụng không đúng cách, có thể dẫn đến crash, dump memory hay tràn bộ nhớ. :)


    Struct:
    - Kiểu dữ liệu ở đây chỉ dành cho struct thôi nhé. Để xem chi tiết bạn gõ hàm DllStructCreate trong SciTE rồi nhấn F1 hoặc truy cập link này.

    - Để tạo một struct, ta sử dụng hàm DllStructCreate, hàm có 2 tham số. Tham số thứ nhất là chuỗi chứa nhiều kiểu dữ liệu & tên phần tử liên tục cách nhau bởi dấu chấm phẩy như sau:
    "<type> <name>[<[array]>];..."​

    Mã (AutoIt):

    Local $t = DllStructCreate("int a;int b;char c[5]")
    $t.a = 2 ; gán a = 2
    DllStructSetData($t, "b", 4.5) ; do b là số nguyên nên chỉ nhận giá trị là 4
    $t.c = "abcdwjwif" ; c chỉ có 5 phần tử nên chỉ nhận chuỗi "abcdw"

    $s = DllStructGetData($t, "c", 4) ; thay "c" là 3 cũng được,
    ; vì nó ở vị trí thứ 3 trong struct; mảng thứ 4 =>> giá trị là "d"
    ; mảng trong struct bắt đầu từ 1 nhé
     
    Ví dụ ở code trên: tạo một struct với 3 phần tử (thực chất là 7 đấy). a và b thuộc kiểu số nguyên int, c thuộc kiểu char, array 5 ở đây có thể hiểu như array trong AutoIt là c[5], xét theo chuỗi thì mỗi phần tử mảng c chỉ chứa được một kí tự, nhưng cả mảng c sẽ tính là một chuỗi có độ dài 5.

    - Tham số thứ hai của hàm DllStructCreate là biến lưu con trỏ đến một struct, tất nhiên là có thể sử dụng con trỏ lấy từ chương trình C. Sử dụng con trỏ để lắp đầy struct (fill) và bạn có thể thay đổi giá trị của biến sở hữu con trỏ đó thông qua struct. Việc fill struct cũng như phép trừ vậy, nếu struct có nhiều phần tử fill một struct ít phần tử thì struct sau chỉ sử dụng các phần tử đã fill đủ từ struct đầu (thông qua pointer, hai struct phải giống nhau về từng kiểu dữ liệu hoặc các phần tử ít nhất giống nhau về size).

    - Để lấy pointer của một sctruct thì ta dùng hàm DllStructGetPointer, nên nhớ pointer thực chất chỉ là một số nguyên không dấu lưu địa chỉ dữ liệu trên bộ nhớ.

    Mã (AutoIt):

    Local $tagMyStruct = "int a;bool b" ; tạo chuỗi tag struct cho tiện hơn

    Local $st1 = DllStructCreate($tagMyStruct)
    $st1.a = 10
    $st1.b = True ; => 1 (bool trong này là int nhé, nếu dùng boolean thì chỉ có mỗi true & false)

    Local $pst1 = DllStructGetPtr($st1) ; lấy pointer của struct $st1

    Local $st2 = DllStructCreate($tagMyStruct, $pst1) ; fill struct $st2 bằng pointer của struct $st1
    $st2.a = 9999
    $st2.b = False ; => 0

    MsgBox(0, '', $st1.a & @CRLF & $st1.b) ; kiểm tra thử
     

    2. DLL là gì? (theo Microsoft)

    - DLL viết tắt của Dynamic Link Library - thư viện liên kết động hay thường gọi là môdun, là một thư viện mã & dữ liệu có thể được sử dụng bởi nhiều chương trình cùng một lúc. Điều này giúp khuyến khích sử dụng lại mã và sử dụng bộ nhớ hiệu quả.

    Danh sách sau đây mô tả một số lợi ích được cung cấp khi sử dụng chương trình DLL:
    • Sử dụng ít tài nguyên
      Khi nhiều chương trình sử dụng cùng một thư viện hàm, một DLL có thể giảm sao chép mã được tải vào đĩa và bộ nhớ vật lý. Điều này có thể ảnh hưởng đáng kể hiệu suất của không chỉ chương trình đang chạy trên nền còn các chương trình khác đang chạy trên hệ điều hành Windows.

    • Khuyến khích kiến trúc mô-đun
      Một DLL giúp khuyến khích phát triển các mô-đun chương trình. Điều này giúp bạn phát triển chương trình lớn cần nhiều phiên bản ngôn ngữ hoặc chương trình yêu cầu kiến trúc module. Ví dụ về một chương trình mô-đun là một chương trình kế toán có nhiều mô-đun có thể được tự động tải tại thời gian chạy.

    • Cài đặt và triển khai dễ dàng
      Khi cập nhật các chức năng trong DLL mà không cần relinked (tức chỉ thay đổi code trong các hàm) thì sẽ không ảnh hưởng đến chương trình sử dụng DLL. Điều này giúp tiết kiệm thời gian & việc cập nhật phần mềm trở nên dễ dàng hơn.

    3. Viết DLL


    - Việc code & build một file DLL không quá khó khăn
    - Ngoài C ra, hầu hết các ngôn ngữ biên dịch ra C hoặc sử được CAPI và tương tác được với WinAPI đều có thể sử dụng để viết DLL, chẳng hạn như C++, Go, Rust, Java (JNI), FreeBasic, VisualBasic (không phải VB.Net nhé)...

    Trong phần này mình sẽ sử dụng C để viết DLL.
    - Đối với trình biên dịch C thì cũng không khó tìm, bạn có thể sử dụng MinGW, Cygwin, Tiny C Compiler,... Nếu bạn sử dụng các IDE như DevC++, Code::Blocks, Visual Studio thì khi cài cũng có kèm theo C compiler.

    - Cấu trúc code chính sẽ như sau:

    Mã (Javascript):

    #include <windows.h>

    BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
    {
        switch (reason) {
            case DLL_PROCESS_ATTACH:
            case DLL_PROCESS_DETACH:
            case DLL_THREAD_ATTACH:
            case DLL_THREAD_DETACH:
                break;
        }

        return 1;
    }

    int __declspec(dllexport) add(int a, int b)
    {
        return a + b;
    }
    - Thư viện <windows.h> là thư viện chuẩn bắt buộc, nó có sẵn trong trình biên dịch.
    - Hàm DllMain thì có thể có hoặc không, hàm này chủ yếu để quản lý khi nào chương trình gọi DLL, khi nào chương trình đóng DLL... thông qua tham số thứ hai là reason. Tham số thứ nhất instance có thể hiểu như vị trí của DLL trong bộ nhớ, nơi lưu các resources (icon, manifest, text, rcdata...) bên trong DLL vừa load vào bộ nhớ sau khi gọi DLL.
    - Hàm add (ví dụ) là một hàm chức năng trong DLL, phía trước nó có __declspec(dllexport) (hoặc extern ở đầu dòng hay extern "C" đối với C++ đều được) giúp xuất hàm ra bên ngoài ở dạng "shared".

    - Bạn lưu code trên vào file đặt tên là test.c rồi mở cmd lên, chạy dòng batch sau để biên dịch nó.

    Mã (Text):
    gcc -shared -o test.dll test.c
    - gcc ở đây mình sử dụng trình biên dịch GNU (đối với các trình biên dịch khác đều có thể dùng được dòng trên). -shared để báo cho compiler biết la bạn sẽ build ra DLL, -o để đặt tên cho file xuất ra, và cuối cùng là file source code test.c (đặt vị trí nào cũng được, nhưng phải đúng quy tắc).
    - Sau khi compile thành công, bạn sẽ thấy file test.dll xuất hiện, nếu dùng DLL Export sẽ thấy như sau:

    [​IMG]

    - Lưu ý thêm: sử dụng DLL vẫn có thể chạy đa luồng một cách an toàn nhé (đa luồng code C, nếu chèn code AutoIt hay gọi hàm AutoIt trong C sẽ sinh lỗi).

    Mã (Javascript):

    DWORD __stdcall newThreadProc(void* param)
    {
        MessageBoxA(NULL, "I'm in another thread.", "Hello", 0);
        return 0;
    }

    void __declspec(dllexport) newThread()
    {
        CreateThread(NULL, 0, newThreadProc, NULL, 0, NULL);
    }
     

    4. Gọi DLL


    - Mình sẽ sử dụng tiếp tục file DLL ở trên luôn.
    - Nếu gọi DLL xuyên suốt từ đầu đến kết thúc chương trình (load nó vào stream) thì sử dụng hàm DllOpen, còn không thì bạn sử dụng DllCall với tên file DLL cũng được.
    - Gọi một hàm trong DLL thì hàm đó sẽ chạy nối tiếp chương trình, khi hàm thoát thì chương trình lại tiếp tục.

    Mã (AutoIt):

    Local $dll = DllOpen("test.dll") ; open dll

    Local $v = DllCall($dll, 'int:cdecl', 'add', 'int', 4, 'int', 6) ; gọi hàm add,
    ; nên nhớ thêm vào kiểu dữ liệu trả về <kiểu dữ liệu>:cdecl nhé (__cdecl)
    ; đối với các kèm theo __stdcall trước hàm thì không cần nó

    MsgBox(0, '', $v[0])

    DllCall("user32.dll", 'bool', 'MessageBoxW', 'hwnd', Null, 'wstr', 'Hello World!', 'wstr', 'Test', 'uint', 0)
    ; các dll system sẽ dùng __stdcall, sau khi đóng hộp thoại thì chương trình mới thoát đc
     

    - Thử biên dịch code đa luồng ở trên (cuối mục 3) rồi chạy code sau xem nào.

    Mã (AutoIt):

    Local $dll = DllOpen('test.dll') ; vì có sử dụng thread nên bắt buộc phải dùng DllOpen

    DllCall($dll, 'none:cdecl', 'newThread')
    DllCall($dll, 'none:cdecl', 'newThread')
    DllCall($dll, 'none:cdecl', 'newThread')

    MsgBox(0, '', 'AutoIt') ; đóng hộp thoại này là thoát luôn đấy
     
    [​IMG]


    Một vài rắc rối khi dùng DllCall:
    - Kiểu void thì bạn sử dụng none nhé.
    - Kiểu dữ liệu là một pointer thì để ptr, nếu pointer struct AutoIt thì có thể để là struct* rồi bỏ nguyên tên biến chứa struct vào.

    Mã (AutoIt):

    Local $tagPOINT = 'int x;int y'
    Local $pt = DllStructCreate($tagPOINT)

    DllCall('user32.dll', 'bool', 'GetCursorPos', 'ptr', DllStructGetPtr($pt))
    ; // hoặc
    DllCall('user32.dll', 'bool', 'GetCursorPos', 'struct*', $pt)

    MsgBox(0, '', $pt.x)
     

    - Đối với DLL C++ trả về class thì nên tạo thêm một DLL C++ rồi xử lý class đó sang hàm, rất dễ.

    Mã (Javascript):

    class ABC
    {
    public:
        ABC() {}
        ~ABC() {}

        int value;

    };

    ABC* ABC_New()
    {
        return new ABC;
    }

    int ABC_GetValue(ABC* self)
    {
        return self->value;
    }
     
    - Kiểu LPSTR, LPCSTR, LPWSTR, LPCWSTR... (nó là con trỏ đến chuỗi, W chỉ chuỗi unicode). Xử lý như sau:

    Mã (AutoIt):

    #cs
        Hàm MessageBoxW trong user32.dll trên MSDN.com

        int WINAPI MessageBoxW(
          _In_opt_ HWND    hWnd,
          _In_opt_ LPCWSTR lpText,
          _In_opt_ LPCWSTR lpCaption,
          _In_     UINT    uType
        );

    #ce


    ; // lấy pointer của chuỗi
    $s = "Hellô" ; tạo một biến lưu chuỗi
    $sts = DllStructCreate('wchar value[' & StringLen($s) + 1 & ']')
    $sts.value = $s
    ; chuỗi unicode nên length phải + 1
    $psts = DllStructGetPtr($sts, 1) ; => pointer

    ; dùng pointer đến chuỗi cho cho hàm này cũng quan trong lắm đấy,
    ; nếu bạn chuyên về bộ nhớ sẽ biết nó ntn =))
    DllCall('user32.dll', 'bool', 'MessageBoxW', 'hwnd', Null, 'ptr', $psts, 'wstr', '', 'uint', 0)
     
    ----
    Việc sử dụng DLL sẽ giúp bạn cải thiện được hiệu năng của chương trình & bảo mật tốt hơn (bạn có thể cho DLL làm hết mọi thứ).

    Chúc bạn thành công!
     
    Chỉnh sửa cuối: 16/6/18
  2. wuuyi123

    wuuyi123 Thành viên
    • 18/23

    Tham gia ngày:
    18/6/16
    Bài viết:
    54
    Đã được thích:
    98
    Một ví dụ thực tế đó là ImageSearch, một thư viện tìm kiếm hình ảnh khá phổ biến, được viết dưới dạng chuẩn hàm nên AutoIt dễ dàng sử dụng thông qua DllCall.

    Hay project CefAu3 của mình, build một DLL wrapper để đơn giản các class giúp tạo Chromium window dễ dàng hơn, sau đó chuyển class về hàm đành cho AutoIt.
    Source: https://github.com/wy3/cefau3
     
  3. Huân Hoàng

    Huân Hoàng Administrator Thành viên BQT Administrator Super Moderator
    • 93/113

    Tham gia ngày:
    29/9/15
    Bài viết:
    642
    Đã được thích:
    1,181
    Tuyệt vời, điểm 10 cho chất lượng...mỗi tội iêm biết C mà C nó không biết iêm :((
     
    Quản lượng thích bài này.
  4. Quản lượng

    Quản lượng qlf
    • 18/23

    Tham gia ngày:
    27/4/17
    Bài viết:
    74
    Đã được thích:
    39
    Hồi xưa em có ý định làm 1 ứng dụng gì đó cần kết hợp c++ và autoit. Cụ thể là autoit sẽ làm front-end còn c++ sẽ làm back-end. Hồi đó bí không biết làm sao :)))
    Bây giờ thấy bài viết của bác chất lượng quá phải vào like và cmt :)))
     
  5. wuuyi123

    wuuyi123 Thành viên
    • 18/23

    Tham gia ngày:
    18/6/16
    Bài viết:
    54
    Đã được thích:
    98
    Mình viết bài này cũng là việc nên làm thôi =))
    Đơn giản vì một số lý do sau:
    - Dùng C/C++ để viết DLL backend cho AutoIt sẽ giúp đạt được hiệu năng tối đa trên AutoIt.
    - Sử dụng ngôn ngữ scripting chính là tái sử dụng lại các hàm binary, call chúng trên AutoIt sẽ tiết kiệm nhiều thời gian hơn là dùng C/C++ (compile time, hard code).
    - Trong AutoIt thì array (mảng) không hỗ trợ references (tham chiếu, các mảng không có liên hệ với nhau, phép gán mảng chỉ mang tính chất copy) nên vô cùng bất tiện đối với các chương trình lớn. Trong khi DllStruct thì ref dễ dàng, ngoài ra có thể dùng dynamic allocate (malloc, calloc, realloc...) để tạo data và dùng DllStruct để access interface.
     
    Quản lượng thích bài này.
  6. wuuyi123

    wuuyi123 Thành viên
    • 18/23

    Tham gia ngày:
    18/6/16
    Bài viết:
    54
    Đã được thích:
    98
    Một vài chuyển đổi type cơ bản từ C/C++ sang AutoIt

    1 byte:
    - Giá trị logic boolean: bool (hoàn toàn lowercase nhé)
    => dùng "boolean" hoặc "byte"

    - Nhóm số nguyên 8bit: char, int8_t, INT8...
    => dùng "char"

    - Nhóm số nguyên không dấu 8bit: unsigned char, uint8_t, UINT8...
    => dùng "byte"

    2 byte:
    - Nhóm số nguyên 16bit: short, int16_t, INT16, SHORT...
    => dùng "short"

    - Nhóm số nguyên không dấu 16bit: unsigned short, uint16_t, wchar_t, UINT16, WCHAR...
    => dùng "ushort" hoặc "word"

    4 byte:
    - Nhóm số nguyên 32bit: int, int32_t, INT, INT32, __int32, BOOL, long, LONG...
    => dùng "int" hoặc "bool"

    - Nhóm số nguyên không dấu 32bit: unsigned int, uint32_t, UINT, UINT32, unsigned __int32, ULONG...
    => dùng "uint"

    - Số thực 32bit: float
    => dùng "float"

    8 byte:
    - Nhóm số nguyên 64bit: long long int, int64_t, INT64,...
    => dùng "int64"

    - Nhóm số nguyên không dấu 64bit: unsigned long long int, uint64_t, UINT64, DWORD64...
    => dùng "int64"

    - Nhóm số thực 64bit: double, long double
    => dùng "double"

    8/16 byte (dựa trên 32bit/64bit hay kiến trúc x86/x64 (hay x86_64)):
    - Nhóm con trỏ (pointer): intptr_t, uintptr_t, HWND, HBITMAP, HANDLE, INT_PTR, UINT_PTR.... nói chung mọi kiểu pointer hoặc có thêm dấu sao (*).
    => dùng "ptr" hoặc "handle" ("hwnd" nên dùng riêng cho handle cửa sổ).

    - Nhóm size_t, SIZE_T
    => dùng "lresult"

    - Riêng chuỗi thì nên nhớ rằng, chuỗi thuần trong AutoIt mà đưa vào DllCall thì phải dùng "wstr" nếu đầu ra bên C/C++wchar_t*, WCHAR*, LPWSTR, LCPWSTR... (unicode); "str" cho char*, const char*, CHAR*, LPSTR, LPCSTR... (ascii)
    - Nếu trong AutoIt đã có pointer của chuỗi thì phải dùng "ptr" (nếu "str" hay "wstr" thì nó sẽ cast đoạn pointer đó thành string).

    Lưu ý: chỉ nên dùng MSVC (Visual C++ để build) sẽ cho hiệu quả tốt nhất, vì các compiler khác nhau có thể sẽ định nghĩa các type khác nhau (không theo chuẩn mà AutoIt đang dùng - VC++), chẳng hạn long double trong GNU GCC là 16byte, long int là 8byte trong khi đó, lần lượt trong VC++ là 8 và 4byte.
     
    Quản lượng and Huân Hoàng like this.

Chia sẻ trang này

Đang tải...