SWIG - C++

Overview

PyPi module N/A
git repository https://bitbucket.org/arrizza-public/swig-cpp
git command git clone git@bitbucket.org:arrizza-public/swig-cpp.git
Verification Report https://arrizza.com/web-ver/swig-c++-report.html
Version Info
  • Ubuntu 20.04 focal, Python 3.10
  • Ubuntu 22.04 jammy, Python 3.10
  • Ubuntu 24.04 noble, Python 3.10

Summary

A discussion and project for using SWIG C++ for Python, Ruby and javascript functions.

I had a need to call the same C++ functions from multiple scripting languages: python, ruby, javascript. This project is an example of how to set that up in JetBrain's CLion and CMake

Note: the javascript is working but not using the SWIG generated source file (see javascript/index.cpp and nwrap.h for temporary content). It is not generating a working javascript_example.node file that registers the module.

Some of the issues were:

  • configuring the individual cmake subprojects for each scripting language
  • separating the output files for each scripting language

Other good sources for SWIG & python:

Next Steps

For SWIG documentation, see https://www.swig.org/Doc3.0/SWIG.html#SWIG

$ swig -version
SWIG Version 4.0.1
Compiled with g++ [x86_64-pc-linux-gnu]
Configured options: +pcre
Please see http://www.swig.org for reporting bugs and further information
  • current calls only use simple functions. Need to try on various other data structures:
    • strings
    • arrays
    • structs
    • unions
    • JSON
    • enums (i.e. read-only)
    • pointers (is this needed?)

Common

  • The source code is in src directory. The example.cpp and .h come from (with minor changes) https://valelab4.ucsf.edu/svn/3rdpartypublic/swig/Doc/Manual/Introduction.html

  • example.i is used across all sub-targets and is used by swig to configure what is made available to the language

    • I used #include "../src/example.h" and %include. The assumption is that all and only exported functions and variables are named in the example.h file. This may not be true for all projects or all scripting languages.

    • the functions in example.c are simple. No unions, no structs, no char* or kinds of pointers, no arrays, i.e. simple. Other C constructs may cause some additional definitions to be needed.

Do a clean rebuild and test

  • ./do_install - insures latest ruby, python, node are installed
  • ./do_clean - wipes out all the output directories and the cmake build.
  • ./do_build - runs cmake using the CLion command lines
  • ./doit - runs scripts for all languages

Overall:

./do_install
./do_clean
./do_build debug all
./doit

# expected stdout:
./doit debug all
==== versions
Python 3.10.13
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux-gnu]
v10.19.0

==== run
           :     python        ruby       node 
fact       :        720         720        720 
my_mod     :          3           3          3 
before     :        3.0         3.0        3.0 
after      :        5.1         5.1        5.1 
sum1       :        261         261        261 
sum2       :        261         261        261 
append     :     abc-ok      abc-ok     abc-ok 
app before :        3.0         3.0        3.0 
app after  :        5.2         5.2        5.2 
app append :     xy-app      xy-app     xy-app 

==== convert to hex
python     :    01 02 03 FF 05 06 07 08   09 0A 0B 0C 0D 0E 0F 10 
ruby       :    01 02 03 FF 05 06 07 08   09 0A 0B 0C 0D 0E 0F 10 
node       :    01 02 03 FF 05 06 07 08   09 0A 0B 0C 0D 0E 0F 10 

Note that ./doit or ./do_build defaults to the incorrect target, so you must specify debug all

  • fact - factorial, passes in an integer, returns an integer
  • my_mod - modulus, passes in two integers, returns an integer
  • before - gets the default value of global variable my_variable
  • after - sets my_variable and then gets it again
  • sum1 - sums an array of integers
  • sum2 - sums an array of bytes (uint8_t)
  • append - concats "-ok" to given string
  • app before - App::my_variable default value
  • app after - App::my_variable is set then gets it again
  • app append - App.append() call, concats "-app" to given string
  • second section shows to_hex() output

Installation:

Note: I ran this only on Ubuntu 20.04.1 LTS. Things may be different in other Ubuntu versions or other OS.

All installations are handled by the ./do_install script

$ ./do_install
python3.10 is already the newest version (3.10.13-1+focal1).
<snip>
python3.10-venv is already the newest version (3.10.13-1+focal1).
<snip>
ruby-dev is already the newest version (1:2.7+1).
<snip>
nodejs is already the newest version (10.19.0~dfsg-3ubuntu1.3).
<snip>
swig is already the newest version (4.0.1-5build1).

Note: I have not tested a clean installation of these packages. I may have necessary pip, gem, npm global modules already installed that I have not specified here.

Run test.py

Output directory: out/py

The import loads the factorial function, the mod function and cvar. "cvar" is used to access global C variables. Note these are read/write.

from out.py.example import fact, my_mod, cvar

Expected output:

$ python3.10 test.py
# see doit output above, column starting with "python"

Run test.rb

Output directory: out/rb

The require is very simple in ruby, it loads the .so:

require_relative './out/rb/example.so'

Expected output:

$ ruby test.rb
# see ./doit output above, column starting with "ruby"

Run test.js

For good examples of calls in node_wrap.h, see https://github.com/nodejs/node-addon-examples For node_api.h doc & usage: https://nodejs.org/api/n-api.html#n_api_usage

Output directory: out/js

The require is very simple in javascript, it loads the .node:

const example = require("./out/js/example.node")

Expected output:

$ node test.js
# see ./doit output above, column starting with "javascript"

If you get a failure:

internal/modules/cjs/loader.js:1144
[snip]
Error: /snap/core/current/usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.32' not found
[snip]
  code: 'ERR_DLOPEN_FAILED'

You need to update the GLIBC libraries that are installed. To determine which ones use this:

$ strings debug/libnode_example.so  | grep GLIBC
GLIBCXX_3.4.32
GLIBCXX_3.4.31
GLIBCXX_3.4.29
GLIBCXX_3.4
GLIBC_2.4
GLIBC_2.14
GLIBC_2.2.5

Use this to determine which is currently installed:

ldd --version
ldd (Ubuntu GLIBC 2.39-0ubuntu8.3) 2.39

Then use apt to install:

# TODO 

- John Arrizza