雖然Dart 語言本身支援跨平台的編譯方式,但在實務開發時還是不免需要使用外部非Dart語言所提供的函式庫進行功能開發且由於C 語言是最為廣泛且通用的程式語言,因此Dart語言也有提供支援與C語言函式庫互通性的方式;本篇主要是以MSVC作為C的編譯器來實作說明如何引用C語言會遇到的作法。
1. 動態載入C語言函式庫
與一般C語言載入函式庫的方式相同在Dart語言中載入C函式語言函式庫同樣是操作流程:
1) 定義函式指標 2 ) 開啟C函式庫 3) 查找函式位址
#include <windows.h> // 1. Define function pointer for imported function prototypes. typedef BOOL(__cdecl* CreateClassInstanceProc) (__inout HANDLE* ClassHandle); // 2. Open and load DLL HINSTANCE hinstLib = LoadLibrary(TEXT("crosscode_wrapc_lib.dll")); // 3. Find function addresses in loadded DLLs. CreateClassInstanceProc CCIP = (CreateClassInstanceProc) GetProcAddress(hinstLib, "CreateClassInstance");
而在Dart 語言中要完成上述載入函式庫的流程則需要先引用dart:ffi文件,且由於在dart:ffi中定義的NativeType資料格式與Dart中所對應的資料格式不完全對應,因此定義C函式指標需要同時以Dart 資料格式定義函式指標和以NativeType資料格式定義函式指標來進行對照。
import 'dart:ffi' // 1. Define function pointer for imported function prototypes. typedef CreateClassInstanceForNative = Int32 Function(Pointer classHandle); typedef CreateClassInstanceForDarte = int Function(Pointer classHandle); // 2. Open and load DLL final _wrapc_lib = DynamicLibrary.open("crosscode_wrapc_lib.dll"); // 3. Find function addresses in loadded DLLs. final createClassInstanceProc = _wrapc_lib.lookupFunction<CreateClassInstanceForNative, CreateClassInstanceForDarte>('CreateClassInstance');
2. 定義資料結構
在引用C函數庫時常需要使用其定義的相關資結構作為傳遞參數則如何在Dart 語言中定義C函數可接受的資料結構是另一個主要的問題;本節將會自訂包含多個常用資料格式 (wchar_t, array, function pointer ... etc.) 的C語言資料結構並以此資料結構作為參數建立相關的函式來測試Dart語言轉換C語言資料結構的方法。
typedef bool (*CallbackFunc)(double* pDoubleArray); typedef bool (*CopyStructCallbackFunc)(PVOID pStruct); typedef struct _UnrestrictedAlignmentStruct { int intField; TCHAR charField; CallbackFunc funcptrField; float floatField; wchar_t stringField[15]; double dubaryField[10]; } UnrestrictedAlignmentStruct, * pUnrestrictedAlignmentStruct; #pragma pack(push, 1) typedef struct _RestrictedAlignmentStruct { int intField; TCHAR charField; CallbackFunc funcptrField; float floatField; wchar_t stringField[15]; double dubaryField[10]; } RestrictedAlignmentStruct, *pRestrictedAlignmentStruct; #pragma pack(pop)
要在Dart語言中定義符合C函數的資料結構要使用 dart:ffi 文件中所定義的NativeType資料格式,在Dart語言中使用NativeType定義與C語言函式互通的資料結構需要特別指定Dart資料格式與NativeType資料格式的對照格式。
import 'dart:ffi'; typedef CallbackFuncForDart = int Function( Pointer<Double> pDoubleArray); typedef CallbackFuncForFFI = Uint32 Function(Pointer<Double> pDoubleArray); typedef CopyStructCallbackFuncForFFI = Bool Function(Pointer pStruct); class UnrestrictedAlignmentStruct extends Struct { @Int32() external int intField; @Uint16() external int charField; external Pointer<NativeFunction<CallbackFuncForFFI>> funcptrField; @Float() external double floatField; @Array(15) external Array<Int16> stringField; @Array(10) external Array<Double> dubaryField; } @Packed(1) class RestrictedAlignmentStruct extends Struct { @Int32() external int intField; @Int16() external int charField; external Pointer<NativeFunction<CallbackFuncForFFI>> funcptrField; @Float() external double floatField; @Array(15) external Array<Int16> stringField; @Array(10) external Array<Double> dubaryField; }
3. 實作測試函式
為了確認上述定義的資料結構與C語言函式庫是否能正確的傳遞與讀取資料結構的資訊,因此特別針常發生的使用情境定義下列其相應的函式進行驗證與實際執行的方式。
3-1. 記憶體配置的長度
由於在不同的程式語言中定義的資料結構可能因為其資料格式的長度差異而佔用的記憶體配置的大小不同而造成資料在轉換兩種不同的語言中傳遞錯誤;因此定義回傳在C語言函式中資料結構的記憶體配置長度的函式與Dart語言中相同資料結構是否為相同長度。
// Create a data struct in C executables UnrestrictedAlignmentStruct sUnrestrictedAlignmentStruct = { 0 }; // Define the function prototype of the C library typedef int(__stdcall* GetSizeOfUnrestrictedAlignmentStructProc) (HANDLE pClassHandle, UnrestrictedAlignmentStruct sStrut); // Find the the pointer of function in the C library GetSizeOfUnrestrictedAlignmentStructProc getSizeOfUnrestrictedAlignmentStructProc = (GetSizeOfUnrestrictedAlignmentStructProc)GetProcAddress(hinstLib, "GetSizeOfUnrestrictedAlignmentStruct"); // Display the memory size of data structures in executeables and libraries. wprintf(L"Used memory size of an unrestricted alignment struct in execute(%llu) and library(%d)\n", sizeof(sUnrestrictedAlignmentStruct), getSizeOfUnrestrictedAlignmentStructProc(hinstLib, sUnrestrictedAlignmentStruct));
// Create a data struct in Dart executables final sUnrestrictedAlignmentStruct = calloc<UnrestrictedAlignmentStruct>() // Define the function prototype of the C library typedef GetSizeOfUnrestrictedAlignmentStructProcForNative = int Function(Pointer classHandle,RestrictedAlignmentStruct confirmStrut); typedef GetSizeOfUnrestrictedAlignmentStructProcForDart = Int32 Function(Pointer classHandle,RestrictedAlignmentStruct confirmStrut); // Find the the pointer of function in the C library late final getSizeOfUnrestrictedAlignmentStructProc = _wrapc_lib.lookupFunction< GetSizeOfUnrestrictedAlignmentStructProcForDart, GetSizeOfUnrestrictedAlignmentStructProcForNative >('GetSizeOfUnrestrictedAlignmentStructProc'); // Display the memory size of data structures in executeables and libraries. int retSize = getSizeOfUnrestrictedAlignmentStructProc(classHandle.value, sUnrestrictedAlignmentStruct.ref); print("Used memory size of an unrestricted alignment struct in execute(${sizeOf<UnrestrictedAlignmentStruct>()}) and library( $retSize)");
3-2. 字元轉換的正確性
由於不同程式語言可能採用的預設字元編碼(utf16, utf6, unicode... etc.)不同而可能造成函式在傳送與接送字串因編碼錯誤而造成判斷有誤甚至是造成執行崩潰(crash)或執行錯誤的情況,因此在C函式庫中定義函式用於判斷傳遞的資料結構的字串與函式庫中字串是否相同。
// Build and initial data structure in the C executable. static const TCHAR* pCmpStr = L"Test String"; UnrestrictedAlignmentStruct sUnrestrictedAlignmentStruct = { 0 }; wcsncpy_s(sUnrestrictedAlignmentStruct.stringField, wcslen(pCmpStr) + 1, pCmpStr, sizeof(sUnrestrictedAlignmentStruct.stringField)); // Define the function prototype of the C library. typedef bool (__stdcall* CompareStringOfUnrestrictedAlignmentStructProc) (HANDLE pClassHandle, pUnrestrictedAlignmentStruct pStrut); // Find the the pointer of function in the C library. CompareStringOfUnrestrictedAlignmentStructProc compareUnrestrictedAlignmentStructProc = (CompareStringOfUnrestrictedAlignmentStructProc)GetProcAddress(hinstLib, "CompareStringOfUnrestrictedAlignmentStruct"); // Compare the string of data structure in the C library. wprintf(L"Compare string in C library : %s\n", compareUnrestrictedAlignmentStructProc(hinstLib, &sUnrestrictedAlignmentStruct) == 1 ? L"True " : L"False");
// Convert string to int array and copy to the stringField of structure. extension StringArrayFill<T> on Array<Int16> { void fillFromList(List<T> list) { for (var i = 0; i < list.length; i++) { this[i] = list[i] as int; }}} // Build and initial data structure in the Dart executable. final String pCmpStr = "Test String"; final sUnrestrictedAlignmentStruct = calloc<UnrestrictedAlignmentStruct>() ..ref.stringField.fillFromList(pCmpStr.codeUnits); // Define the function prototype of the C library. typedef CompareStringOfUnrestrictedAlignmentStructProcForNative = int Function(Pointer classHandle,Pointer<UnrestrictedAlignmentStruct> pStruct); typedef CompareStringOfUnrestrictedAlignmentStructProcForDart = Int32 Function(Pointer classHandle,Pointer<UnrestrictedAlignmentStruct> pStruct); // Find the the pointer of function in the C library. late final compareStringOfUnrestrictedAlignmentStructProc = _wrapc_lib.lookupFunction< CompareStringOfUnrestrictedAlignmentStructProcForDart, CompareStringOfUnrestrictedAlignmentStructProcForNative >('CompareStringOfUnrestrictedAlignmentStruct'); // Compare the string of data structure in the C library String isSame = compareStringOfUnrestrictedAlignmentStructProc(classHandle.value, sUnrestrictedAlignmentStruct) == 1 ? "True" : "False"; print("Compare string in C library : $isSame");
3-3. 回調函數的執行
回調函數是一種常用於程式與程式之間互相建立傳遞通知訊息的手段,且由於是傳遞函式的指標位置且不同的程式語言對於函式參數載入與讀取的方式也不盡相同,因此若在轉換過程中發生誤差則可能造成執行崩潰(crash)或執行錯誤的情況,所以在函式庫中定義函式來執行資料結構中的回調函數。
// Define the prototype of the callback function typedef int (*CallbackFunc)(double* pDoubleArray); typedef bool (*CopyStructCallbackFunc)(PVOID pStruct); // Define the callback function of data structure. int comparArray(double *cmpArray) { for (int i = 0; i < 10; i++) { if (cmpArray[i] != notifyDouble[i]) return 0; } return 1; } // Define double array static double notifyDouble[] = { 0, 0.1, 2.2, 3.33, 44.44, 55.555, 666.666, 777.7777, 8888.8888, 99999.9999 }; // Define the function prototype of the C library. typedef bool(__stdcall* NotifyCallbackOfUnrestrictedAlignmentStructProc) (HANDLE pClassHandle, pUnrestrictedAlignmentStruct pStruct); // Build and initial data structure in the C executable. UnrestrictedAlignmentStruct sUnrestrictedAlignmentStruct = { 0 }; sUnrestrictedAlignmentStruct.funcptrField = comparArray; for (int i = 0; i < 10; i++) { sUnrestrictedAlignmentStruct.dubaryField[i] = notifyDouble[i]; } // Find the the pointer of function in the C library. NotifyCallbackOfUnrestrictedAlignmentStructProc notifyCallbackOfUnrestrictedAlignmentStructProc = (NotifyCallbackOfUnrestrictedAlignmentStructProc)GetProcAddress(hinstLib, "NotifyCallbackOfUnrestrictedAlignmentStruct"); // Execute callback function for unrestricted alignment structure in C library. wprintf(L"Execute callback function in C library : %d\n", notifyCallbackOfUnrestrictedAlignmentStructProc(hinstLib, &sUnrestrictedAlignmentStruct));
// Define the prototype of the callback function typedef CallbackFuncForDart = int Function( Pointer<Double> pDoubleArray); typedef CallbackFuncForFFI = Uint32 Function(Pointer<Double> pDoubleArray); typedef CopyStructCallbackFuncForFFI = Bool Function(Pointer pStruct); // Define the callback function of data structure. int checkArray(List<double> cmpArray){ for (int i =0; i < cmpArray.length;i++){ if (cmpArray.elementAt(i) != notifyDouble.elementAt(i)) { return 0; }} return 1; } // Define double array final List<double> notifyDouble = [ 0, 0.1, 2.2, 3.33, 44.44, 55.555, 666.666, 777.7777, 8888.8888, 99999.9999]; // Define the function prototype of the C library. typedef NotifyCallbackOfUnrestrictedAlignmentStructProcForNative = Int32 Function(Pointer pClassHandle, Pointer<UnrestrictedAlignmentStruct> pStruct); typedef NotifyCallbackOfUnrestrictedAlignmentStructProcForDart = int Function(Pointer pClassHandle, Pointer<UnrestrictedAlignmentStruct> pStruct); // Build and initial data structure in the Dart executable. final sUnrestrictedAlignmentStruct = calloc<UnrestrictedAlignmentStruct>() ..ref.funcptrField = Pointer.fromFunction<CallbackFuncForFFI>(comparArray, 0) // Find the the pointer of function in the C library. late final notifyCallbackOfUnrestrictedAlignmentStructProc = wrapclib.lookupFunction< NotifyCallbackOfUnrestrictedAlignmentStructProcForNative, NotifyCallbackOfUnrestrictedAlignmentStructProcForDart >('NotifyCallbackOfUnrestrictedAlignmentStruct'); // Execute callback function for unrestricted alignment structure in C library. isSame = notifyCallbackOfUnrestrictedAlignmentStructProc(classHandle.value, sUnrestrictedAlignmentStruct) == 1 ? "True":"False"; print("Execute callback function in C library : $isSame");
3-4. 驗證資料結構的資料
由於資料結構可能由許多不同的資料格式所組成,因此可能在傳遞時發生部份資料內容有錯誤,所以在函式庫中定義複製資料結構的函式並透過回調函式回傳至執行檔中顯示其複製結果。
// Define a callback function to display replicated data. bool getUnrestrictedAlignmentStructProc(PVOID pStruct) { pUnrestrictedAlignmentStruct pUAS = (pUnrestrictedAlignmentStruct) pStruct; wprintf(L"stringField : %s floatField : %f intField : %d charField : %c \n", pUAS->stringField, pUAS->floatField, pUAS->intField, pUAS->charField); wprintf(L"Verify the data of dubaryField : %s\n", comparArray(pUAS->dubaryField) == 1 ? L"True" : L"False"); bool bRetVal = pUAS->funcptrField(notifyDouble) == 1; wprintf(L"Execute the function porinter of pointerField : %s", bRetVal ? L"True" : L"False"); return bRetVal; } // Define the function prototype of the C library. typedef bool(__stdcall* CopyDataOfUnrestrictedAlignmentStructWithCallbackProc) (HANDLE pClassHandle, UnrestrictedAlignmentStruct sStruct, CopyStructCallbackFunc pCopyCallbackFunc); // Build and initial data structure in the C executable. UnrestrictedAlignmentStruct sUnrestrictedAlignmentStruct = { 0 }; sUnrestrictedAlignmentStruct.charField = 'A'; sUnrestrictedAlignmentStruct.floatField = 456.123f; sUnrestrictedAlignmentStruct.intField = 123; sUnrestrictedAlignmentStruct.funcptrField = comparArray; wcsncpy_s(sUnrestrictedAlignmentStruct.stringField, wcslen(pCmpStr) + 1, pCmpStr, sizeof(sUnrestrictedAlignmentStruct.stringField)); for (int i = 0; i < 10; i++) { sUnrestrictedAlignmentStruct.dubaryField[i] = notifyDouble[i]; } // Find the the pointer of function in the C library. CopyDataOfUnrestrictedAlignmentStructWithCallbackProc copyDataOfUnrestrictedAlignmentStructWithCallbackProc = (CopyDataOfUnrestrictedAlignmentStructWithCallbackProc) GetProcAddress(hinstLib, "CopyDataOfUnrestrictedAlignmentStructWithCallback"); // Validate each field of the replicated data structure in C library. wprintf(L"\nCopy structure data in C library : %d\n", copyDataOfUnrestrictedAlignmentStructWithCallbackProc(hinstLib, sUnrestrictedAlignmentStruct, getUnrestrictedAlignmentStructProc));
// Define a callback function to display replicated data. int getUnrestrictedAlignmentStructProc(Pointer pStruct){ int iRetVal = 0; var pUAS = pStruct.cast<UnrestrictedAlignmentStruct>(); List<int> strList = []; List<double> doubleList =[] ; pUAS.ref.dubaryField.fillFromArray(doubleList,10); pUAS.ref.stringField.fillFromArray(strList, 15); print ('stringField : ${String.fromCharCodes (strList)} floatField : ${pUAS.ref.floatField} intField : ${pUAS.ref.intField} charField : ${String.fromCharCode(pUAS.ref.charField)}'); var pPassParm = calloc.allocate<Double>(sizeOf<Double>() * 10); for (int i = 0;i<10;i++) { pPassParm[i] = doubleList[i]; } String isSame = comparArray(pPassParm) == 1 ? "True" : "False"; print("Verify the data of dubaryField : $isSame"); final callbackFuncProc = pUAS.ref.funcptrField .cast<NativeFunction<CallbackFuncForFFI>>().asFunction<CallbackFuncForDart>(); iRetVal = callbackFuncProc(pPassParm); isSame = iRetVal == 1 ? "True" : "False"; print ("Execute the function porinter of pointerField : $isSame"); calloc.free(pPassParm); return iRetVal; } // Define the function prototype of the C library. typedef CopyDataOfRestrictedAlignmentStructWithCallbackProcForNative = Int32 Function(Pointer classHandle, RestrictedAlignmentStruct sStruct, Pointer<NativeFunction<CopyStructCallbackFuncForFFI>> pCopyCallbackFunc); typedef CopyDataOfRestrictedAlignmentStructWithCallbackProcForDart = int Function(Pointer classHandle, RestrictedAlignmentStruct sStruct, Pointer<NativeFunction<CopyStructCallbackFuncForFFI>> pCopyCallbackFunc); // Build and initial data structure in the Dart executable. final sUnrestrictedAlignmentStruct = calloc<UnrestrictedAlignmentStruct>() ..ref.charField = 'A'.codeUnitAt(0) ..ref.floatField = 456.123 ..ref.intField = 123 ..ref.funcptrField = Pointer.fromFunction<CallbackFuncForFFI>(comparArray, 0) ..ref.stringField.fillFromList(pCmpStr.codeUnits) ..ref.dubaryField.fillFromList(notifyDouble); // Find the the pointer of function in the C library. late final copyDataOfUnrestrictedAlignmentStructWithCallbackProc = wrapclib.lookupFunction< CopyDataOfUnrestrictedAlignmentStructWithCallbackProcForNative, CopyDataOfUnrestrictedAlignmentStructWithCallbackProcForDart >('CopyDataOfUnrestrictedAlignmentStructWithCallback'); // Validate each field of the replicated data structure in C library. isSame = copyDataOfUnrestrictedAlignmentStructWithCallbackProc( classHandle.value, sUnrestrictedAlignmentStruct.ref, Pointer.fromFunction<CopyStructCallbackFuncForFFI>(getUnrestrictedAlignmentStructProc, false) ) == 1 ? "True" : "False"; print("Copy structure data in C library : $isSame");
3-5. 回傳資料結構的完整性
C 語言對於資料結構的記憶體配置規則可藉由設定 pack 的方式來進行調整且若配置規則不一致時會影響資料轉換後函式引用,而dart:ffi 對於資料結構也有提供調整記憶體配置的方式以避免與 C 語言函數所建立的資料結構有所差異而導致資料結構的不完整,因此分別定義使用設定與預定對齊格式的資料結構進行來驗證是否能正確執行C函式庫中的函式。
// Default alignment according to computer architecture. typedef struct _UnrestrictedAlignmentStruct { int intField; TCHAR charField; CallbackFunc funcptrField; float floatField; wchar_t stringField[15]; double dubaryField[10]; } UnrestrictedAlignmentStruct, * pUnrestrictedAlignmentStruct; // Default alignment according to pack function. #pragma pack(push, 1) typedef struct _RestrictedAlignmentStruct { int intField; TCHAR charField; CallbackFunc funcptrField; float floatField; wchar_t stringField[15]; double dubaryField[10]; } RestrictedAlignmentStruct, *pRestrictedAlignmentStruct; #pragma pack(pop)
// Default alignment according to computer architecture. class UnrestrictedAlignmentStruct extends Struct { @Int32() external int intField; @Uint16() external int charField; external Pointer<NativeFunction<CallbackFuncForFFI>> funcptrField; @Float() external double floatField; @Array(15) external Array<Int16> stringField; @Array(10) external Array<Double> dubaryField; } // Default alignment according to pack function. @Packed(1) class RestrictedAlignmentStruct extends Struct { @Int32() external int intField; @Int16() external int charField; external Pointer<NativeFunction<CallbackFuncForFFI>> funcptrField; @Float() external double floatField; @Array(15) external Array<Int16> stringField; @Array(10) external Array<Double> dubaryField; }
4. 自動轉換C語言標頭檔套件
在pub.dev中有提供將C語言標頭檔的內容自動轉換成Dart語言所支援dart:ffi中定義的NativeType資料格式的工具套件。
4-1. 設定流程
1. 環境需求
使用FFIGEN套件前需要先行安裝Visual Studio With C++ 與LLVM套件並在開發專案的pubspec.yaml文件中設定安裝ffigen套件,才能正常的執行其套件功能。
Install Visual Studio C++
// pubspec.yaml dev_dependencies: ffigen: ^4.0.0
2. 新增FFIGEN套件的組態檔
新增FFIGEN轉換的組態檔,ffigen提供下列兩種設定的方式:
2-1. 新增ffigen專屬的yaml檔案並搭配 --config 參數來指定組態檔。
name: AutoConvertByffigen description: A starting point for Dart libraries or applications. output: 'lib/crosscode_by_auto.dart' headers: entry-points: - 'third_party/crosscode_wrap_cfunc.h' # 只轉換下列的header文件而其內所引用的相關header文件將不會被轉換 include-directives: - '**crosscode_defstruct.h' - '**crosscode_wrap_cfunc.h' structs: include: - '.*' exclude: - '_ExcludeUnrestrictedAlignmentStruct' # 移除起始"_"符號並加入FFI字串 rename: '_(.*)': 'FFI$1' # 此選項中只有被引用的typedef才會進行轉換 typedefs: # 若要直接引用其原始定義而不要建立其對應的定義 exclude: - 'CallbackFunc2' - 'UChar' # 將轉換後的定義變數名稱加入ForFFI字串 rename: '(.*)': '$1' functions: include: - '.*' # 指定排除轉換的函式 exclude: - 'ExcludeFunction' # 指定型別對應至NativeType型別 typedef-map: 'TCHAR': 'Int16' comments: style: any length: full preamble: /* ************** Cross Code Co.Ltd ******************** how to auto convert from C to dart by ffigen packages ***************************************************** */
2-2. 在pubspec.yaml中新增ffigen標籤來設定組態資訊
name: crosscode_wrapc_exe description: A sample command-line application. version: 1.0.0 homepage: https://vocus.cc/user/61ca9f1bfd89780001efa2a6 environment: sdk: '>=2.15.1 <3.0.0' dependencies: ffi: ^1.1.0 dev_dependencies: lints: ^1.0.0 test: ^1.16.0 ffigen: ^4.0.0 ffigen: name: AutoConvertByffigen description: Convert Header file of C to dart file of Dart by ffigen package output: 'lib/crosscode_by_auto.dart' headers: entry-points: - 'third_party/crosscode_wrap_cfunc.h' # 只轉換下列的header文件而其內所引用的相關header文件將不會被轉換 include-directives: - '**crosscode_defstruct.h' - '**crosscode_wrap_cfunc.h' structs: include: - '.*' exclude: - '_ExcludeUnrestrictedAlignmentStruct' # 移除起始"_"符號並加入FFI字串 rename: '_(.*)': 'FFI$1' # 此選項中只有被引用的typedef才會進行轉換 typedefs: # 若要直接引用其原始定義而不要建立其對應的定義 exclude: - 'CallbackFunc2' - 'UChar' # 將轉換後的定義變數名稱加入ForFFI字串 rename: '(.*)': '$1' functions: include: - '.*' # 指定排除轉換的函式 exclude: - 'ExcludeFunction' # 指定C型別對應至NativeType型別 typedef-map: 'TCHAR': 'Int16' comments: style: any length: full preamble: /* ************** Cross Code Co.Ltd ******************** how to auto convert from C to dart by ffigen packages ***************************************************** */
【GitHub】: https://github.com/crosscode-software/dart_wrap_c