2

I was tasked to write a service in C that listens to a named or unnamed Windows OS pipe (two way data transfer should be possible) and the "host application" that writes data to the OS pipe should be a java program. I am not allowed to use sockets (for performance reasons) or any external non-default library.

Is it possible to write data to named/unnamed OS pipes with Java (if possible without turning the program into C code with JNI?) I've been thinking of running a CMD script that writes to an OS pipe with java but I'm pretty sure that it would be extremely inefficient. I've also looked into the java Pipe class but if I understood correctly, this is only for inter-thread communication inside the JVM and not a "real" OS pipe. How exactly can I write data to a "real" OS pipe? Performance is very important for this because I need a Java program to communicate with a C program, which runs CUDA operation so I can't afford to waste a lot of performance on sending/receiving the data.

5
  • It sounds like you might genuinely be looking for System.out. Commented Jun 17, 2022 at 21:36
  • This answer describes how to handle named pipes on Windows from Java: stackoverflow.com/a/44218273/18980756
    – Franck
    Commented Jun 17, 2022 at 21:36
  • @LouisWasserman what do you mean? can I write to an OS pipe with sout directly? Commented Jun 17, 2022 at 21:38
  • Just out of curiosity, if performance plays a very important role in calling the C routines, why is JNI ruled out as a potential solution? Commented Jun 17, 2022 at 21:59
  • @StephanSchlecht uhm I am just trying to do what my boss told me to do. We are working on a big, well thought out product but I'm not from the planning team so I don't know their reasoning. Commented Jun 18, 2022 at 5:52

2 Answers 2

2

Requirements:

  • Java caller program calls a C calculator program running in its own process
  • Java sends operations
  • C program returns results
  • Avoid JNI/JNA if possible

The easiest way is probably to call the C program from Java and communicate via standard I/O. The C program could get the operations via stdin in a loop, do the calculations, and return the results via stdout.

In Java, the central mechanism for this would be the Process class. Oracle's documentation states:

Process provides control of native processes started by ProcessBuilder.start ... By default, the created process does not have its own terminal or console. All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods getOutputStream(), getInputStream(), and getErrorStream().

To avoid a deadlock, we alternately write a line with the operation to be performed and read back a line with the result.

The advantage of using stdin and stdout is of course also the manual ad-hocs testability since you can simply call the C program on the command line. By the way, the approach would also work in the same way under Linux and macOS.

To get as simple a test case as possible, here is a C program that gets either an addition or a subtraction operator with two arguments, calculates the result and returns it. Of course, the whole thing doesn't include CUDA and only minimal error handling and probably doesn't meet the detailed requirements, but it could be a start in the right direction.

The Java demo program finally call this C program and sends an addition and subtraction operation to the C program and outputs the returned results accordingly. The q command then terminates the C program.

Java

package com.software7.test;

import java.io.*;

public class Caller {

    public static void main(String[] args) {
        Caller caller = new Caller();
        caller.runCCalculator();
    }

    private void runCCalculator() {
        try {
            String[] command = { "C:\\Users\\stephan\\source\\repos\\CCalculator\\x64\\Debug\\CCalculator.exe" };
            ProcessBuilder processBuilder = new ProcessBuilder(command);
            Process process = processBuilder.start();

            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
            BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));

            String[] ops = {"+ 1.1 2.2", "- 1024.123 512.123"};
            for (String op : ops) {
                bw.write(op);
                bw.write("\n");
                bw.flush();
                String result = br.readLine();
                if (result != null) {
                    System.out.println(op + " = " + result);
                }
            }
            bw.write("q\n");
            bw.flush();

            bw.close();
            br.close();
            System.out.println("Process exited with " + process.waitFor());
        } catch (IOException | InterruptedException exp) {
            exp.printStackTrace();
        }
    }

}

C

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static void error_exit(const char *msg);
static char readOp(const char *token);
static double readArg(const char *token);

int main(void) {
    char buf[128];
    char *ptr;

    while ((ptr = fgets(buf, sizeof(buf), stdin)) != NULL) {
        buf[strcspn(buf, "\n")] = 0;
        if (strcmp(ptr, "q") == 0) {
            break;
        }
        char *token = strtok(ptr, " ");
        char op = readOp(token);
        token = strtok(NULL, " ");
        double val1 = readArg(token);
        token = strtok(NULL, " ");
        double val2 = readArg(token);

        switch (op) {
            case '+':
                printf("%lf\n", val1 + val2);
                fflush(stdout);
                break;
            case '-':
                printf("%lf\n", val1 - val2);
                fflush(stdout);
                break;
            default:
                error_exit("unknown operator");
        }
    }
    return 0;
}

static char readOp(const char *token) {
    if (token != NULL) {
        return *token;
    } else {
        error_exit("no operator");
    }
}

static double readArg(const char *token) {
    double res;
    if (token != NULL) {
        char *end_ptr;
        res = strtod(token, &end_ptr);
        if (token == end_ptr || *end_ptr != '\0') {
            error_exit("invalid float operand");
        }
    } else {
        error_exit("invalid operand");
    }
    return res;
}

static void error_exit(const char *msg) {
    fprintf(stderr, "%s\n", msg);
    exit(1);
}

Result

+ 1.1 2.2 = 3.300000
- 1024.123 512.123 = 512.000000
1

There's other answers lurking on StackOverflow that suggest that Java on Windows doesn't do Windows' pipes (see this one).

This restriction actually isn't that uncommon. ZeroMQ (another good candidate that does a very good job of abstracting IPC / sockets / etc) doesn't support IPC on Windows either. Cygwin - think Posix / Unix environment for Windows - does use Windows pipes for posix pipes, but they had to commit all sorts of horrid coding sins to do it (specifically, recreating a select() that supported Windows IPC / pipes meant starting a thread per "file descriptor" to poll the underlying Windows object - like going back to *nix in the 1980s).

In principal you could recompile a *nix JVM source code bundle for Cygwin, and then java code run in that would be able to access a Windows pipe. But that sounds really horrid. However, so long as you didn't need to use select() or epoll() to block on reading the pipe, it might end up being fairly fast and efficient.

EDIT

Franck has found otherwise! I'd be interested to know whether or not there's an implementation of java nio channels SelectorProvider that works in the Windows JVM for pipes.

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.