DEV Community

loading...
Cover image for Dart FFI and CLI App

Dart FFI and CLI App

aseemwangoo profile image aseem wangoo Updated on ・7 min read

In case it helped :)
Pass Me A Coffee!!

Pre-Requisite: Install package FFI and Args

Hosted URL : https://web.flatteredwithflutter.com/#/

We will cover briefly about

  1. Using Dart FFI
  2. Fetch login history of a user (macOS)
  3. Create a CLI App

Article here: https://flatteredwithflutter.com/dart-ffi-cli-dart2native/

Note: We won’t be explaining in-depth about Dart FFI as there are good articles dedicated to it.

  1. Using Dart FFI

FFI: (Foreign Function Interface) can be used to call the C/C++ language-based APIs

Dart FFI and CLI App
Dart FFI and CLI App


A statically linked library is embedded into the app’s executable image and is loaded when the app starts.

Symbols from a statically linked library can be loaded using DynamicLibrary.executable or DynamicLibrary.process.

A dynamically linked library is distributed in a separate file or folder within the app and loaded on-demand. A dynamically linked library can be loaded into Dart via DynamicLibrary.open.

How will we approach?

For this article, we will be utilizing the dynamic system library. 

In case you want to create your own dynamic library, read this.


2. Fetch login history of a user (macOS)

Open the terminal and type this command

last login `username`
where username is before @ [username@Macbook-Pro]
  • You should get your login history. Our goal is to call this command from Dart.
  • This command is defined in a system dynamic library, present in the macOS.

In macOS, the system libraries are present in

/usr/lib/libSystem.dylib

For getting paths for different OS, visit here.

We will load our dynamic library, specified at the path above, in Dart.

Dart FFI and CLI App
  • Import the dart ffi package(present inside Flutter) as
import 'dart:ffi' as ffi

This has a class DynamicLibrary. We call the method open and load our dynamic library (Dylibs.systemDyLib).

class Dylibs {
  static const String systemDyLib = '/usr/lib/libSystem.dylib';
  static const String systemSymbolName = 'system';
}

Each dynamic library is composed of symbols. For finding the symbols in a library refer here.

Now, getting the user login history falls under the symbol System.

Dart FFI and CLI App
  • Lookup for the symbol from the loaded dynamic library using lookupFunction.

We have specified SystemC and SystemDart functions, which basically are

// C header typedef:
typedef SystemC = ffi.Void Function(ffi.Pointer<Utf8> command);

// Dart header typedef
typedef SystemDart = void Function(ffi.Pointer<Utf8> command);

This combines the lookup function and casts to a Dart function.

  • Next, we convert the command (our last login command) into something which C understands using
/// Convert a [String] to a Utf8-encoded null-terminated C string.
/// Returns a malloc-allocated pointer to the result.
final cmd = Utf8.toUtf8(command);
  • Run the command using sysFunc(cmd) 
  • Finally, free up the memory, since C/C++ don’t have garbage collection

Create a CLI App

We will create our own command, which would have info and description regarding the CLI App.

Dart FFI and CLI App
  • Create our command

We use the CommandRunner, and define our command(last_login) with some description as 

CommandRunner in CLI
  • Add our last login Command. In order to create your own commands, you need to extend Command Class
// THIS ADDS OUR LAST LOGIN COMMAND
runner.addCommand(LastLoginCmd());

Note: LastLoginCmd is our custom class

WE create an abstract base command class and declare the methods.

abstract class BaseCLICommand extends Command {
  String loadingMessage;
  void execCommand(String arg);

  @override
  Future run() async {
    if (argResults.arguments.isEmpty) {
      throw Exception('😳😳 Please specify the argument');
    }

    final arg = argResults.arguments.first;
    final loadingMsg = '$loadingMessage $arg';
    stdout.write('$loadingMsg\n');
    execCommand(arg);
  }
}
  • argResults.arguments help us in fetching the user input.
  • stdout.write is used to print out to the console.
  • execCommand is the function, which the extended class defines.

Our LastLoginCmd extends the above class and implements the methods.

LastLoginCmd
  • After we add the command as above

// THIS ADDS OUR LAST LOGIN COMMAND
runner.addCommand(LastLoginCmd());

Finally, we run this command as

runner.run(args);

Last Step (Convert to CLI App)

We utilize the power of

dart2native, which has the ability to compile Dart programs to self-contained executables. With dart2native, you can create tools for the command line on macOS, Windows, or Linux using Dart.

  1. Navigate to the directory containing your dart entry point. For instance

My directory will be lib/ffi

2. Run the command

dart2native cmd_line.dart -o login_history

-o <path> or --output=<path>Generates the output.

This creates our CLI App named login_history.

Final Output

In case it helped :)
Pass Me A Coffee!!

Source code for Flutter Desktop App..

Hosted URL : https://web.flatteredwithflutter.com/#/

Discussion

pic
Editor guide