Wednesday, June 22, 2011

How to Convert C FILE* to C++ iostream

When we deal with a lot of C libraries in our C++ code, there may be cases where we need to convert the C FILE* into C++ iostream. Boost iostreams can help to deal with such problems easily. Here's an example how to do it.

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>

void write() {
    FILE* fp = fopen("whatever.txt", "w");
    if (fp == NULL) {
        perror("fopen error");
    }
    int fd = fileno(fp);
    boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_sink> bis(fd);
    std::ostream os(&bis);
    os << "Hello World!" << std::endl;

    fclose(fp);
}

void read() {
    FILE* fp = fopen("whatever.txt", "r");
    if (fp == NULL) {
        perror("fopen error");
    }
    int fd = fileno(fp);
    boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_source> bis(fd);
    std::istream is(&bis);
    while (is) {
        std::string line;
        std::getline(is, line);
        std::cout << line << std::endl;
    }
    fclose(fp);
}

int main() {
    write();
    read();

    return 0;
}

Tuesday, June 14, 2011

Creating a Custom Builder in SCons

Suppose we need to create a custom builder in SCons. This custom builder will read a text file and append an author name at the end of the file.

import os

def my_builder(target, source, env):
    for i in range(0, len(source)):
       txt = open(source[i].abspath).read()
       txt = txt + os.linesep + "Author: " + env['AUTHOR']
       open(target[i].abspath, "wb").write(txt)

env = Environment()
env['BUILDERS']['MyBuilder'] = Builder(action = my_builder)

env.MyBuilder(['output1.txt', 'output2.txt'], ['input1.txt', 'input2.txt'], 
              AUTHOR = "Foo")

The custom builder allows an argument to be passed in into our custom builder. How we pass the argument is by using the Python's keyword argument, e.g. AUTHOR = "Foo". The custom builder function can then read this value from the env, e.g. env["AUTHOR"].

How to Flatten a List In Python

Here is a simple algorithm to flatten a list in Python. The algorithm also works with the list that have nested lists.

def flatten1(l, newlist):
    if isinstance(l, list):
        for i in l: flatten1(i, newlist)
    else: newlist.append(l)

def flatten2(l):
    if isinstance(l, list):
        newlist = []
        for i in l: newlist = newlist + flatten2(i)
        return newlist
    else: return [l]
    
def flatten3(l):
    if not l: return l
    if not isinstance(l, list): return [l]
    return flatten3(l[0]) + flatten3(l[1:])
    
if __name__ == '__main__':
    l = [1, [2, 3], 4, [[5]], [], [[]], [6, 7, [8]]]
    newlist = []
    flatten1(l, newlist)
    print newlist
    
    print flatten2(l)
    
    print flatten3(l)

The output:
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]

The first algorithm is a ugly since it requires a newlist parameter to be passed in. The second one is a bit better but it still requires a loop. The third one doesn't require any loop and is the shortest one.

Wednesday, June 1, 2011

Getting Started with SWIG

Creating JNI by hand can be pretty challenging especially if we intend to wrap a large C++ code base. In such scenario, SWIG can dramatically reduce the effort of creating the JNI wrapper. There is difference between using javah and swig tools. Swig is useful for creating JNI from C++ while javah is useful for creating JNI from Java.

Hello.h
#ifndef HELLO_H_
#define HELLO_H_

#include <string>
#include <stdint.h>

class Hello {
public:
    Hello();
    virtual ~Hello();
    void sayHello(const std::string& name) const;
    uint32_t getNumber() const;
    const char* getMessage() const;
};

#endif /* HELLO_H_ */

Hello.cpp
#include <iostream>
#include "Hello.h"
using namespace std;

Hello::Hello() {
}

Hello::~Hello() {
}

void Hello::sayHello(const std::string& name) const {
    cout << "Hello, " + name << endl;
}

uint32_t Hello::getNumber() const {
    return 10;
}

const char* Hello::getMessage() const {
    return "Hello";
}

SWIG requires an interface file.
Hello.i
%module JniHello
%{
#include "Hello.h"
typedef unsigned int uint32_t;
%}

%include "std_string.i"
%include "Hello.h"

typedef unsigned int uint32_t;
As we can see here, there is %module verbatim that indicates the module name. Everything in the %{..%} block will be copied to the resulting wrapper file created by SWIG. Here we use include std_string.i library, which will convert std::string to java.lang.String. Without including it, only char* will be converted to java.lang.String and std::string will be a pointer. We can also define a typedef to tell that uint32_t is basically an unsigned int. As a matter of fact, there is stdint.i library that can help to deal with stdint type mapping, but for this tutorial, let's keep it as it is.

Now let's compile the C++ programs and create shared libraries.
swig -c++ -java Hello.i

g++ -c -fpic Hello.cpp
g++ -shared Hello.o -o libhello.so

g++ -c -fpic Hello_wrap.cxx -I$JAVA_HOME/include -I$JAVA_HOME/include/linux
g++ -shared Hello_wrap.o -o libjnihello.so libhello.so

Test it out with a simple Java program. Make sure to add -Djava.library.path or set LD_LIBRARY_PATH to point to the directory where libhello.so and libjnihello.so are located.
Main.java
public class Main {
    static {
        try {
            System.loadLibrary("jnihello");
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Hello hello = new Hello();
        hello.sayHello("Foo");
        System.out.println(hello.getMessage());
        System.out.println(hello.getNumber());
    }
}

The output is:
Hello, Foo
Hello
10