C++/Matlab Inter-Process Communication (IPC)

In a project I'm currently working on together with a colleague, we wanted to enable inter-process communication between a C++ program and a Matlab program. At first we thought of using TCP/IP directly for communication. However, TCP is a stream-oriented protocol and we wanted to avoid having to do message framing of the stream data ourselves. Thus, we decided to use ZeroMQ with the request-reply pattern instead for the transmission of JSON-encoded messages.

However, when setting up ZeroMQ for use with Matlab, we faced some difficulties. In order to make it easier for others to use ZeroMQ with Matlab for IPC with a C++ program in the future, I have written down below the steps that were necessary to get ZeroMQ to work with Matlab on an Ubuntu 20.04 system with Matlab 2020b.

First of all, we need to install Maven and the Java 8 SDK. Initially, I tried using Java 11, however, Matlab 2020b still does not seem to support it.

1sudo apt-get install maven libmaven-javadoc-plugin-java openjdk-8-jdk openjdk-8-jdk-headless

Then, one needs to download the latest release of JeroMQ, a Java implementation of ZeroMQ/libzmq. For example, the currently latest release is: https://github.com/zeromq/jeromq/archive/v0.5.2.tar.gz

After unpacking the source archive, switch to the source directory and execute the following commands.

1export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/
2mvn package

Finally, copy target/jeromq-0.5.2.jar to a directory where you'd like to store the compiled library, e.g., a subdirectory libs/ in the folder containing your Matlab project. Then, you can execute your Matlab script using JeroMQ. Below, you can find example code for the request-reply pattern, which correctly handles program interrupts when the user presses CTRL+C.

 1% BSD 2-Clause License
 2% 
 3% Copyright (c) 2021, Christoph Neuhauser
 4% All rights reserved.
 5% 
 6% Redistribution and use in source and binary forms, with or without
 7% modification, are permitted provided that the following conditions are met:
 8% 
 9% 1. Redistributions of source code must retain the above copyright notice, this
10%    list of conditions and the following disclaimer.
11% 
12% 2. Redistributions in binary form must reproduce the above copyright notice,
13%    this list of conditions and the following disclaimer in the documentation
14%    and/or other materials provided with the distribution.
15% 
16% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19% DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20% FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21% DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27function zeromqReplier
28    % Either call the function javaclasspath below, or use:
29    % cd(prefdir)
30    % edit javaclasspath.txt
31    % -> Add a global path to the .jar file.
32    javaclasspath('libs/jeromq-0.5.2.jar')
33
34    import org.zeromq.SocketType;
35    import org.zeromq.ZMQ;
36    import org.zeromq.ZContext;
37    import java.lang.Thread
38
39    context = ZContext();
40    socket = context.createSocket(SocketType.REP);
41    socket.bind('tcp://127.0.0.1:17384'); % arbitrary port
42    socket.setReceiveTimeOut(1000);
43
44    cleanupObj = onCleanup(@()cleanUpSocket(socket));
45
46    while (~Thread.currentThread().isInterrupted())
47        request_string = socket.recvStr(0);
48        
49        if isempty(request_string)
50            continue;
51        end
52        
53        request_string = request_string.toCharArray';
54
55        % native2unicode(..., 'UTF-8') not necessary?
56        request = jsondecode(native2unicode(request_string(:)', 'UTF-8'));
57        %fprintf('Request string: %s\n', request_string);
58        disp(request);
59
60        % ... do something with the received request ...
61
62        % Send a reply.
63        reply = struct('filenames', 'test umlaut ü');
64        reply_string = unicode2native(jsonencode(reply), 'UTF-8');
65        %fprintf('Reply string: %s\n', reply_string);
66        disp(reply);
67        
68        socket.send(reply_string, 0);
69    end
70
71    function cleanUpSocket(socket)
72        socket.close();
73    end
74end