RoadRunner, our simulation main simulation engine in the Systems Biology Workbench, has been written in C#. This allows RoadRunner to be used in scripting scenarios with languages like IronPython, or even from the csharp-shell or Windows PowerShell. But what if you wanted to use RoadRunner from plain old C / C++?
C++ / CLI
On Windows operating systems the obvious choice would probably be C++/CLI. And really, the task could not be easier. Just add the RoadRunner reference to the application include the RoadRunner namespace and you are good to go:
1: // RoadRunnerCLI.cpp : main project file.
2:
3: #include "stdafx.h"
4:
5: using namespace System;
6: using namespace CSharpSimulator;
7:
8: static void PrintResult(cli::array<double, 2>^ data)
9: {
10: for (int i = 0; i < data->GetLength(0); i++)
11: {
12: for (int j = 0; j < data->GetLength(1); j++)
13: {
14: Console::Write(data[i,j]);
15: Console::Write("\t");
16: }
17: Console::WriteLine();
18: }
19: }
20:
21: int main(array<System::String ^> ^args)
22: {
23:
24: sbwInterface roadRunnerInstance;
25:
26: roadRunnerInstance.loadSBMLFromFile
27: (L"C:\\Users\\fbergmann\\Documents\\SBML Models\\BorisEJB.xml");
28: roadRunnerInstance.setTimeStart(0.0);
29: roadRunnerInstance.setTimeEnd(100.0);
30: roadRunnerInstance.setNumPoints(11);
31:
32: cli::array<double, 2>^ result = roadRunnerInstance.simulate();
33:
34: PrintResult(result);
35:
36: return 0;
37: }
The only drawback would be that this will not work on Linux or OS X.
MONO Embedding
So what about MONO Embedding? Or in other words, writing a C++ application, that would embed the MONO Runtime. This enables the C++ application to directly reference RoadRunner. The idea is basically the same as when using any SBW Module. First we get a hold of the RoadRunner module, or in this case an instance of the RoadRunner class:
1: mono_set_dirs(NULL, NULL);
2: domain = mono_jit_init ("LibRoadRunner.dll");
3: mono_set_dirs(NULL, NULL);
4: mono_config_parse(NULL);
5: assembly = mono_domain_assembly_open (domain, "LibRoadRunner.dll");
6: if (assembly == NULL)
7: cout << "Couldn't load RR assembly" << endl;
8: image = mono_assembly_get_image (assembly);
9: rr_class = mono_class_from_name (image, "CSharpSimulator", "sbwInterface");
10: if (rr_class == NULL)
11: cout << "Couldn't get hold of the RoadRunner class" << endl;
12:
13: // create new roadRunner instance
14: rr_instance = mono_object_new (domain, rr_class);
15: // call constructor
16: mono_runtime_object_init (rr_instance);
Next one would get hold of all the method one would like to call, as in:
1: // get the loadSBML
2: methodLoadSBML = mono_class_get_method_from_name(rr_class, "loadSBML", -1);
3: if (methodLoadSBML == NULL)
4: cout << "Couldn't get loadSBML" << endl;
5:
finally for calling the method all that’s needed is to wrap the arguments into arguments that MONO would understand:
1: void HostRR::LoadSBML(const char* model)
2: {
3: MonoString *str = mono_string_new (domain, model);
4: void *args[1]; args[0] = str;
5: MonoObject *exception = NULL;
6: mono_runtime_invoke(methodLoadSBML, rr_instance, args, &exception);
7: if (exception != NULL)
8: {
9: PrintException("Error while loading SBML", exception);
10: }
11: }
continuing like that for the remaining methods that have to be available for C++. For a client calling into RoadRunner the result would look like this:
1: #include "HostRR.h"
2: #include <iostream>
3: #include <iomanip>
4:
5: using namespace std;
6:
7: void PrintResult(double** data, int numRows, int numCols)
8: {
9: if (data == NULL) return;
10:
11: for (int y = 0; y < numRows; y++)
12: {
13: for (int x = 0; x < numCols; x++)
14: {
15: cout << setiosflags(ios::fixed)
16: << setw(7) << setprecision(2) << setfill(' ')
17: << data[y][x] << "\t";
18: }
19: cout << endl;
20: }
21: cout << endl;
22: }
23:
24: int main(int argc, char* argv[])
25: {
26: HostRR instance;
27:
28: instance.LoadSBMLFromFile("BorisEJB.xml");
29:
30: instance.SetTimeStart(0.0);
31: instance.SetTimeEnd(1100.0);
32: instance.SetNumPoints(100);
33:
34: int numRows; int numCols;
35: double** result = instance.Simulate(&numRows,&numCols);
36:
37: PrintResult(result, numRows, numCols);
38:
39: return 0;
40: }
And the advantage? It runs like a charm on Linux and OS X. However I did struggle a bit with getting it compiled. After all one draws several dependencies when embedding mono, the most troublesome for me proved to be glib2 and OS X. At the end the problem turned out to be that Snow Leopard liked the executable to be 64bit by default, but the glib libraries were only available for 32bit and ppc. Since I have Qt installed on all my systems, I used a qmake project, to generate the make files (or Xcode projects as the case may be). So here is what worked for me:
1:
2: TEMPLATE = app
3: CONFIG = console
4: TARGET = HostRR
5: DEPENDPATH += .
6: INCLUDEPATH += .
7:
8: mac {
9: CONFIG += x86
10: CFLAGS += -arch i386
11: INCLUDEPATH += /Library/Frameworks/Mono.framework/Versions/2.6.1/include/mono-1.0 /sw/include/glib-2.0 /sw/lib/glib-2.0/include
12: LIBS += -L/Library/Frameworks/Mono.framework/Versions/2.6.1/lib -L/sw/lib -pthread -lmono -lpthread -lm -lgthread-2.0 -lglib-2.0 -lintl
13: }
14:
15: unix {
16: DEFINES +=_REENTRANT -pthread
17: INCLUDEPATH += /usr/include/mono-1.0 /usr/include/glib-2.0 /usr/lib/glib-2.0/include
18: LIBS += -Wl,--export-dynamic -pthread -lmono -ldl -lpthread -lm -lgthread-2.0 -lrt -lglib-2.0
19: }
20:
21: win32 {
22: INCLUDEPATH += "C:\Program Files (x86)\Mono-2.6\include\glib-2.0" "C:\Program Files (x86)\Mono-2.6\lib\glib-2.0\include" "C:\Program Files (x86)\Mono-2.6\include\mono-1.0"
23: LIBS += -L"." -lmono
24: }
25:
26: # Input
27: HEADERS += HostRR.h
28: SOURCES += HostRR.cpp main.cpp
29:
If you’d like to give it a try, I’ve posted all the source to sourceforge. The results are right here:
http://jdesigner.svn.sourceforge.net/viewvc/jdesigner/trunk/csharp/HostRR/
Conclusions
We’ve seen, that accessing a .NET assembly from C/C++ is really no issue at all. C++/CLI is a great language it provides access to all of the .NET framework in a snap, however it will lock you in to the Windows world. With MONO Embedding, it is easy to break out! Given the reflection capabilities of .NET I believe the way to go forward would be to have a wrapper generator, that would just write the wrapper code.
Going forward if I were to use C++ to interact with RoadRunner, I’d probably combine the best of two worlds by falling back to C++/CLI on Windows systems and Mono embedding on Linux / OS X. But this might be a personal preference.
No comments:
Post a Comment