Wednesday, December 26, 2012

Building PHP extensions with C++, the easy way (update: MySQL, threads)

Here is an easy way to build a PHP extension with C++ and default PHP packages on a Ubuntu system. I use SWIG to wrap C++ code to the Zend API. When using loops and recursion intensively, porting a few functions to C++ can give you some extra power.

First, install all required packages (tested with Ubuntu 12.10):
apt-get install php5-cli php5-dev swig g++

Second, write the code:

// example.swig
%{
#include <iostream>
#include <vector>
using namespace std;

int HelloWorld(char *str) {
    cout << "Hello World: " << str << endl;

    // run some slow code
    vector<int> array;
    for (int i=0; i < 10000000; i++) array.push_back(i*2);
    for (int i=0; i < array.size(); i++) array[i]++;
    return 0;
}
%}

%module example
int HelloWorld(char *str);

Third, compile the code and load the exension:

swig -c++ -php5 example.swig
g++ `php-config --includes` -O2 -march=native -mtune=native -std=c++11 -fPIC -c *.cpp
g++ -shared *.o -o example.so

echo extension=`pwd`/example.so >/etc/php5/mods-available/example.ini
php5enmod example

And finally run: php test.php

// test.php
<?php
HelloWorld("C++"); // gives: Hello World: C++

HelloWorld("foo\0bar"); // gives: Hello World: foo

Note: To send the output to a web server, zend_printf() needs be used instead of std::cout or printf().

Note: When using Apache2 MPM-Prefork, processes are re-used for a client until the keep-alive ends (see KeepAlive* in your apache2.conf). Therefore, global variables in the C++ code need to be reset with %rshutdown {...}.

Returning vectors is also possible:

// example.swig
%{
#include <vector>
using namespace std;

vector<string> HelloArray() {
    vector<string> v;
    v.push_back("hello world");
    return v;
}
%}

%typemap(out) vector { # convert vector to php-array[string]
    array_init(return_value);
    for (int i=0; i<$1.size(); ++i) add_next_index_string(return_value, $1[i].c_str(), 1);
}

%module example
vector<string> HelloArray();
// test.php
print_r( HelloArray() ); // Array( [0] => hello world )

You can also start Threads from an extension (C++11 is really nice here):

// example.swig
%{
#include <iostream>
#include <vector>
#include <thread>
using namespace std;

void someThread(int i) {
  for (int j=0; j < 100; j++) {
    cout << " thread: " << i << endl ;
    vector<int> array;
    for (int i=0; i < 10000000; i++) array.push_back(i*2);
    for (int i=0; i < array.size(); i++) array[i] *= 2;
  }
}

int HelloWorld(char *str) {
  cout << "Hallo World: " << str << endl;

  // start some threads
  thread t1(someThread, 1);
  thread t2(someThread, 2);
  thread t3(someThread, 3);
  // wait for threads to finish
  t1.join();
  t2.join();
  t3.join();
  return 0;
}
%}

%module example
int HelloWorld(char *str);

A small MySQL example looks like this:
// apt-get install mysql-server-5.5 libmysqlcppconn-dev
// g++ -shared *.o -lmysqlcppconn -o example.so

// example.swig
%{
#include <iostream>
#include <cppconn/driver.h>
#include <cppconn/statement.h>
#include <cppconn/resultset.h>
#include <cppconn/exception.h>
#include "mysql_connection.h"

using namespace std;
using namespace sql;

int HelloWorld(char *str) {
  try {
    auto_ptr<Connection> con( get_driver_instance()->connect("127.0.0.1", "root", "") );
    con->setSchema("test");

    auto_ptr<Statement> stmt( con->createStatement() );
    auto_ptr<ResultSet> res( stmt->executeQuery("SELECT 'Hello World!' AS result") );
    while (res->next()) {
      cout << res->getString("result") << endl;
      cout << res->getString(1) << endl;
    }
  } catch (SQLException &e) {
    cout << __FILE__ << " " << __FUNCTION__ << " " << __LINE__ << endl;
    cout << e.what() << " " << e.getSQLState() << " " << e.getErrorCode() << endl;
  }
  return 0;
}
%}

%module example
int HelloWorld(char *str);

Note: Inside extensions, php.ini settings like open_basedir and memory_limit are not active.

Other resources:

3 comments:

  1. thanks to share your knowledge, can you please tell me that you are using %{ .... %} to create an extension, is it the default standard to make a php extension cause i didn't found ??

    Web Development Company

    ReplyDelete
    Replies
    1. The "%{...%}" is based on http://www.swig.org/tutorial.html . Instead of having .swig, .h and .cpp files, I put everything in one file to make it easier to understand.

      Delete
  2. I have tried so many different examples to get Swig to work and everything works good up until the point of trying to load the extension. I always get the following error:

    PHP Warning: PHP Startup: Unable to load dynamic library: /usr/lib/php/modules/example.so: undefined symbol: compiler_globals_id in Unknown on line 0

    Any suggestions?

    ReplyDelete