---
title: "Combining Oberon-07 and C with OBNC"
series: "Mostly Oberon"
number: 5
author: "rsdoiel@gmail.com (R. S. Doiel)"
date: "2020-05-01"
keywords: [ "Oberon", "programming" ]
copyright: "copyright (c) 2020, R. S. Doiel"
license: "https://creativecommons.org/licenses/by-sa/4.0/"
---


# Combining Oberon-07 and C with OBNC

By R. S. Doiel, 2020-05-01

This is the fifth post in the [Mostly Oberon](../../04/11/Mostly-Oberon.html)
series. Mostly Oberon documents my exploration of the Oberon
Language, Oberon System and the various rabbit holes I will
inevitably fall into.

In my day job I write allot of code in Go and
orchestration code in Python.  It's nice having
the convenience of combining code written one
language with an another.  You can do the same
with [OBNC](https://miasap.se/obnc/).  The OBNC
compiler supports inclusion of C code in a
straight forward manner. In fact Karl's compiler
will generate the C file for you!

In learning how to combine C code and Oberon-07
I started by reviewing Karl's [manual page](https://miasap.se/obnc/man/obnc.txt).
The bottom part of that manual page describes
the steps I will repeat below. The description
sounds more complicated but when you walk through
the steps it turns out to be pretty easy.

## Basic Process

Creating a C extension for use with OBNC is very
straight forward.

1. Create a Oberon module with empty exported procedures
2. Create a Oberon test module that uses your module
3. Compile your test module with OBNC
4. Copy the generated module `.c` file to the same directory as your Oberon module source
5. Edit the skeleton `.c`,  re-compile and test

Five steps may sound complicated but in practice is
straight forward.

## Fmt, an example

In my demonstration of Karl's instructions I will be
creating a module named `Fmt` that includes two
procedures `Int()` and `Real()` that let you use
a C-style format string to format an INTEGER
or REAL as an ARRAY OF CHAR. We retain the idiomatic
way Oberon works with types but allow a little more
flexibility in how the numbers are converted and
rendered as strings.

### Step 1

Create [Fmt.Mod](Fmt.Mod) defining two exported procedures
`Int*()` and `Real*()`. The procedures body should be
empty. Karl's practice is to use exported comments to
explain the procedures.


~~~ {.oberon}

    MODULE Fmt;

    	PROCEDURE Int*(value : INTEGER; fmt: ARRAY OF CHAR;
                       VAR dest : ARRAY OF CHAR);
    	END Int;

    	PROCEDURE Real*(value : REAL; fmt: ARRAY OF CHAR;
                        VAR dest : ARRAY OF CHAR);
    	END Real;

    BEGIN
    END Fmt.

~~~


### Step 2

Create a test module, [FmtTest.Mod](FmtTest.Mod), for
[Fmt.Mod](Fmt.Mod).


~~~ {.oberon}

    MODULE FmtTest;
      IMPORT Out, Fmt;

    PROCEDURE TestInt(): BOOLEAN;
      VAR
        fmtString : ARRAY 24 OF CHAR;
        dest : ARRAY 128 OF CHAR;
        i : INTEGER;
    BEGIN
        i := 42;
        fmtString := "%d";
        Fmt.Int(i, fmtString, dest);
        Out.String(dest);Out.Ln;
        RETURN TRUE
    END TestInt;

    PROCEDURE TestReal(): BOOLEAN;
      VAR
        fmtString : ARRAY 24 OF CHAR;
        dest : ARRAY 128 OF CHAR;
        r : REAL;
    BEGIN
        r := 3.145;
        fmtString := "%d";
        Fmt.Real(r, fmtString, dest);
        Out.String(dest);Out.Ln;
        RETURN TRUE
    END TestReal;

    BEGIN
      ASSERT(TestInt());
      ASSERT(TestReal());
      Out.String("Success!");Out.Ln;
    END FmtTest.

~~~


### Step 3

Generate a new [Fmt.c](Fmt.c) by using the
OBNC compiler.


~~~ {.shell}

    obnc FmtTest.Mod
    mv .obnc/Fmt.c ./

~~~


the file `.obnc/Fmt.c` is your C template file. Copy it
to the directory where Fmt.Mod is.

### Step 4

Update the skeleton `Fmt.c` with the necessary C code.
Here's what OBNC generated version.


~~~ {.c}

    /*GENERATED BY OBNC 0.16.1*/

    #include "Fmt.h"
    #include <obnc/OBNC.h>

    #define OBERON_SOURCE_FILENAME "Fmt.Mod"

    void Fmt__Int_(OBNC_INTEGER value_, const char fmt_[], 
                   OBNC_INTEGER fmt_len, char dest_[], 
                   OBNC_INTEGER dest_len)
    {
    }


    void Fmt__Real_(OBNC_REAL value_, const char fmt_[],
                    OBNC_INTEGER fmt_len, char dest_[],
                    OBNC_INTEGER dest_len)
    {
    }


    void Fmt__Init(void)
    {
    }

~~~


Here's the skeleton revised with do what we need to be done.


~~~ {.c}

    #include ".obnc/Fmt.h"
    #include <obnc/OBNC.h>
    #include <stdio.h>

    #define OBERON_SOURCE_FILENAME "Fmt.Mod"

    void Fmt__Int_(OBNC_INTEGER value_, 
                   const char fmt_[], OBNC_INTEGER fmt_len,
                   char dest_[], OBNC_INTEGER dest_len)
    {
        sprintf(dest_, fmt_, value_);
    }


    void Fmt__Real_(OBNC_REAL value_, const char fmt_[],
                    OBNC_INTEGER fmt_len, char dest_[],
                    OBNC_INTEGER dest_len)
    {
        sprintf(dest_, fmt_, value_);
    }


    void Fmt__Init(void)
    {
    }

~~~


NOTE: You need to change the path for the `Fmt.h` file reference.
I also add the `stdio.h` include so I have access to the C
function I wish to use. Also notice how OBNC the signature
for the functions use the `_` character to identify mapped values
as well as the char arrays being provided with a length parameter.
If you are doing more extensive string work you'll want to take
advantage of these additional parameters so insure that the
as strings are terminated properly for Oberon's reuse.


### Step 5

Recompile and test.


~~~ {.shell}

    obnc FmtTest.Mod
    ./FmtTest

~~~


### Next and Previous

+ Next [Compiling OBNC on macOS](../06/Compiling-OBNC-on-macOS.html)
+ Previously [Oberon Loops and Conditions](../../04/19/Mostly-Oberon-Loops-and-Conditions.html)