I have this great C program I'd like to embed into an iOS app. One passes command line arguments to it and the results are printed to stdout
via printf
and fputs
- like with all the good old Unix programs.
Now I'd like to just edit main
and the print functions to use my own printf
function which collects all the output that normally goes to stdout
and return it at the end.
I implemented a solution by using a line buffer to collect all the printf
s until the newline. And a dynamic char
array whereto I copy when an output line is finished.
The charm of this solution is - it's kind of tcl'ish: just throw everything into a text line, and if it's complete, store it. Now do that as long as necessary and return the whole bunch at the end.
C code:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
// outLineBuffer collects one output line by several calls to tprntf
#define initialSizeOfReturnBuffer 10 // reduced for testing (would be 16*1024)
#define incrSizeOfReturnBuffer 5 // reduced for testing (would be 1024*1024)
#define outLineBufferMaxSize 4095
char outLineBuffer[sizeof(char)*outLineBufferMaxSize] = "";
char *tReturnString;
size_t sizeOfReturnBuffer, curPosOutBuffer = 0, lenOutLine = 0;
With the replacement tprntf
for all the original printf
and fputs
:
// replace printf with this to collect the parts of one output line.
static int tprntf(const char *format, ...)
{
const size_t maxLen = sizeof(char)*outLineBufferMaxSize;
va_list arg;
int done;
va_start (arg, format);
done = vsnprintf (&outLineBuffer[lenOutLine], maxLen-lenOutLine, format, arg);
va_end (arg);
lenOutLine = strlen(outLineBuffer);
return done;
}
And the function when we complete one output line (everywhere \n
is printed):
// Output line is now complete: copy to return buffer and reset line buffer.
static void tprntNewLine()
{
size_t newSize;
long remainingLenOutBuffer;
char *newOutBuffer;
remainingLenOutBuffer = sizeOfReturnBuffer-curPosOutBuffer-1;
lenOutLine = strlen(outLineBuffer)+1; // + newline character (\n)
remainingLenOutBuffer -= lenOutLine;
if (remainingLenOutBuffer < 0) {
newSize = sizeOfReturnBuffer + sizeof(char)*incrSizeOfReturnBuffer;
if ((newOutBuffer = realloc(tReturnString, newSize)) != 0) {
tReturnString = newOutBuffer;
sizeOfReturnBuffer = newSize;
} else {
lenOutLine += remainingLenOutBuffer; //just write part that is still available
remainingLenOutBuffer = 0;
}
}
snprintf(&tReturnString[curPosOutBuffer], lenOutLine+1, "%s\n", outLineBuffer);
curPosOutBuffer += lenOutLine;
outLineBuffer[0] = 0;
lenOutLine = 0;
}
And a little main to test it (without Swift - e.g. plain gcc):
int main(int argc, char *argv[])
{
int i;
sizeOfReturnBuffer = initialSizeOfReturnBuffer*sizeof(char);
if ((tReturnString = malloc(sizeOfReturnBuffer)) == 0) {
return 1; // "Sorry we are out of memory. Please close other apps and try again!";
}
tReturnString[0] = 0;
for (i = 1; i < argc; i++) {
tprntf("%s ", argv[i]);
}
tprntNewLine();
tprntf("%s", "ABC\t");
tprntf("%d", 12);
tprntNewLine(); // enough space for that ;-)
tprntf("%s", "DEF\t");
tprntf("%d", 34);
tprntNewLine(); // realloc necessary ...
tprntf("%s", "GHI\t");
tprntf("%d", 56);
tprntNewLine(); // again realloc for testing purposes ...
printf("tReturnString at the end:\n>%s<\n", tReturnString); // contains trailing newline
return 0;
}
The call from swift will then be as follows (using CStringArray.swift):
let myArgs = CStringArray(["computeIt", "par1", "par2"])
let returnString = mymain(myArgs.numberOfElements, &myArgs.pointers[0])
if let itReturns = String.fromCString(returnString) {
print(itReturns)
}
freeMemory()