A C++ Native Interface for Interpreted JVMs - Semantic Scholar

1 downloads 344 Views 110KB Size Report
Juice is a POSIX-compliant J2ME-CLDC interpreted JVM. It was designed ... As depicted in Figure 1, RTSJ applications developed using jRate rely on the GCJ.
A C++ Native Interface for Interpreted JVMs 

Angelo Corsaro , Corrado Santoro 

Washington University Department of Computer Science and Engineering 1 Brookings Drive, BOX 1045, St. Louis, 63130 Missouri, USA [email protected] 

University of Catania Dept. of Computer and Telecommunication Engineering Viale A. Doria, 6 - 95125 - Catania, Italy [email protected]

Abstract. This paper describes JuNI++, a C++ native interface for interpreted Java Virtual Machines. While JuNI++ was initially designed in order to integrate the RTSJ implementation jRate with Juice, a virtual machine for small footprint environments, its engineering and performance advantages outlived the initial goal. The main contribution of this paper is to show how a C++ based native interface can fully and efficiently support the Java mapping in interpreted environments without requiring any cooperation from the C++ compiler.

1 Introduction Most of Java execution environments for embedded systems are featured by a precompiler/pre-processor that transforms bytecoded classes into a different representation form which is, in general, more compact and optimized. This is made necessary due to the limited availability of resources of embedded systems (limited memory size, above all), thus requiring class files to be transformed into a form that contains only the parts fundamental for program execution. For example, Sun’s KVM [20], Superwaba [3] (the JVM for PDAs) and IBM’s WebSphere Studio Device Developer [14] have a tool that produces a single file containing the class closure of an application; it is generated by removing debugging information and performing a pre-linking and a pre-verification process. To improve performances, other Java environments pre-compile classes into native code (ahead-of-time compilers), thus producing a single native executable. This is common in real-time environments where meeting time-constraints often requires high execution efficiency. This approach is used by jRate [9], the Real-Time Specification for Java (RTSJ) [6] implementation developed by one of the authors, which is based on the GNU Compiler for Java (GCJ) [13]. All of these mechanisms work well when the whole code (program and libraries) is directly available from a ROM or a flash memory, and class loading from external sources is thus not needed. However, most of the modern hardware platforms for embedded systems and Systems-On-Chip (SoC) are equipped with network connections

that make these systems ready to be integrated in—more or less standardized [4, 5]— distributed environments, such as the Internet or Internet-based (TCP/IP-based) networks. Java applications running on these systems can take advantage of network class loading to retrieve programs, at boot, from a suitable code repository (thus avoiding software updating problems occurring when using e.g. ROMs), or to enable class loading on-demand during program execution (i.e. by means of the Class.forName() method). Java execution environments as those above are not suited and need to be integrated with modules able to perform loading, linking and execution of classical “.class” Java files. To this aim, in this paper we investigate how to integrate Java native libraries, written for an ahead-of-time compiler such as GCJ using the Cygnus Native Interface (CNI), into an interpreted JVM. The reasons that led us to start such a research derive from the need to integrate jRate in an environment based on a light executive—called NUXI [17, 2], developed by one of the authors—equipped with an interpreted J2ME [18] virtual machine—called Juice—designed for that executive. The aim is to provide a system developer with a complete solution for the design of a real-time embedded system not only capable to run ROMized/pre-compiled Java programs, but also suited for Java class loading and execution on-demand. To reach such an objective, the main issue to deal with is the integration of the CNI [12] into Juice. CNI is the interface to native classes provided by GCJ and used by jRate; it is based on C++ types, C++ class definitions and implementation, and is essentially designed for compiled environments1. Juice instead provides a pure-C native interface, called JuNI (Juice Native Interface). Therefore, in order minimize the changes needed to port jRate on Juice, JuNI++ has been designed and implemented, so to support calling of native methods implemented in C++ from interpreted code and vice versa. The resulting virtual machine has been called Juice++. It is worth noticing, how having a C++-like native interface gives several advantages in terms of code reuse, maintainability, and integration with existing C++ code. The paper is structured as follows, Section 1 motivates our work; Section 2 provides an overview of Juice and jRate; Section 3 introduces Juice++ and shows how we have addressed the challenges that arise in integrating Juice and jRate; Section 4 provides some comments on the proposed approach, also comparing it with other proposals; finally Section 5 gives our conclusions provides an outline of the future works.

2 Juice and jRate 2.1 Juice Juice is a POSIX-compliant J2ME-CLDC interpreted JVM. It was designed to be integrated in NUXI [17, 2], a light multi-tasking executive for Intel-based platforms, developed for both educational purpose and embedded/real-time applications. The aim is to obtain a complete solution—operating system and JVM—for a Java-enabled embedded system. Indeed, we investigated some other existing solutions before deciding to 1

Basically a native class is defined and implemented as a C++ class, provided that it uses suitably defined C++ types that corresponds to Java types.

write a new virtual machine from scratch. We had initially chosen KVM [20], the Sun’s J2ME-CLDC reference implementation, but, although the developed port worked well in NUXI, it was not suitable for our next step: to implement Java real-time services. KVM has a thread scheduler which does not use OS-native multi-threading and concurrency control services, but it simply performs a manual context switch each time it interprets 1000 bytecodes2 . This is obviously time/CPU-dependent and thus not a good solution for time-constrained environments. We considered other virtual machines, such as Superwaba [3] and Kaffe [1], but the former is too PDA-oriented while the latter is not J2ME and its implementation is too complex for our purpose (porting to NUXI and adaptation to J2ME should need too many patches and thus too much work). For these reasons, we decided to start to develop a new virtual machine, Juice, designed keeping in mind two basic requirements: execution on POSIX multi-threaded environments (as NUXI is) and tight integration with the executive. These objectives are met by using pthread-based services to support Java multi-threading (each Java thread is mapped to a system thread) and concurrency control (synchronized constructs mapped to mutexes and wait/notify methods implemented with condition variables). This implies a minimal overhead because there is a direct mapping with OS-native mechanisms. Since the targets of Juice are embedded systems, optimization, footprint and performances are key characteristics, which have been considered during development. Size reduction is achieved by loading only class parts which are strictly needed (debug information are not loaded, for example) and by hashing all strings in order to avoid redundancy and to speed-up comparisons (if two strings have the same hash pointer then the strings are the same). 2.2 jRate jRate 3 is an open-source RTSJ-based real-time Java implementation that we are developing at the Washington University, St. Louis (WashU). jRate extends the open-source GCJ front-end and runtime system [13] to provide an ahead-of-time compiled platform for the development of RTSJ-compliant applications. jRate differs from a traditional JVM since there is no bytecode interpretation. Instead, jRate ahead-of-time compiles RTSJ applications into native code. jRate uses Generative Programming (GP) [10] in order to make it possible to have a configurable, customizable and yet efficient RTSJ implementation. GP is also used as a way of exploring design alternatives, which differ from the RTSJ, in a well engineered manner. The generative behaviour is achieved by using a series of techniques and tools such as C++ template meta programming [10], AspectJ [21] and Python [16] scripting. In order to maintain the jRate runtime reusable, configurable, and highly efficient, jRate relies on a set of RTSJ-like C++ Template classes. This set of C++ Template classes, called the jRate-Core, provide a configurable kernel which can be reused in different settings and language binding by properly instantiating the template classes. As depicted in Figure 1, RTSJ applications developed using jRate rely on the GCJ runtime for basic Java services, and on the jRate runtime for RTSJ services. In order 2 3

This number is indeed a factor scaled to thread’s priority. jRate can be freely downloaded at http://tao.doc.wustl.edu/˜corsaro/jRate

to make it easier to reuse the jRate code base, most of the extension that were needed by the GCJ runtime have been factored out and moved to the jRate-Core. As shown in Figure 1, the RTSJ binding represents just one instance of use of this C++ core, and by writing the proper binding, RTSJ-like abstraction could be provided to other languages such as C#, C++ etc. RTSJ Application

RTSJ Services

javax.realtime Classes

J2SE Services

GCJ Runtime

Memory Management, Scheduling, etc. RTSJ C++ Peers

jRate C++ Core Binding Layer

Fig. 1. The jRate-Core and RTSJ binding

Currently jRate supports most of the RTSJ features such as memory areas, real-time threads, periodic threads, asynchronous event handlers, timers, etc. It allows a fine control over the properties of the different types of memory areas, such as size, allocators, locking etc. jRate provides several optimizations such a constant time memory reference checking, and single parent rule test implementation [7] and lock free dispatching of events [8].

3 Juice++: Integrating Juice and jRate One of the issues that arise when using jRate is that the runtime footprint of GCJ based applications is currently quite high. This is due to the fact that GCJ libraries are currently too monolithic and hard to subset. This inflexibility badly matches the requirements of small-end embedded systems, in which JVM and application have to take on the order of kilobytes of memory. Thus, jRate on GCJ is not currently a viable solution for very small footprint devices, for which memory constraints are more stringent than the runtime efficiency. On the other end, Juice was designed to be a small footprint JVM (it also run on NUXI which is well suited for devices with limited memory resources). Based on what said so far, it should seem natural that the way to go to provide a uniform solution across different embedded systems classes is that of porting the jRate runtime system to Juice. 3.1 Requirements for Porting the jRate Runtime A key requirement to make the porting of jRate practically effortless, is that the target JVM supports CNI, which is GCJ’s preferred native interface. The choice of using CNI in jRate was dictated by several factors, some of which include performances and programmability—CNI allows to use a classical C++ programming style for the development of native methods and provides better performances than Sun’s Java Native Interface (JNI) [19].

The approach we have decided to follow is that of designing a CNI-like native interface for Juice, called JuNI++ (Juice Native Interface). In such a design work, we faced several challenges since, (1) differently from CNI, we did not want to require a Javaaware C++ compiler as GCJ is, and (2) we wanted to support a full mapping from Java to C++, also adding some features, such as support for interfaces, which are not currently provided by CNI. Shortly, our objective was to have Java and C++ interoperate in a safe, efficient and seaming-less manner. In order to achieve this goal, the main issues which have to be addressed can be classified in two categories: Java-to-C++ mapping issues and Java-to-C++ interoperability issues. The Java-to-C++ mapping is rather important since it has to provide an intuitive and easy to program environment. On the other hand the mechanism chosen to achieve Java-to-C++ interoperability has to be efficient in both time and space, otherwise it would not be usable in embedded systems with stringent time and space constraints. Next we provide an overview of JuNI++, as well an overview of the issues we had to solve to make it possible to support a CNI-like native interface without requiring cooperation from the C++ compiler. RTSJ

Application

Bytecode

Native code bytecode execution

classes

JuNI++ bytecode−to−native

javax.realtime

Juice

native−to−bytecode

jRate Core

proxies

Fig. 2. Juice++ Architecture

3.2 JuNI++ Figure 2 depicts the structure of the solution adopted by showing the modules composing Juice++4 , their relationships and the role of JuNI++. We consider a generic (RTSJ) application which is composed by Java and native portions and the jRate runtime. Given that bytecode is interpreted by the Juice virtual machine, the JuNI++ module provides the appropriate services to perform access to native code (i.e. native method invocation), from the bytecode interpreter, and access to bytecode (i.e. bytecoded method invocation, reading/writing object attributes) from native code. This is made possible by means of “proxies”, which are pieces of code acting as bridges and suitably produced by the JuNI++ Proxy Generator (JuniPG), given the class files constituting the application and the needed libraries. It is worth noticing, that the proxies need to be generated for all those classes that are explicitly used in the native code—even if these classes do not implement native methods. This is needed since the native code has to be able to call, from the C++ side, the method defined in these classes. Writing native code using JuNI++ is rather straightforward since the Java language features can be seen (to a certain extent) as a proper subset of the C++ language. To this extent, Table 1 reports the correspondence between Java and C++ constructs and types. 4

Juice++ is the JVM resulting from the integration of JuNI++ into Juice

Java C++ class class package namespace reference pointer operator . operator char jchar int jint long jlong float jfloat double jdouble Java Exception C++ Exception 











These types are defined by means of typedef statements on the basis of the target platform and compiler. Table 1. Java-to-C++ Mapping

In order to understand what is the Java-to-C++ programming model provided by JuNI++ lets consider a simple example. Let’s assume that we are implementing a Counter class, and that we want to write some methods of this class natively (this is indeed an artificial example, but it is meant to explore most of the JuNI++ features in a contained way). Let’s then assume that the Counter class, whose code is listed in Listing 1, provides some methods implemented directly in Java and some other methods declared as native, such as inc(), dec() and so on. It also implements the Comparable interface, and throws an exception in case the counter’s count becomes either negative or exceeds a certain maximum value. Listing 2 contains the code generated by the JuniPG. As it can be easily seen from the listing, a sample::Counter C++ class corresponds to the sample.Counter Java class. The former class is declared within a namespace which matches the package in which the Java class was declared. Something worth noticing is that none of the C++ class methods declare any exception. This is done so that C++ counter part of Java classes can throw Java unchecked exceptions. The last thing that should be noticed in Listing 2 is the declaration of proxy methods. As it will be shown next, these methods provide all the glue code that ties together the Java and the C++ world. Listing 3 contains the implementation of the native methods. As shown in this listing, the JuNI++ native interface provides a quite natural way of mapping Java native methods to C++. While the example shown so far seems to be quite simple, there are actually quite a few issues that need to be taken care of in order to make Java and C++ interoperate in an efficiently and seamless manner. So far we have seen how to implement native methods, but we have not revealed how Java code can actually invoke methods on C++ classes and vice-versa. Next Section will provide an explanation of the techniques used by JuNI++ to achieve this goal. In our proposal we tried to come up with a solution that, as much as it could, would not rely on the C++ object layout, and on specific features

Listing 1: Java code for the Counter sample package sample; class Counter implements Comparable { private int count, maxCount; public Counter(int maxCount) { this.maxCount = maxCount; } public void reset() { this.count = 0; } public native void resetAndInc(); public native void inc() throws CountOverflowException; public native void dec() throws CounterUnderflowException; public native int getCount(); public native void setCount(int newCount); public int compareTo(Object o) { this.compareTo((Counter)o); } public int compareTo(Counter c) { (this.count < c.count) ? -1 : (this.count == c.count) ? 0 : 1; } }

provided by a given C++ compiler. This implies a high degree of portability was a big concern taken into account in designing our solution. 3.3 Making Java talk with C++ The first problem that has to be solved is that of making it possible, for the JVM, to invoke C++ methods that implement native code. As widely known, the mechanism used by JVM to identify a method is based on its “signature” which is obtained through the operand associated with invokeXXX opcodes5 (see [15]); the problem is thus to provide an efficient and effective way to directly access a C++ method starting from a string Java signature. Our solution is strictly C++ based and uses a variation of the Proxy design pattern [11] to provide a translation layer between Java-style and C++-style method invocations: this is made possible by means of the proxy methods generated by JuniPG (see Listing 2). When Juice++ executes a “new” opcode, it passes the control to JuNI++ which creates the proper C++ objects. Figure 3 clarifies this mechanism with reference to the Counter example: there, the execution of “new” triggers the creation of an instance of the C++ class Counter. Thanks to its structure, the C++ object created provides a quick and effective way to access native C++ implementation. The mechanism is based on a static table, the methodCallerTable in Listing 2, which contains the pointers to some proxy methods which are called invokeMethod0(), invokeMethod1(), etc., each associated and implementing the call to a native method. More specifically, the steps performed by the JVM when a “invokevirtual” (or 5

invokevirtual, invokestatic, invokespecial and invokeinterface.

Listing 2: C++ mapping for the Java simple Counter #include #include namespace sample { class Counter; }} class sample::Counter : public ::java::lang::Object, public ::java::lang::Comparable { public: Counter(jint); void resetAndInc(); void inc(); void dec(); jint getCount(); void setCount(jint); int compareTo(::java::lang::Object* o); int compareTo(::sample::Counter* c); private: jint count; jint maxCount; public: /* Proxy Methods */ virtual jvalue getField(int index, int depth); virtual void setField(int index, int depth, jvalue val); virtual void invokeMethod(int index, int depth, jenv env); jvalue getField0(); void setField0(jvalue value); void invokeMethod0(jenv env); void invokeMethod1(jenv env); void invokeMethod2(jenv env); void invokeMethod3(jenv env); void invokeMethod4(jenv env); protected: static FieldSetter_t fieldSetterTable[]; static FieldGetter_t fieldGetterTable[]; static MethodCaller_t methodCallerTable[]; };

Listing 3: C++ mapping for the Java simple Counter void sample::Counter::inc() { this->count++; if (this->count > this->maxCount) throw CountOverflowException; } void sample::Counter::resetAndInc() { this->reset(); this->count++; } void sample::Counter::setCount (jint newCount) { this->count = newCount; }

“invokestatic”) opcode is executed are described below, and also illustrated by the Figure 3 with reference to the Counter example (let us consider, for the moment, that the called method belongs to the object’s class and not to one of its ancestors): 1. A numerical methodIndex is derived by the method signature. This corresponds to the index of the method in the method area of the class;

Juice++ Interpreter

Pointer to the Counter object

void setCount (jint newCount) { this−>count = newCount; }

new ... ...

invokevirtual

Counter

JuNI++ translates method signature to "index" and "depth"

By means of methodCallerTable the proxy method is called

void invokeMethod (index, dept, env) { (this−>*methodCallerTable[index])(env); } void invoke4 (jenv env) { handle−>setCount (JPARAM (env,0,jint)); }

Fig. 3. Proxy Objects and Java-to-C++ Access

2. The invokevirtual bytecode is replaced with invokevirtual quick, with the operand equals to methodIndex. This operation avoids another string lookup and speed-up the next execution of this call (indeed this kind of optimization is performed in many virtual machines, as suggested in [15]); 3. The JVM prepares the method parameters, which are marshaled in a structure of the jenv type—this is the “method environment”, a structure containing data needed for the execution of method, i.e. the parameters, the local frame, the operand stack, a reference to the current thread, etc. 4. The method invokeMethod() of the C++ object is called, passing as parameters the methodIndex and the method environment (the meaning of the “depth” parameter will be instead explained later on); 5. The proxy method invokeX() is called by means of an indirect addressing on the basis of the dispatch table methodCallerTable; 6. The proxy method calls the native method. The proxy method extracts the parameters from the method environment, by using the JPARAM macro, and finally calls the native method of the C++ object. If there is an inheritance tree and the method invoked is not implemented in the class’ object but in one of its ancestors, the step 1 of the sequence above does not succeed as the requested method cannot be found in that class. In this case, several solution are possible to find the right method, some of which trade space for time efficiency. The most elegant but, not necessarily the most time efficient, is based on recursion. During the execution of the step 1 above, the JVM recursively searches for the method signature in the ancestor classes and, each time a hierarchy level is passed, a depth parameter is incremented by one. When the method is found, this parameter represents the depth of the inheritance hierarchy, starting from the class of the referenced object, for the class which implements the searched method. The depth parameter determined is passed to the invokeMethod() of a proxy; this method checks if depth is zero— meaning that the implementation of the native method is in the associated class—the steps 5 and 6 above are directly executed; if it is non-zero, the invokeMethod() of the ancestor C++ class is called with the depth decremented by one. The process is thus executed recursively until the depth reaches zero, meaning that the class implementing the requested method was found. Using the described technique, execution time depends on the value of the depth parameter. A constant time solution could be instead easily implemented by using the depth as an index into an array of pointers which refer to the dispatch tables of parent classes. In this case, the cost to pay is the use of more memory space than the former solution, since each class must include the array of pointers in addition to its own dispatch table.

3.4 Accessing Attributes Another issue concerning Java-to-C++ interoperability is the access to object attributes. According to the programming model of JuNI++/CNI, each attribute declared in a Java class is directly mapped to an attribute declared in the C++ native implementation (see attribute count of the Counter example). This allows C++ native methods to access attributes using the standard “- ” operator (see Listing 2), but when the JVM needs to read or write such an attribute (by means of “getfield” / “putfield” opcodes), a suitable technique able to reach a C++ attribute starting from its Java string signature is needed. This problem is quite similar to that of invoking native methods and is therefore solved using a technique based to that of Sect. 3.3. As Listing 2 reports, the C++ object has two static pointer tables, fieldGetterTable and fieldSetterTable, generated by JuniPG. They contain the pointer to the proxy methods getField0(), setField0(), getField1(), setField1(), etc. Therefore, like native method invocation, the JVM, when executing “getfield” / “putfield” opcodes, determines the “fieldIndex” and “depth” parameters, and then invokes the method getField()/putField() of the C++ object. The latter calls the proxy method (by using the pointer table) which, in turn, performs read/write of the relevant object field. 3.5 Handling Interfaces The Java language allows multiple inheritance of interfaces, thus their treatment in JuNI++ deserves some further discussion—multiple inheritance usually complicates method dispatching. Java interfaces can only define constant values (static final attributes) and declare methods: interfaces are treated by JuNI++ as regular C++ classes, transforming the implements clause in a standard C++ inheritance (see the example in Listing 2 which shows how is declared class Counter which, in Java, implements the interface java.lang.Comparable). Constant values thus do not present particular issues: if a Java interface IntA defines a constant foo, JuniPG will generate a C++ class, called IntA, defining the static attribute foo. The latter can thus be accessed from native code with the usual C++ mode IntA::foo. As for methods are concerned, their definitions appear in the constant pool of the class implementing the interface, and thus can be treated as regular methods with the mechanism described in Sect. 3.3. Only the use of the “quick” variant of the invokeinterface opcode requires particular care, since the object implementing an interface could not be always the same and the index of the method to be invoked thus varies on the basis of the class of the target object. In the current prototype version of JuNI++ this problem is solved as suggested in the Sun’s JVM specification [15]. 3.6 Making C++ talk with Java The last issue to be dealt with, is related to the invocation of Java methods from native code. Since the aim is to provide a totally C++-based native programming environment, the problem is to allow native code to invoke a Java method with the usual statement

“object- method name (params)”. Listing 3 shows an example in which a native method—resetAndInc()—invokes a method—reset()—which is written in Java (see also Listing 1). Making C++ talk with Java is much easier than the reverse. The C++to-Java interoperability can be obtained, by using proxies which provide the proper interface. Specifically, for each Java class defining a method called by some native code, the JuniPG automatically generates the associated C++ class and embeds in it a method with the same name and prototype. This C++ method is indeed a proxy that prepares the local parameters, invokes the Java method and gathers a return value, if present. Listing 4 reports an example of such a proxy code for the Counter example. It shows the code generated to provide access to Java method reset(): it first creates the environment needed for method execution, then invokes the Java method through a call to the JVM interpreter6 and finally releases the memory held by the jenv variable since it is no more used.

Listing 4: A C++ proxy accessing a Java method void sample::Counter::reset() { jenv * env = newJavaEnvironment (this); callJavaMethodFromCXX (this, sample_Counter_reset_V_V, env); destroyJavaEnvironment (env); }

4 Discussion The work described in this paper demonstrates how it is possible to embed into a JVM, designed for bytecode interpretation, some native code written using a programming model typical of (ahead-of-time) compiled environments. Even if our work derives from the necessity to solve a specific problem—porting jRate to an embedded environment—it presents several interesting aspects and provides also some advantages with respect to other approaches used in building JVMs and native interfaces. It is worth noticing that the main characteristic of our approach is that it provides a way to integrate C++ code within Java programs; this gives two advantages on native code development and execution: (i) a more simple and standard programming model, and (ii) a performance improvement. As for the former item, using C++ means to provide a standard way to write native code and does not force the programmer to use JVM-specific services to access Java methods or attributes from the native part implementation. Anyone who has written native methods knows how verbose and cumbersome is Sun’s JNI [19]. On the other hand, with JuNI++, invoking a method is as easy as: “object- method (params)”. 6

sample Counter reset V V is the pointer to the hashed string containing the signature of the Java method; it is generated by the JuniPG.

This basically means that the programmer can ignore the services provided by the native interface of the JVM employed, and the knowledge of Java classes of the API and the developed application, together with the names of C++ types to be used to represent Java types7 , suffices to immediately start writing native code. Obvious consequences are reuse and portability; the former is due to the fact that re-engineering or upgrade of the native interface do not have any effect on native code, since only the stub generator (JuniPG) needs to be re-written to reflect the new structure/functionality of the native interface, while native code remains the same. Portability implies the possibility to use the same native code on different virtual machines, provided that they are equipped with the appropriate stub generator: indeed our approach in designing the native interface is quite general and can be adapted to any virtual machine, thus enabling native code portability. As far as performance improvement is concerned, even if we still have not performed appropriate measures8 , we expect that our approach provides better performances than JNI. Indeed, if we analyze the way in which JNI and JuNI++ implement Java-to-native and native-to-Java interoperability, we find that (1) Java-to-native interoperability roughly needs the same amount of operations in both JNI and JuNI++ (method finding, parameter preparation in the stack or in a structure, calling to the native method), (2) native-to-Java interoperability should definitively be more efficient in JuNI++—attributes and methods are directly accessed (from native code) without the necessity to use interface functions as described above. Indeed, we plan to perform, in the near future, some measurement tests in order to validate what we assert and optimize—when needed—the code of JuNI++.

5 Conclusions This paper proposed a software architecture that allows seamless integration of native code written in C++, into Juice, an interpreted POSIX-compliant Java Virtual Machine developed to be used in a small executive designed for embedded systems. The proposed architecture basically served to support the porting of the jRate RTSJ implementation into Juice, thus providing a complete solution for a Java-enabled real-time embedded environment. To this aim, we designed a native interface, called JuNI++, aimed at providing the layer needed to perform Java/C++ integration. This is done by using proxy methods which bridge Java code with C++ native code. Proxy methods are provided by means of a suitable code generator, called JuniPG, which builds the interface code starting from the class files of the Java application. We called Juice++ the JVM resulting from the introduction of JuNI++ into Juice. As for future works, we plan to improve Juice++ by analyzing its characteristics in terms of performances and memory footprint. Our aim is to allow the introduction of optimizations on the basis of the requirements of the target environment, e.g. for size, if the target has limited memory resources, or speed, in the case of applications needing fast execution. This will be done means of options passed to JuniPG which will generate the proxy code, suitably optimized for the desired requirements. 7 8

i.e. jint for int, jchar for char, etc. This will be a subject of our future works as well as code optimization.

References 1. 2. 3. 4.

5.

6. 7.

8.

9.

10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.

Kaffe Virtual Machine Home Page. http://www.kaffe.org, 2002. NUXI Home Page. http://nuxi.iit.unict.it, 2002. Superwaba Home Page. http://www.superwaba.org, 2002. Antonella Di Stefano and Corrado Santoro. A Java Kernel for Embedded Systems in Distributed Process Control. IEEE Concurrency, , special issue on “Operating Systems Research & Development”, 8(4), 2000. Antonella Di Stefano and Corrado Santoro. Java Devices for Distributed Process Control. In Proc. of 2000 IEEE International Symposium on Industrial Electronics (ISIE 2000). IEEE, 2000. Bollella, Gosling, Brosgol, Dibble, Furr, Hardin, and Turnbull. The Real-Time Specification for Java. Addison-Wesley, 2000. A. Corsaro and R. K. Cytron. Efficient Memory-Reference Checks for Real-Time Java. In Proceedings of the 2003 ACM SIGPLAN conference on Language, compiler, and tool for embedded systems, pages 51–58. ACM Press, 2003. A. Corsaro and D. C. Schmidt. Evaluating Real-Time Java Features and Performance for  Real-time Embedded Systems. In Proceedings of the  IEEE Real-Time Technology and Applications Symposium, San Jose, Sept. 2002. IEEE. A. Corsaro and D. C. Schmidt. The Design and Performance of the jRate Real-Time Java Implementation. In R. Meersman and Z. Tari, editors, On the Move to Meaningful Internet Systems 2002: CoopIS, DOA, and ODBASE, pages 900–921, Berlin, 2002. Lecture Notes in Computer Science 2519, Springer Verlag. K. Czaenwcki and U. W. Eisenecker. Generative Programming: Methods, Tools, and Applications. Addison-Wesley, Reading, Massachusetts, 2000. E. Gamma, R. Helm, R. Johnson, and R. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. Reading, MA, 1994. GNU is Not Unix. The Cygnus Native Interface for C++/Java Integration. http://gcc. gnu.org/java/papers/cni/t1.html. GNU is Not Unix. GCJ: The GNU Complier for Java. http://gcc.gnu.org/java, 2002. IBM. WebSphere Studio Device Developer. http://www-3.ibm.com/software/ wireless/wsdd/, 2003. T. Lindholm and F. Yellin. The Java(TM) Virtual Machine Specification. Addison-Wesley, 2nd edition, 1999. M. Lutz. Programming Python. O’Reilly, 2nd edition, 2001. C. Santoro. An Operating System in a Nutshell. Internal Report, Dept. of Computer Engineering and Telecommunication, UniCT, Italy, 2002. Sun Microsystems inc. Java Micro Edition Documentation. http://java.sun.com/ j2me, 2002. Sun Microsystems inc. Java Native Interface Documentation. http://java.sun.com/ j2se/1.4.2/docs/guide/jni/index.html, 2002. Sun Microsystems inc. KVM White Paper. http://java.sun.com, 2002. The AspectJ Organization. Aspect-Oriented Programming for Java. http://www. aspectj.org, 2001.

Suggest Documents