1

I'm using C++/CLI to connect a Java client to my C# ServiceHost. So far I have used this to access my service, where Client defines my ServiceHost client:

JNIEXPORT jstring JNICALL Java_Client_GetData(JNIEnv *env, jobject, jstring xml)
{
    try
    {
        Client ^client = gcnew Client();

        return env->NewStringUTF(marshal_as<string>(
              client->GetData(marshal_as<String^>(env->GetStringUTFChars(xml, 0))
        )).c_str());
    }
    catch(Exception^ ex)
    {
        Console::WriteLine(ex->ToString());
    }
    return NULL;
}


That works fine, but I want to be able to store my Client object on the Java side in order to make calls with the same object instead of opening and closing the connection for each call.

It has been difficult to find anything definitive about this. Is it possible?



Here is the solution I came up with per the marked answer:

JNIEXPORT jlong JNICALL Java_Client_Create(JNIEnv* env, jobject obj)
{
    try
    {
        Client^ client = gcnew Client();
        client->Connect();
        long result =  reinterpret_cast<long>(GCHandle::ToIntPtr(GCHandle::Alloc(client)).ToPointer());
        return result;
    }
    catch(Exception^ ex)
    {
        Console::WriteLine(ex->ToString());
    }
    return NULL;
}

By storing that long in Java, I can pass it as a jlong parameter to my GetData method:

JNIEXPORT jstring JNICALL Java_Client_GetData(JNIEnv *env, jobject, jlong ptr, jstring xml)
{
    try
    {
        GCHandle h = GCHandle::FromIntPtr(IntPtr(reinterpret_cast<void*>(ptr)));
        Client^ client = safe_cast<Client^>(h.Target);

        const char* xmlChars = (const char*)env->GetStringChars(xml, 0);
        string xmlString(xmlChars);
        env->ReleaseStringChars(xml, (const jchar*)xmlChars);

        const char* data = marshal_as<string>(client->GetData(
                marshal_as<String^>(xmlString)
            )).c_str();

        int length = strlen(data);

        return env->NewString((const jchar*)data, length);
    }
    catch(EndpointNotFoundException^)
    {
        return NULL;
    }
    catch(Exception^ ex)
    {
        Console::WriteLine(ex->ToString());
    }
    return NULL;
}

All I have left to do is create another JNI method to close the Client connection and dispose the object.

6
  • 1
    If your data is not characters then you shouldn't use strings to transfer it. If it is then you should not convert it from UTF-16LE to something else (esp. non-Unicode or non-standard Unicode [JNI's *UTF functions]) and back to UTF-16LE. Both .NET and Java on Windows use UTF-16LE. Use JNI's GetStringChars and NewString, and std::wstring if needed as an intermediary (C++/CLI doesn't support std::u16string, which in VC++ is effectively the same thing). Commented Nov 15, 2013 at 23:46
  • @TomBlodget I appreciate your feedback. I edited your suggestion into my solution above, though I'm not sure if what I did is the best way to go about it. Please let me know if you see something else I should change. However, the XML I'm passing through this function is in UTF-8, so for the sake of knowledge- does your suggestion still apply in this case? Commented Dec 2, 2013 at 20:01
  • 1
    Both .NET and JVM store strings in counted sequences of UTF-16 codeunits and with the same endianness. Use GetStringLength to get the number of codeunits. GetStringChars gives you a pointer to the codeunits. Then call Marshal::PtrToStringUni with the pointer cast to an IntPtr and the number of codeunits to get a .NET string. Commented Dec 3, 2013 at 5:25
  • If your XML is in a JVM or .NET string, its encoding is UTF-16. If the xml declaration says otherwise, it's just wrong. If you store it an array of bytes, then it can have whatever encoding you desire, but the xml declaration should say what that is. XmlReader/XmlWriter and similar classes do this for you. Commented Dec 3, 2013 at 5:27
  • The XML is declared as UTF-8 and must be passed as a string, unfortunately that part is out of my hands. Is there an advantage to using Marshal::PtrToStringUni over casting between char/jchar? That does seem cleaner, I'm just trying to get a better grasp of this. Thanks Commented Dec 3, 2013 at 16:28

1 Answer 1

1

Change your native method to return a jlong, and return the address of the newly created Client object pointer.

Create a second native method for cleaning up memory, which accepts the store jlong from the previous call, cast it back to a Client pointer, and delete it.

See This answer for the correct syntax on handle to pointer conversion

Sign up to request clarification or add additional context in comments.

1 Comment

You are correct sir. Thank you! I will edit my solution into my post.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.