How to build and use binary components ====================================== 1) Binary Components -------------------- nesC 1.2 allows you to build and use binary components. This is supported through a new way of definining components. A component "Foo" can be defined in file Foo.nc as: component Foo { provides interface A as X; uses interface A as Y; } Note that you say "component" rather than "module" or "configuration", and there is no implementation section. When generating the .o file for a nesC application including Foo, the nesC compiler will: - include references to external symbols for all provided commands and used events of Foo - define external symbols (based on the application's wiring graph) for all used events and provided commands of Foo - These symbols are named 'component-name$interface-name$command-or-event-name' - The compiler requires that all commands and events used by Foo (including all commands in used interfaces and all events in provided interfaces) be fully wired (in particular, parameterised interfaces must be wired as a whole and not just for individual values) In this example, if interface A is interface A { command int request(); event void done(int val); } Then the generated .o file for an application including Foo will: - use symbols named Foo$X$request, Foo$Y$done - define symbols named Foo$X$done, Foo$Y$request 2) Using binary components -------------------------- Using a binary component is straightforward: just wire the binary component (e.g., Foo) into your application just as you would any other component. At some point, you will have to link your application with the binary component's definition. While this operation is beyond the scope of the nesC language itself, this might happen in one of the following ways: - The binary component is distributed in an object (.o) or library (.a) file. You just link your application against this object or library as you would any other. For instance: ncc -target=mica2 MyApp.nc some-binary-component.o - The binary component is pre-installed in the mote's ROM. Some loader is responsible for linking newly installed user applications with the appropriate ROM entry points. 3) Building binary components ----------------------------- While the 'component' language feature appears to be designed for linking to externally created binary component, it can actually also be used to create them. An example will make this clear. Assume I want to build a binary component "Foo" from our example above. I have some implementation of Foo (in this case a module, but it could also be a configuration): module FooImplementation { provides interface A as X; uses interface A as Y; } implementation { // This does something mysterious to requests... int x; command int X.request() { return call Y.request() + x; } event void Y.done(int val) { x = val; } } I can build a binary version of Foo by compiling BuildBinaryFoo: configuration BuildBinaryFoo { } implementation { components Foo, FooImplementation; Foo.X -> FooImplementation.X; Foo.Y <- FooImplementation.Y; } where Foo is a *different* component from the one we saw earlier: component Foo { // Note that the uses, provides are reversed from the definition // of Foo (and FooImplementation) above! uses interface A as X; provides interface A as Y; } 4) Warnings ----------- Some issues you should be aware of when building and using binary components: - If a component (e.g., TimerC) is included in both your application and the binary component definition, you will end up with two copies of TimerC. This is a) wasteful, b) unlikely to work. A binary component should include the strict minimum necessary, and should express its dependencies on outside services through interfaces. - Compile with -DNESC_BUILD_BINARY when building binary components to avoid having copies of the runtime support functions (TOS_post, __nesc_atomic_start, __nesc_atomic_end) in your binary components. Note that the necessary #ifdef's are only present from TinyOS 1.1.11 onwards. - The symbol names used in binary components include '$'. If you want to call these from C code, you may have to include the -fdollars-in-identifiers command-line flag. On some platforms (avr, msp430), the assembler does not support '$' in symbol names. To avoid this, use 'nescc -gcc=' (e.g., 'nescc -gcc=avr-gcc') rather than avr-gcc or msp430-gcc to compile your C code. nescc will invoke your-gcc, but with a custom assembler which accepts '$'. - 'default' commands and events do not work across binary component boundaries: the decision to call a default command is made at compile-time based on whether or not the command is wired. A call to a command in a binary component is always assumed wired, so the default command will never be called (the same applies to events, of course). This is most likely to cause surprises with parameterised interfaces. The following code: component BinaryA { provides interface A[int id]; } module UseA { uses interface A; } implementation { void f() { call A.request(); } event void A.done(int val) { } } configuration WireA { } implementation { components UseA, BinaryA; UseA.A -> BinaryA.A[10]; } will produce the error binary entry point BinaryA.A.done is not fully wired as even if the binary implementation of BinaryA had a default handler for BinaryA.A.done, the compiler would not know when it should be called.