0

I'm trying to return a string from C function to a C# program and I'm getting a AccessViolationException and I have no idea why? Here's my C code:

__declspec(dllexport) void DoLuceneRequest(byte *xmlin, int datasize, char *xmlout){
    JNIEnv *env;

    if (jvm == NULL){
        JavaVMOption* options = new JavaVMOption[1];
        JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
        options[0].optionString = "-Djava.class.path=LuceneServlet.jar;lucene-core-2.9.4.jar;lucene-spellchecker-2.9.4.jar;servlet-api.jar";
        vm_args.version = JNI_VERSION_1_6;
        vm_args.nOptions = 1;
        vm_args.options = options;
        vm_args.ignoreUnrecognized = false;
        /* load and initialize a Java VM, return a JNI interface
        * pointer in env */
        JNI_CreateJavaVM(&jvm, (void**)&globalenv, &vm_args);
        delete options;
    }

    jvm->AttachCurrentThread((void **)&env, NULL);

    jobject obj;

    jclass cls = env->FindClass("lucene/LuceneServlet");
    if (cls != NULL){
        jmethodID constructor = env->GetMethodID(cls, "<init>", "()V");
        if (constructor != NULL){
            obj = env->NewObject(cls, constructor);
            if (obj != NULL){
                jmethodID processRequest = env->GetMethodID(cls, "processRequest", "([B)Ljava/lang/String;");
                if (processRequest != NULL){
                    jbyte *jbytes = (jbyte*) xmlin;                     
                    jbyteArray jba = env->NewByteArray(datasize);

                    env->SetByteArrayRegion(jba, 0, datasize, jbytes);
                    jstring results = (jstring)env->CallObjectMethod(obj, processRequest, jba);
                    MessageBox(NULL, "call successful, returning values", "", MB_OK);
                    const char *cresults = env->GetStringUTFChars(results, NULL);                       

                    strcpy_s(xmlout, strlen(cresults) + 1, cresults);
                    for (int i=0; i <= strlen(cresults); i++){
                        if (cresults[i] == '\0'){
                            MessageBox(NULL, "Has null terminator", "", MB_OK);
                        }
                    }

                    MessageBox(NULL, cresults, "", MB_OK);
                    MessageBox(NULL, xmlout, "", MB_OK);

                    env->ReleaseStringUTFChars(results, cresults);

                    //env->ReleaseByteArrayElements(jba, jbytes, 0);
                }else{
                    MessageBox(NULL, "method not found", "", MB_OK);
                }
            }
        }
    }

    //jvm->DestroyJavaVM();

    jvm->DetachCurrentThread();
}

The messagebox's show the correct string.

And here's my C# code:

[DllImport("LuceneHandler.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void DoLuceneRequest(byte[] xmlin, int datasize, StringBuilder xmlout);

StringBuilder sbRsltXml = new StringBuilder();

        //DisplayHelloFromDLL("hello", sbRsltXml);
        //MessageBox.Show(sbRsltXml.ToString());
        byte[] ubytes = Encoding.UTF8.GetBytes("<test></test>");
        DoLuceneRequest(ubytes, ubytes.Length, sbRsltXml);

        MessageBox.Show(sbRsltXml.ToString());

If I do new StringBuilder(99999) then it works, but I don't know the size of the returned data ahead of time so I don't want to do that. Any ideas on what I'm doing wrong?

  • Just look at your C code, specifically at the argument *xmlout*. *xmlout* is a pointer to an **existing** char array, which *DoLuceneRequest* fills with *strcpy*, and *strlen(cresults)* being the number of chars copied. What do you guess will happen, if the char array passed as *xmlout* is shorter than *strlen(cresults)*? Note, that *DoLuceneRequest* does not allocate *xmlout*, but it expects you to provide a char array of sufficient size. Now, either you change that function, or will need to figure out a way how to determine what *strlen(cresults)* will be whenever you call *DoLuceneRequest* –  May 13 '14 at 13:47
  • Well if I hard code a string into xmlout, like xmlout = "hello", then it works. Why would that work? Shouldn't something equivalent to cresults = "hello"; strcpy_s(xmlout, strlen(cresults) + 1, cresults); (which doesn't work). The message boxes display the same string. – user3023202 May 13 '14 at 14:08
  • I guess you need to learn the basics of C/C++ a little bit. xmlout = "hello" does not do a strcpy, it just sets the xmlout pointer (note the "\*" in "char \*xmlout") to the memory where the "hello" literal is stored. And it would not really work, no. Yes, the messagebox would show something. But *xmlout* is just an argument variable like *datasize*, for example -- setting those argument variables to other values will not be carried over to the caller when the function returns... –  May 13 '14 at 14:19
  • Maybe your confusion is that you think that *strcpy* changes *xmlout*. But it doesn't. *strcpy* just copies characters into the memory **pointed at by** *xmlout*. The pointer variable *xmlout* itself is not changed by *strcpy*. Ofcourse, if the pointer is invalid (or the allocated memory pointed at being too small) then problems will occur when calling *strcpy*. On the other hand, `xmlout = "hello"` **does** change the pointer variable *xmlout*. –  May 13 '14 at 14:24
  • Thanks. I get it now. Just not sure how to get it to work. – user3023202 May 13 '14 at 14:31
  • Well, conservatively speaking, i would look into two options: (A) allocate the char buffer in the function (holding a **zero-terminated string**), and return the pointer to the allocated char buffer. Disadvantage is that the caller needs to take care of any necessary deallocation of the buffer (but which would be rather easy in C# interop). Or, (B) use something like BSTR ([see here, for example](http://stackoverflow.com/questions/5298268/returning-a-string-from-pinvoke?answertab=active#tab-top)). –  May 13 '14 at 14:52
  • Got it to work! I had to use CoTaskMemAlloc on the returned variable, malloc kept crashing. Thanks for pointing me in the right direction. – user3023202 May 13 '14 at 18:18

1 Answers1

0

Got it to work, here's the code:

__declspec(dllexport) char* DoLuceneRequest(byte *xmlin, int datasize){
    JNIEnv *env;
    char* returnedString = NULL;

    if (jvm == NULL){
        JavaVMOption* options = new JavaVMOption[1];
        JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
        options[0].optionString = "-Djava.class.path=LuceneServlet.jar;lucene-core-2.9.4.jar;lucene-spellchecker-2.9.4.jar;servlet-api.jar";
        vm_args.version = JNI_VERSION_1_6;
        vm_args.nOptions = 1;
        vm_args.options = options;
        vm_args.ignoreUnrecognized = false;
        /* load and initialize a Java VM, return a JNI interface
        * pointer in env */
        JNI_CreateJavaVM(&jvm, (void**)&globalenv, &vm_args);
        delete options;
    }

    jvm->AttachCurrentThread((void **)&env, NULL);

    jobject obj;

    jclass cls = env->FindClass("lucene/LuceneServlet");
    if (cls != NULL){
        jmethodID constructor = env->GetMethodID(cls, "<init>", "()V");
        if (constructor != NULL){
            obj = env->NewObject(cls, constructor);
            if (obj != NULL){
                jmethodID processRequest = env->GetMethodID(cls, "processRequest", "([B)Ljava/lang/String;");
                if (processRequest != NULL){
                    jbyte *jbytes = (jbyte*) xmlin;                     
                    jbyteArray jba = env->NewByteArray(datasize);

                    env->SetByteArrayRegion(jba, 0, datasize, jbytes);
                    jstring results = (jstring)env->CallObjectMethod(obj, processRequest, jba);
                    const char *cresults = env->GetStringUTFChars(results, NULL);   

                    returnedString = (char*)CoTaskMemAlloc(strlen(cresults) + 1);
                    strcpy_s(returnedString, strlen(cresults) + 1, cresults);

                    env->ReleaseStringUTFChars(results, cresults);
                    //env->ReleaseByteArrayElements(jba, jbytes, 0);
                }else{
                    MessageBox(NULL, "method not found", "", MB_OK);
                }
            }
        }
    }

    //jvm->DestroyJavaVM();

    jvm->DetachCurrentThread();
    return returnedString;
}