Reputation: 14863
Then following code has an unexpected behavior: the File argument becomes, at runtime, a DirectoryListingPerfTest.
I suppose the java heap is corrupted and my native code doesn't call it with the correct arguments.
Here is the Java Code:
public class DirectoryListingPerfTest implements FileFilter {
private final SortedSet<File> _dirs = new TreeSet<>();
@SuppressWarnings("cast")
@Override
public boolean accept( File pathname ) {
if( pathname instanceof File ) {
_dirs.add( pathname );
}
else {
System.err.println( pathname.getClass());
}
return false;
}
static native void find( String path, FileFilter listener );
public static void main( String[] args ) {
System.loadLibrary( "fs_DirectoryListingPerfTest" );
final DirectoryListingPerfTest ff = new DirectoryListingPerfTest();
long atStart, elapsed;
atStart = System.nanoTime();
find( "/usr", ff );
elapsed = System.nanoTime() - atStart;
System.err.printf( "native(1): %,9.2f ms\n", elapsed / 1.0E+06 );
atStart = System.nanoTime();
find( "/usr", ff );
elapsed = System.nanoTime() - atStart;
System.err.printf( "native(2): %,9.2f ms\n", elapsed / 1.0E+06 );
}
}
Here is the C++ JNI code:
static jclass File;
static jmethodID FileCtor;
static void Java_fs_DirectoryListingPerfTest_find_r(
JNIEnv * env,
const char * path,
jobject listener,
jmethodID accept )
{
jstring jPath = env->NewStringUTF( path );
jobject file = env->NewObject( File, FileCtor, jPath );
jobject gFile = env->NewGlobalRef( file );
env->CallBooleanMethod( listener, accept, gFile );
DIR * dir = opendir( path );
if( dir ) {
struct dirent * entry;
while(( entry = readdir( dir )) != NULL ) {
if( ( 0 == strcmp( entry->d_name, "." ))
||( 0 == strcmp( entry->d_name, ".." )))
{
continue;
}
if( entry->d_type == DT_DIR ) {
char * buffer = (char *)malloc(
strlen( path ) + 1 + strlen( entry->d_name ) + 1 );
strcpy( buffer, path );
strcat( buffer, "/" );
strcat( buffer, entry->d_name );
Java_fs_DirectoryListingPerfTest_find_r(
env, buffer, listener, accept );
free( buffer );
}
}
closedir( dir );
}
}
JNIEXPORT void JNICALL Java_fs_DirectoryListingPerfTest_find(
JNIEnv * env,
jclass clazz,
jstring jpath,
jobject listener )
{
if( File == NULL ) {
File = env->FindClass( "java/io/File" );
FileCtor = env->GetMethodID( File, "<init>", "(Ljava/lang/String;)V");
}
jclass FileFilter = env->GetObjectClass( listener );
jmethodID accept =
env->GetMethodID( FileFilter, "accept", "(Ljava/io/File;)Z");
const char * path = env->GetStringUTFChars( jpath, JNI_FALSE );
Java_fs_DirectoryListingPerfTest_find_r( env, path, listener, accept );
env->ReleaseStringUTFChars( jpath, path );
}
Here is the execution trace:
native(1): 584,76 ms
class fs.DirectoryListingPerfTest
class fs.DirectoryListingPerfTest
... (a lot !)
class fs.DirectoryListingPerfTest
Exception in thread "main" java.lang.ClassCastException: fs.DirectoryListingPerfTest cannot be cast to java.lang.Comparable
at java.util.TreeMap.put(TreeMap.java:565)
at java.util.TreeSet.add(TreeSet.java:255)
at fs.DirectoryListingPerfTest.accept(DirectoryListingPerfTest.java:16)
at fs.DirectoryListingPerfTest.find(Native Method)
at fs.DirectoryListingPerfTest.main(DirectoryListingPerfTest.java:91)
Upvotes: 1
Views: 422
Reputation: 14863
Thanks Andrew, from your advices, I have made ant tested a corrected C++ version, hope this helps:
namespace fs {
class DirectoryListingPerfTest {
private:
JNIEnv * _env;
jclass _File;
jmethodID _FileCtor;
jobject _listener;
jmethodID _accept;
public:
DirectoryListingPerfTest(
JNIEnv * env,
jclass File,
jmethodID FileCtor,
jobject listener,
jmethodID accept )
:
_env ( env ),
_File ( File ),
_FileCtor( FileCtor ),
_listener( listener ),
_accept ( accept )
{}
void find( const char * path ) {
jstring jPath = _env->NewStringUTF( path );
jobject file = _env->NewObject( _File, _FileCtor, jPath );
jobject gFile = _env->NewGlobalRef( file );
_env->CallBooleanMethod( _listener, _accept, gFile );
DIR * dir = opendir( path );
if( dir ) {
struct dirent * entry;
while(( entry = readdir( dir )) != NULL ) {
if( ( 0 == strcmp( entry->d_name, "." ))
||( 0 == strcmp( entry->d_name, ".." )))
{
continue;
}
if( entry->d_type == DT_DIR ) {
char * buffer = (char *)malloc(
strlen( path ) + 1 + strlen( entry->d_name ) + 1 );
strcpy( buffer, path );
strcat( buffer, "/" );
strcat( buffer, entry->d_name );
find( buffer );
free( buffer );
}
}
closedir( dir );
}
}
private:
DirectoryListingPerfTest( const DirectoryListingPerfTest & );
DirectoryListingPerfTest & operator = ( const DirectoryListingPerfTest & );
};
}
JNIEXPORT void JNICALL Java_fs_DirectoryListingPerfTest_find(
JNIEnv * env,
jclass clazz,
jstring jpath,
jobject listener )
{
jclass File = env->FindClass( "java/io/File" );
jmethodID FileCtor = env->GetMethodID( File, "<init>", "(Ljava/lang/String;)V");
jclass FileFilter = env->GetObjectClass( listener );
jmethodID accept = env->GetMethodID( FileFilter, "accept", "(Ljava/io/File;)Z");
const char * path = env->GetStringUTFChars( jpath, JNI_FALSE );
fs::DirectoryListingPerfTest( env, File, FileCtor, listener, accept ).find( path );
env->ReleaseStringUTFChars( jpath, path );
}
Upvotes: 0
Reputation: 1
You can not cache Java objects between calls. This code is wrong:
static jclass File;
static jmethodID FileCtor;
...
if( File == NULL ) {
File = env->FindClass( "java/io/File" );
FileCtor = env->GetMethodID( File, "<init>", "(Ljava/lang/String;)V");
}
If you want to cache Java values, in general you need to create a global reference. See:
Upvotes: 2