Can I describe a shared variable in multiple C++ functions that can be called in Octave?

Hello everyone. I am wondering if it is possible to keep a state variable that is shared by multiple C++ functions called by Octave or not. I tried to explain what I am doing below:

#include <iostream>
#include <octave/oct.h>
#include "stdlib.h"

int x = 0;

DEFUN_DLD(func_a, args, , "foo"){
    x++;
    std::cout << "foo=" << x << "\n";
    octave_value_list retVal = ovl(x);
    return retVal;
}

DEFUN_DLD(func_b, args, , "bar"){
    x++;
    std::cout << "bar=" << x << "\n";
    octave_value_list retVal = ovl(x);
    return retVal;
}

After I compiled the above .cpp file with mkoctfile as following, I am able to call them in Octave:

mkoctfile -c sandbox.cpp
mkoctfile -o func_a sandbox.o
mkoctfile -o func_b sandbox.o

My expectation from this code is incrementing the global variable x by 1 at every call of func_a and func_b. Apparently func_a and func_b functions recognize x in different contexts. When I call func_a and func_b, both functions print 1 to the screen. Is there any way to define a common variable that can be reached by both functions?

By the way, I simplified the example by changing the type of x. I know that I can return it to Octave and feed both functions with the updated value of x. However, the type of x is actually a struct that I could not achieve to return to Octave.

With the commands, you are creating two translation units func_a.oct and func_b.oct, which both define each a “global” (to the translation unit) variable int x. How should func_a.oct and func_b.oct know that there is memory in the respective other binary translation unit holing an integer value?

The approach I propose might not be the best, but works for me.

Split your project into logical units:

func_a.cpp / func_b.cpp, as you created them, but only with one DEFUN_DLD function each:

#include <iostream>
#include <octave/oct.h>
#include "stdlib.h"

extern int x;

DEFUN_DLD(func_a, args, , "foo"){
    x++;
    std::cout << "foo=" << x << "\n";
    octave_value_list retVal = ovl(x);
    return retVal;
}

Important is the line extern int x; which tells that somewhere at the linking stage a memory for a global variable x will be presented.

Now let’s create an overwhelming shared library shared.cpp, which both compilation units will later be linked to (code that is literally “shared”):

int x = 0;

Yeah, that is it. One line presenting the necessary memory in your shared library :sweat_smile:

Now some compiler magic from the Octave command-line:

system ("g++ -fPIC -c shared.cpp");
system ("g++ -o libshared.so -shared shared.o");

## The following setenv command is indeed a bit ugly,
## but only sets an environment variable, such that
## shared libraries are found in the current directory "."
## after compilation with mkoctfile
setenv ("DL_LDFLAGS", ...
        strrep ([mkoctfile("-p", "DL_LDFLAGS"), " -Wl,-rpath,."], "\n", ""));
mkoctfile func_a.cpp -L. -lshared
mkoctfile func_b.cpp -L. -lshared

And you are done :white_check_mark:

>> func_a;
foo=1
>> func_a;
foo=2
>> func_b;
bar=3
>> func_b;
bar=4
>> func_b;
bar=5
>> func_a;
foo=6
>> func_a;
foo=7

Some handy statement, if you get intro trouble with the loading of shared libraries is to check if they are correctly linked and found by your system:

>> system ("ldd ./func_a.oct");
        linux-vdso.so.1 (0x00007ffdc3754000)
        libshared.so => ./libshared.so (0x00007f8079a8f000)
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f8079706000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f80794ee000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f80792cf000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8078ede000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f8078b40000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f8079e94000)

Of course you can make your libshared.so as complex as you wish, not only defining only a single integer.

:warning: One final warning: This code is not thread safe, reentrant, etc. It is the bare minimum.

Alternatively to the approach described by Kai, you could compile the file only once and use the autoload command (possibly in a PKG_ADD file) to load both functions from the same compilation unit.

Thanks for all. Calling autoload function simply overcame my problem. As well, defining the global variable in a one line shared library is working :grinning:.

I was also looking for an answer in Stackoverflow and the answers are so similar. Therefore, I am copying the link in stackoverflow for future readers: Can I describe a shared variable in multiple C++ functions that can be called in Octave? - Stack Overflow

@montekristo07 Please avoid cross posting and clearly mark with URL before if you already asked a question in another forum. See this short statement to this topic.

Markus and me spent some amount of time creating this hopefully helpful answer. Please be fair and announce this thread on StackOverflow for future readers, as well :slightly_smiling_face:

1 Like