This example describes how to setup a new Foundation Library in MicroEJ SDK and how to distribute it through a MicroEJ Platform.
A Foundation Library is a library that provides core runtime APIs or hardware-dependent functionality. It is often connected to underlying C low-level APIs.
This example has been tested on:
MicroEJ SDK 5.4.1
With a MicroEJ 5 Demonstration Platform imported into the MicroEJ repository that contains:
- EDC-1.3
- BON-1.4
- SP-2.0
- Basic knowledge about Java and C programming.
- Basic knowledge about MicroEJ (Platform build and Standalone Application launch).
A Foundation Library is composed of
- A MicroEJ API project: contains API skeletons for compilation purpose.
- A MicroEJ Implementation project: contains the runtime code executed by the Platform and Low Level C header files.
- C code: contains the implementation of native methods linked to the C project.
- Java Mock-up project: contains the implementation of native methods for simulation.
- Select File > New > Module Project
- Set the project settings.
- Project Name: mylib-api
- Organization: com.mycompany.api
- Module: mylib
- Revision: 1.0.0
- Select microej-javaapi skeleton.
- Click on Finish.
- Set the project settings.
- Select File > New > Class
- Source folder: mylib-api/src/main/java.
- Package: com.mycompany.
- Name: MyLib.
- Copy and paste the following code into this class:
package com.mycompany; /** * My Foundation Library. */ public class MyLib { /** * Computes the factorial of an integer. * * @param number * a positive integer * * @return the factorial of number. */ public static int factorial(int number) { throw new RuntimeException(); } }
This class defines a factorial API. The method content is filled with throw a new RuntimeException just for successful compilation.
Right-click on mylib-api project and select Build Module. After a successful build, the project build directory target~/artifacts contains:
- Jar file (mylib.jar), that will be used by an Application.
- Rip file (mylib.rip), that will be embedded into a Platform.
- Unzip mylib.rip and copy all the files of the content directory into the dropins directory of the [platform]-configuration project.
- Rebuild the Platform.
- Select File > New > Module Project
- Set the project settings.
- Project Name: mylib-impl
- Organization: com.mycompany.impl
- Module: mylib-impl
- Revision: 1.0.0
- Select microej-javaimpl skeleton.
- Click on Finish.
- Set the project settings.
- Open mylib-impl/module.ivy
- replace
microej.lib.name="mylib-impl-1.0"
withmicroej.lib.name="mylib-1.0"
- Save the file.
- replace
- Select File > New > Class .
- Source folder: mylib-impl/src/main/java.
- Package: com.mycompany
- Name : MyLib
- Copy and paste the following code into this class:
package com.mycompany; @SuppressWarnings({ "javadoc", "nls" }) public class MyLib { public static int factorial(int number) { if (number < 0) { throw new IllegalArgumentException("Factorial cannot be negative"); } return nativeFactorial(number); } public native static int nativeFactorial(int number); }
This class defines the factorial implementation. It first checks the argument validity and then redirects to a native method for speed consideration.
- Create a new file named include/LLMYLIB_impl.h into the content directory of the implementation project.
- Copy and paste the following code into this file:
#ifndef LLMYLIB_IMPL #define LLMYLIB_IMPL /** * @file * @brief MicroEJ factorial Low Level API * @author My Company * @version 1.0.0 */ #include <stdint.h> #ifdef __cplusplus extern "C" { #endif #define LLMYLIB_IMPL_factorial Java_com_mycompany_MyLib_nativeFactorial /* * Returns the factorial */ uint32_t LLMYLIB_IMPL_factorial(uint32_t number); #ifdef __cplusplus } #endif #endif
This file defines the factorial C prototype. The com_mycompany_MyLib part is the fully qualified name of the MyLib class created previously where all . are replaced by _.
The #define statement allows to separate the Java part and the C part. This is called the Low Level API of the Foundation Library. If the fully qualified name of the Java native method is updated, the C implementation code do not need to be updated.
Right-click on mylib-impl project and select Build Module. After a successful build, the project build directory target~/artifacts contains:
- Rip file (mylib-impl.rip), that will be embedded into a Platform.
- Unzip mylib-impl.rip and copy all the files of the content directory into the dropins directory of the [platform]-configuration project.
- Rebuild the Platform.
- Create a new project File > New > Standalone Application Project
- Set the project settings.
- Project Name: mylib-test
- Organization: com.mycompany.test
- Module: mylib-test
- Revision: 1.0.0
- Set the project settings.
- Open module.ivy
- Add the dependency
<dependency org="com.mycompany.api" name="mylib" rev="1.0.0" />
- Add the dependency
- Open file Main.java
- Source folder: mylib-test/src.
- Package: com.mycompany
- Class Name: TestMyLib
- Copy and paste the following code into this class:
package com.mycompany.test; public class Main { public static void main(String[] args) { System.out.println("(5!)=" + MyLib.factorial(5)); } }
This class defined a main entry point that prints the result of 5!.
- Right-click on mylib-test project and select Run As > MicroEJ Application.
The application is started. After a few seconds, the following trace shall appear in the console view:
Exception in thread "main" java.lang.UnsatisfiedLinkError: No HIL client implementor found (timeout) at java.lang.Throwable.fillInStackTrace(Throwable.java:79) at java.lang.Throwable.<init>(Throwable.java:30) at java.lang.Error.<init>(Error.java:10) at java.lang.LinkageError.<init>(LinkageError.java:10) at java.lang.UnsatisfiedLinkError.<init>(UnsatisfiedLinkError.java:10) at com.mycompany.MyLib.factorial(MyLib.java:15) at com.mycompany.TestMyLib.main(TestMyLib.java:5) at java.lang.MainThread.run(Thread.java:836) at java.lang.Thread.runWrapper(Thread.java:372)
This is the normal behavior because nativeFactorial native method is currently not implemented (see below). The HIL engine (Hardware In the Loop) did not find a Platform Mock-up implementing the native method.
To each MicroEJ native method is associated a Java Mock-up method that implements the simulated behavior. A Mock-up project is a standard Java project (J2SE).
- Select File > New > Module Project
- Set the project settings.
- Project Name: mylib-mock
- Organization: com.mycompany.mock
- Module: mylib-mock
- Revision: 1.0.0
- Select microej-mock skeleton.
- Click on Finish.
- Set the project settings.
- Select File > New > Class
- Source folder: mylib-mock/src/main/java.
- Package: com.mycompany
- Class Name: MyLib
- Copy and paste the following code into this class:
package com.mycompany; public class MyLib { public static int nativeFactorial(int number) { if (number == 0) { return 1; } int fact = 1; // this will be the result for (int i = 1; i <= number; i++) { fact *= i; } return fact; } }
This class defines the implementation nativeFactorial method on Simulator. The Mock-up method has the same prototype than the implementation one, except the native modifier. The HIL engine will link the native method to the Mock-up method.
- Right-click on the mylib-mock project and select Build Module.
After a successful build, the project build directory target~/artifacts contains:
- Rip file (mylib-mock.rip), that will be embedded into a Platform.
- Unzip mylib-mock.rip and copy all the files of the content directory into the dropins directory of the [platform]-configuration project.
- Rebuild the Platform.
- Right-click on mylib-test project and select Run As > MicroEJ Application.
The following trace shall appear in the console view:
=============== [ Initialization Stage ] =============== =============== [ Launching on Simulator ] =============== (5!)=120 =============== [ Completed Successfully ] ===============
- Duplicate the Simulation launcher
- Go to Run > Run Configuration…
- Select mylib-test TestMyLib launcher
- Right-Click and select Duplicate
- In Execution tab, select Execute on Device"
- Click on Run
The file microejapp.o is generated to a well known location for the C project.
- Open the Platform C project into the C IDE
- Compile and link the project
Please consult the documentation of the imported Platform for more details on the proceedings.
A similar linker error than the one below should appear in the C IDE console view:
Undefined symbol Java_com_mycompany_MyLib_nativeFactorial (referred from microejapp.o).
This is the normal behavior because the symbol Java_com_mycompany_MyLib_nativeFactorial is currently not implemented in C code. The third-party linker did not find an object file implementing the native function.
- In the C project, create a new File called LLMYLIB_impl.c
- Add the C file to the compilation objects by adding it to the C Project configuration
- Copy and paste the following code to the file:
#include "LLMYLIB_impl.h" #include "sni.h" /** * @file * @brief MicroEJ factorial low level API (the implementation does not support unsigned integer overflow) * @author My Company * @version 1.0.0 */ uint32_t LLMYLIB_IMPL_factorial(uint32_t number) { if (number == 0) { return 1; } uint32_t fact = 1; // this will be the result for (uint32_t i = 1; i <= number; i++) { fact *= i; } return fact; }
This file defines a basic C implementation of the nativeFactorial function.
- Link the C Project.
The link shall produce the executable file.
- Program the executable file on the device.
The following trace shall appear on the standard output:
VM START (5!)=120 VM END (exit code = 0)
- Communication mechanisms from Java to C: Example-Standalone-Java-C-Interface
- Simulation mock specification
- Generate a mock with an UI: Mock-Get-Started