/* * This file is part of JMatLink. * This class is the core class of JMatLink. It makes the connection to the "C"-part * of the project. Only really necessary methods are in this class. All other * functionality is inside JMatLink. * */ //**************************************************************************** // The MATLAB-engine must only be called from the SAME thread * // all the time!! On Windows systems the ActiveX implementation is * // supposed to be the reason for this. I don't know if that happens * // on other platforms, too. * // To achieve this. All native methods of this class, all accesses to * // the engine, are called from the SAME thread. Since I don't know * // how to call them directly I set up a mechanism to send messages * // to that thread and ask it to process all requests. Some locking * // mechanism locks up the engine for each single call in order * // to stay out of concurrent situations / accesses. * //The problem is ActiveX: one only can make calls to the engine library //(especially engEvalString) from ONE single thread. //**************************************************************************** package jmatlink; //import java.io.*; import java.util.Vector; // the following functions are available in engine.h of MATLAB //extern Engine *engOpenSingleUse(const char *startcmd, void *reserved, int *retstatus ); //extern mxArray *engGetVariable( Engine *ep, const char *name ); //extern int engPutVariable( Engine *ep, const char *var_name, const mxArray *ap ); //extern int engOutputBuffer( Engine *ep, char *buffer, int buflen ); //#define engOpenV4() cannot_call_engOpenV4 //#define engGetFull() engGetFull_is_obsolete //#define engPutFull() engPutFull_is_obsolete //#define engGetMatrix() engGetMatrix_is_obsolete //#define engPutMatrix() engPutMatrix_is_obsolete // obsolete //#define engPutArray(ep, ap) engPutVariable(ep, mxGetName(ap), ap) //#define engGetArray(ep, name) engGetVariable(ep, name) public class CoreJMatLink extends Thread { // static declarations // the variable "status" is used to tell the main // thread what to do. private final static int idleI = 0; private final static int engOpenI = 1; private final static int engCloseI = 2; private final static int engEvalStringI = 3; private final static int engGetScalarI = 4; private final static int engGetVariableI = 5; private final static int engPutVariableI = 6; private final static int engOutputBufferI = 7; private final static int engGetOutputBufferI = 8; //private final static int engGetCharArrayI = 9; private final static int destroyJMatLinkI = 10; private final static int engOpenSingleUseI = 11; private final static int engSetVisibleI = 12; private final static int engGetVisibleI = 13; // All variables are global to allow all methods // and the main thread to share all data private int status = idleI; private String arrayS; private String engEvalStringS; private int engOutputBufferInt; private String engOutputBufferS; private double engGetScalarD; private double[][] engGetVariableD; private double[][] engPutVariable2dD; //private String[] engGetCharArrayS; private long epL; /* Engine pointer */ private int retValI; /* return Value of eng-methods */ private String startCmdS; /* start command for engOpen... */ private int buflenI; /* output buffer length */ private boolean debugB = false; private boolean engVisB = false; // Locks private boolean lockEngineB = false; private boolean lockThreadB = false; private boolean lockWaitForValueB = false; public static final int THREAD_DEAD = 0; public static final int THREAD_STARTING = 1; public static final int THREAD_RUNNING = 2; public static final int THREAD_DYING = 3; private int threadStatus = THREAD_DEAD; // vector for all open pointers to MATLAB engines private Vector enginePointerVector = new Vector(); private long engOpenPointerL = 0; // pointer for engOpen() without pointer as argument private Thread runner; // *********************** native declarations **************************** // NEVER call native methods directly, like // JMatLink.engEvalStringNATIVE("a=1"). MATLAB's engine has quite some // thread problems. private native void engTestNATIVE(); private native long engOpenNATIVE (String startCmdS ); private native long engOpenSingleUseNATIVE (String startCmdS ); private native int engCloseNATIVE (long epL); private native int engSetVisibleNATIVE (long epL, boolean visB); private native int engGetVisibleNATIVE (long epL); private native int engEvalStringNATIVE (long epL, String evalS ); private native double engGetScalarNATIVE (long epL, String nameS ); private native double[][] engGetVariableNATIVE (long epL, String nameS ); private native void engPutVariableNATIVE (long epL, String matrixS, double[][] valuesDD); private native int engOutputBufferNATIVE (long epL, int buflenI ); private native String engGetOutputBufferNATIVE(long epL ); private native void setDebugNATIVE (boolean debugB ); // ******************************************************************************* // ************** load JMatLink library into memory ********************** static { try { //System.out.println("loading"); System.loadLibrary("JMatLink"); //System.loadLibrary("jmatlink"); //System.out.println("loaded"); System.out.println("JMatLink.dll loaded OK!"); } catch (UnsatisfiedLinkError e) { // print some useful information for fault tracking System.out.println("ERROR: Could not load the JMatLink library"); System.out.println(" Win: This error occures, if the path to"); System.out.println(" MATLAB's <matlab>\\bin directory is"); System.out.println(" not set properly."); System.out.println(" Or if JMatLink.dll is not found.\n"); System.out.println(" Linux: Check if <matlab>/extern/lib/glnx86 (libeng.so, libmat.so, etc.)"); System.out.println(" and <matlab>/sys/os/glnx86 (libstdc++-libc6.1-2.so.3) are in your path.\n"); System.out.println(" (you can also copy missing libraries to your local path).\n"); System.out.println("**** Find important information below ****"); String os_name = System.getProperty("os.name"); System.out.println("OS Name = "+ os_name); String libpathnames = System.getProperty("java.library.path"); System.out.println("Libpathnames = "+libpathnames); String classpathnames = System.getProperty("java.classpath"); System.out.println("Classpathnames = "+classpathnames); String os_dependant_lib_file_name = System.mapLibraryName("JMatLink"); System.out.println("os dependant lib file name = "+ os_dependant_lib_file_name); System.out.println("**** Copy all above text and send it to ****"); System.out.println("**** stefan@held-mueller.de ****"); System.out.println("**** for inspection and fault tracking ****"); } } /** This is the constructor for the CoreJMatLink library. */ public CoreJMatLink() { if (debugB) System.out.println("JMatLink constructor"); } /** this restarts the engine thread * */ private void restart() { // check if thread is already running, if yes: do not restart thread if ((threadStatus == THREAD_RUNNING ) || (threadStatus == THREAD_STARTING) ) return; lockEngineLock(); // check if thread is still dyning and not finally dead //if (destroyJMatLinkB == false) if (threadStatus != THREAD_DEAD ) throw(new JMatLinkException("engine still open "+threadStatus)); // thread is not running -> restart threadStatus = THREAD_STARTING; runner = new Thread(this); runner.start(); releaseEngineLock(); if (debugB) System.out.println("JMatLink restarted"); } /** closing of the engine thread takes some time. During this time NO call * to engOpen() or engOpenSingleUse() MUST be made. * * @return status of the CoreJMatLink thread (DEAD, STARTING, DYING, RUNNING) */ public int getThreadStatus() { return threadStatus; } /** this kills the engine thread * */ public void kill() { lockEngineLock(); // set current thread status from RUNNING to DYING threadStatus = THREAD_DYING; callThread(destroyJMatLinkI); runner = null; releaseEngineLock(); if (debugB) System.out.println("JMatLink kill"); } /** Returns the number of currently opened engines * * @return number of open engines */ public int getNoOfEngines() { return enginePointerVector.size(); } /** Open engine. This command is used to open a <b>single</b> connection * to MATLAB.<p> This command is only useful on unix systems. On windows * the optional parameter <b>must</b> be NULL. * This method is used in conjunction with engClose() * @param startCmdS start command for engine. Does not work on Windows. */ public synchronized void engOpen(String startCmdS) { // check if engOpen() has been called before if (engOpenPointerL != 0) throw(new JMatLinkException("engine already open")); // restart thread, maybe it has been killed before restart(); lockEngineLock(); lockWaitForValue(); this.startCmdS = startCmdS; callThread( engOpenI ); WaitForValue(); releaseEngineLock(); // if native method return 0, then engine didn't open if (engOpenPointerL==0) throw(new JMatLinkException("couldn't open engine")); // Check if engine pointer is already in use // This must never be true, since we keep track of the pointer // in CoreJMatLink. But we are checking anyway, because the // pointer is created in the "C"-part if (enginePointerVector.contains(new Long(engOpenPointerL))) throw(new JMatLinkException("pointer already in use "+engOpenPointerL)); // store handle to engine in vector enginePointerVector.add(new Long(engOpenPointerL)); } // end engOpen /** Open engine for single use. This command is used to open * <b>multiple</b> connections to MATLAB. * This method is used in conjunction with engClose( enginePointer ) * @param startCmdS start command for engine. Does not work on Windows. */ public synchronized long engOpenSingleUse(String startCmdS) { // restart thread, maybe it has been killed before restart(); lockEngineLock(); lockWaitForValue(); this.startCmdS = startCmdS; callThread( engOpenSingleUseI ); WaitForValue(); releaseEngineLock(); // if native method returns 0, then engine didn't open if (this.epL==0) throw(new JMatLinkException("couldn't open engine")); // Check if engine pointer is already in use. // This must never be true, since we keep track of the pointer // in CoreJMatLink. But we are checking anyway, because the // pointer is created in the "C"-part if (enginePointerVector.contains(new Long(epL))) System.out.println("engine pointer already in use "+epL); // store handle to engine in has map enginePointerVector.add(new Long(epL)); return this.epL; } // end engOpenSingleUse /** Close the connection to MATLAB. * This method is used in conjunction with engOpen() */ public synchronized void engClose() { // close conncection to general engine engClose( engOpenPointerL ); // set marker of engOpen() to 0 engOpenPointerL = 0; } // end engClose /** Close a specified connection to an instance of MATLAB. * @param epL close one connection to the engine, with pointer epL */ public synchronized void engClose(long epL) { // check if handle exists and is still open if (!enginePointerVector.contains(new Long(epL))) throw(new JMatLinkException("handle does not exist")); // remove buffer for engOutputBuffer engOutputBuffer(epL, 0); lockEngineLock(); lockWaitForValue(); this.epL = epL; callThread( engCloseI ); WaitForValue(); releaseEngineLock(); // if retValI returns <>0, then command didn't work if (retValI!=0) throw(new JMatLinkException("engClose didn't work")); // remove handle to engine from hash table enginePointerVector.remove(new Long(epL)); // check if all engines are closed, if yes terminate JMatLink-thread if (enginePointerVector.isEmpty()) kill(); // return retValI; Return value indicates success } /** * Close all open handles to MATLAB. This methods closes all connections, which * have been opened using engOpen() and engOpenSingleUse(). * */ public synchronized void engCloseAll( ) { // loop until all connections to the engine are closed/removed while(!enginePointerVector.isEmpty()) { // get pointer from vector of pointers long pointer = ((Long)enginePointerVector.elementAt(0)).longValue(); // call closing function engClose(pointer); // remove pointer from vector of pointers enginePointerVector.remove(new Long(pointer)); // if pointer to general engine has been removed, clear general pointer if (engOpenPointerL == pointer) engOpenPointerL = 0; } // end while // check if all engines are closed, if yes terminate JMatLink-thread //if (enginePointerVector.isEmpty()) // kill(); } // end engCloseAll /** * * @param epL * @param engVisB */ public synchronized void engSetVisible(long epL, boolean engVisB) { if (!enginePointerVector.contains(new Long(epL))) throw(new JMatLinkException("engine unknown")); lockEngineLock(); lockWaitForValue(); this.epL = epL; this.engVisB = engVisB; callThread( engSetVisibleI ); WaitForValue(); releaseEngineLock(); // if retValI returns <>0, then command didn't work (e.g. engine closed) if (retValI!=0) throw(new JMatLinkException("engSetVisibility didn't work")); } // end engSetVisible /** * * @param epL * @return */ public synchronized boolean engGetVisible(long epL) { if (!enginePointerVector.contains(new Long(epL))) throw(new JMatLinkException("engine unknown")); lockEngineLock(); lockWaitForValue(); this.epL = epL; callThread( engGetVisibleI ); WaitForValue(); releaseEngineLock(); System.out.println("retValI " + retValI); if (retValI==0) return false; else if (retValI==1) return true; else throw(new JMatLinkException("engGetVisibility didn't work")); } // end engGetVisible /** Evaluate an expression in MATLAB's workspace. This function is used for the * general engine connection. Usage is in conjuction with engOpen(), engClose() * * @param evalS String which contains MATLAB commands (e.g. "sin(a);b=rand(3,4)") */ public synchronized void engEvalString(String evalS) { if (debugB) System.out.println("engopenPointerL eval "+engOpenPointerL); engEvalString( engOpenPointerL, evalS); } /** Evaluate an expression in MATLAB's workspace. This function is used for all * connections to MATLAB which have been opened by epL=engOpenSingleUse(), engClose(epL) * * @param epL pointer to individual connection to the engine * @param evalS String which contains MATLAB commands (e.g. "sin(a);b=rand(3,4)") */ public synchronized void engEvalString(long epL, String evalS) { if (!enginePointerVector.contains(new Long(epL))) throw(new JMatLinkException("engine unknown")); // evaluate expression "evalS" in specified engine Ep if (debugB) System.out.println("eval(ep,String) in " + epL + " "+evalS); lockEngineLock(); lockWaitForValue(); this.epL = epL; engEvalStringS = evalS; callThread( engEvalStringI ); WaitForValue(); releaseEngineLock(); // if retValI returns <>0, then command didn't work (e.g. engine closed) if (retValI!=0) throw(new JMatLinkException("engEvalString didn't work")); if (debugB) System.out.println("eval(ep,String) out "+epL+" "+evalS); } // end engEvalString /** Get a scalar value from MATLAB's workspace. * @param scalar variable in MATLAB's workspace */ public synchronized double engGetScalar(String arrayS) { return engGetScalar( engOpenPointerL, arrayS); } /** Get a scalar value from a specified workspace. * * @param epL pointer to individual connection to the engine * @param evalS String which contains MATLAB commands (e.g. "sin(a);b=rand(3,4)") * @return scalar value from the workspace */ public synchronized double engGetScalar(long epL, String arrayS) { // Get scalar value or element (1,1) of an array from // MATLAB's workspace // Only real values are supported right now if (!enginePointerVector.contains(new Long(epL))) throw(new JMatLinkException("engine unknown")); lockEngineLock(); lockWaitForValue(); /* copy parameters to global variables */ this.epL = epL; this.arrayS = arrayS; callThread( engGetScalarI ); WaitForValue(); releaseEngineLock(); return engGetScalarD; } // end engGetScalar /** Get an array from MATLAB's workspace. * * @param */ public synchronized double[][] engGetVariable(String arrayS ) { return engGetVariable(engOpenPointerL, arrayS ); } /** Get an array from a specified instance/workspace of MATLAB. * @param * @param */ public synchronized double[][] engGetVariable(long epL, String arrayS ) { // only real values are supported so far if (!enginePointerVector.contains(new Long(epL))) throw(new JMatLinkException("engine unknown")); lockEngineLock(); lockWaitForValue(); this.epL = epL; this.arrayS = arrayS; callThread( engGetVariableI ); WaitForValue(); releaseEngineLock(); return engGetVariableD; } /** Put an array (2 dimensional) into MATLAB's workspace. * @param * @param */ public synchronized void engPutVariable( String arrayS, double[][] valuesDD ) { engPutVariable( engOpenPointerL, arrayS, valuesDD ); } /** Put an array (2 dimensional) into a specified instance/workspace of * MATLAB. * * @param * @param * @param */ public synchronized void engPutVariable(long epL, String arrayS, double[][] valuesDD) { // send an array to MATLAB // only real values are supported so far if (!enginePointerVector.contains(new Long(epL))) throw(new JMatLinkException("engine unknown")); lockEngineLock(); lockWaitForValue(); this.epL = epL; this.arrayS = arrayS; this.engPutVariable2dD = valuesDD; callThread( engPutVariableI ); WaitForValue(); releaseEngineLock(); } /** Return the outputs of previous commands from MATLAB's workspace. * * @return */ public synchronized int engOutputBuffer( ) { if (!enginePointerVector.contains(new Long(engOpenPointerL))) throw(new JMatLinkException("engine unknown")); return engOutputBuffer( engOpenPointerL, 10000 ); } /** Return the ouputs of previous commands in MATLAB's workspace. * * * @param * @param * @return */ public synchronized int engOutputBuffer( long epL, int buflenI ) { // get the output buffer from MATLAB if (!enginePointerVector.contains(new Long(epL))) throw(new JMatLinkException("engine unknown")); lockEngineLock(); lockWaitForValue(); this.epL = epL; this.buflenI = buflenI; callThread( engOutputBufferI ); WaitForValue(); releaseEngineLock(); return engOutputBufferInt; } /** Return the outputs of previous commands from MATLAB's workspace. * */ public synchronized String engGetOutputBuffer( ) { return engGetOutputBuffer( engOpenPointerL ); } /** Return the ouputs of previous commands in MATLAB's workspace. * * Right now the parameter <i>buflen</i> is not supported. * @param * @return */ public synchronized String engGetOutputBuffer( long epL ) { // get the output buffer from MATLAB if (!enginePointerVector.contains(new Long(epL))) throw(new JMatLinkException("engine unknown")); lockEngineLock(); lockWaitForValue(); this.epL = epL; callThread( engGetOutputBufferI ); WaitForValue(); releaseEngineLock(); return engOutputBufferS; } /** Switch on or disable debug information printed to standard output. */ public void setDebug( boolean debugB ) { this.debugB = debugB; setDebugNATIVE( debugB ); } //////////////////////////////////////////////////////////////////////////////// // This method notifys the main thread to call MATLAB's engine // Since threads don't have methods, we set a variable which // contains the necessary information about what to do. private synchronized void callThread(int status) { this.status = status; lockThreadB = false; notifyAll(); } //////////////////////////////////////////////////////////////////////////////// // The run methods does ALL calls to the native methods // The keyword "synchronized" is neccessary to block the run() // method as long as one command needs to get executed. public synchronized void run() { //int tempRetVal; threadStatus = THREAD_RUNNING; if (debugB) System.out.println("JMatLink: thread is running"); while (true) { // System.out.println("Number of Java-Threads: "+Thread.activeCount()+""); // Thread thread = Thread.currentThread(); // System.out.println("Name of active Java-Threads: "+thread.getName()+""); // System.out.println("active Java-Thread is Daemon: "+thread.isDaemon();+""); if (debugB) System.out.println("JMatLink run status: "+status); switch (status) { case engOpenI: engOpenPointerL = engOpenNATIVE( startCmdS ); releaseWaitForValue(); break; case engOpenSingleUseI: epL = engOpenSingleUseNATIVE( startCmdS ); releaseWaitForValue(); break; case engCloseI: retValI = engCloseNATIVE( epL ); releaseWaitForValue(); break; case engSetVisibleI: retValI = engSetVisibleNATIVE( epL, engVisB ); releaseWaitForValue(); break; case engGetVisibleI: retValI = engGetVisibleNATIVE( epL ); releaseWaitForValue(); break; case engEvalStringI: retValI = engEvalStringNATIVE(epL, engEvalStringS); releaseWaitForValue(); break; case engGetScalarI: engGetScalarD = engGetScalarNATIVE(epL, arrayS ); releaseWaitForValue(); break; case engGetVariableI: engGetVariableD = engGetVariableNATIVE(epL, arrayS ); releaseWaitForValue(); break; case engPutVariableI: engPutVariableNATIVE( epL, arrayS, engPutVariable2dD ); releaseWaitForValue(); break; case engOutputBufferI: engOutputBufferInt = engOutputBufferNATIVE( epL, buflenI ); releaseWaitForValue(); break; case engGetOutputBufferI: engOutputBufferS = engGetOutputBufferNATIVE( epL ); releaseWaitForValue(); break; default: //System.out.println("thread default switch statem."); } status=0; lockThreadB = true; while (lockThreadB == true) { synchronized(this) { try { if (debugB) System.out.println("JMatLink:wait"); wait(); } // wait until next command is available catch (InterruptedException e) {} } } //System.out.println("JMatLink: thread awoke and passed lock"); if (threadStatus == THREAD_DYING) break; } // end while // thread has finally terminated threadStatus = THREAD_DEAD; if (debugB) System.out.println("JMatLink: thread terminated"); } // end run //////////////////////////////////////////////////////////////////////////////// // The MATLAB engine is served by a thread. Threads don't have methods // which can be called. So we need to send messages to that thread // by using notifyAll. In the meantime NO OTHER methods is allowed to // access our thread (engine) so we lock everything up. private void lockEngineLock(){ synchronized(this){ while (lockEngineB==true){ try { //System.out.println("lockEngineLock locked"); wait();} // wait until last command is finished catch (InterruptedException e) { } } //now lockEngineB is false lockEngineB = true; } } // end lockEngine private synchronized void releaseEngineLock(){ lockEngineB = false; notifyAll(); } //////////////////////////////////////////////////////////////////////////////// // The MATLAB engine is served by a thread. Threads don't have methods // which can be called directly. If we send a command that returns data // back to the calling function e.g. engGetArray("array"), we'll notify // the main thread to get the data from MATLAB. Since the data is collected // in another thread, we don't know exactly when the data is available, since // this is a concurrent situation. // The solution is simple: I always use a locking-mechanism to wait for the // data. The main thread will release the lock and the calling method can // return the data. // // Steps: // 1. a method that returns data calls the locking method // 2. notify the thread to call MATLAB // 3. wait for the returned data // 4. after the thread itself got the data it releases the locks method // 5. return data private synchronized void lockWaitForValue(){ lockWaitForValueB = true; } private void WaitForValue(){ synchronized(this){ while (lockWaitForValueB==true){ try { //System.out.println("lockWaitForValue locked"); wait();} // wait for return value catch (InterruptedException e) { } } } //System.out.println("WaitForValue released"); } private synchronized void releaseWaitForValue(){ lockWaitForValueB = false; notifyAll(); } } // end class JMatLink