juerg
juerg

Reputation: 381

How to assign a string literal

I'm a Java Programmer; I last wrote C or C++ 20 years ago. Now I've returned, I'm trying to use a more modern C++ such as C++11/C++14 and to avoid old c-style programming.

How do I assign a string, as in Java:

private static final String MY_STRING = "Hello World"; // could also be public

Now I have the reference MY_STRING to the text "Hello World", which I can use as many times in my program as I want, and if I wish to change it to say "Hello Dolly", I do the change exactly in one single place.

I would like to achieve the same in C++. Example:

JavaVMOption* options = new JavaVMOption[1];

options[0].optionString = (char *)"-Djava.class.path=.";

This works, no more questions, but I don't like it because of the requirement to be able to change it in one place only if required. So, I declare (even in the header file):

std::string classpath = "-Djava.class.path=.";
// or
const std::string classpath = "-Djava.class.path=.";

and I use it as:

options[0].optionString = (char *)classpath.c_str();

Now, using the c_str() function, this works also, but I'm back to plain old C!

I would really like to know if there is a mean to stay at a more modern C++ level?

Yes I know,

JavaVMOption* options = new JavaVMOption[1]; 

is declared as it is and I can't change it. But even in C++ it's a good idea to work with a reference rather than with a pointer in the given case, as in Java. Is there a solution? I was not able to find one.

Upvotes: 2

Views: 1339

Answers (2)

Amber
Amber

Reputation: 2463

I'm a java programmer and I found the same example when trying to use JNI to call Java from C++. That said, I don't know if this is "correct", but it got the code to compile without errors by changing the original code:

JavaVMOption* options = new JavaVMOption[1];   // JVM invocation options
options[0].optionString = "-Djava.class.path=."

To use a char array instead.

JavaVMOption* options = new JavaVMOption[1];
char classpath[] = "-Djava.class.path=.";
options[0].optionString = classpath;

My basic understanding is that a char* is the same as a char[].

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275906

"Hello" is a const buffer of 6 char of value H e l l o \0 respectively whose lifetime is the entire program.

std::string bob = "Hello";

copies that buffer into a std::string object. std::string is a value semantics object; this is something that Java tends to lack, as all objects are implicitly passed by reference everywhere.

options[0].optionString = (char *)"-Djava.class.path=.";

this is extremely dangerous. You cast that buffer to a non-const pointer to char, then assigned it to optionString.

I have no idea what optionString is, but if it is a variable of type char*, this is opening you up to undefined behavior. Any editing of the buffer "-Djava.class.path=." is undefined behavior, and storing a non-const pointer to such a buffer is just begging for that to happen.

In short, the type of optionString is key to how dangerous that is. Is it merely reckless, or actually really stupid?

JavaVMOption* options = new JavaVMOption[1];

this creates on the heap an array of JavaVMOption of size 1 then stores a pointer to the first element in options.

There are so many pointless things here. And destroying that requires destroying it as an array?

JavaVMOption options;

this creates a JavaVMOption on the stack called options. It is automatically destroyed at the end of scope. This seems much more practical. There is no point in using new unless you need new, and you rarely need new in C++.

I found some sample code:

#include <jni.h>       /* where everything is defined */
...
JavaVM *jvm;       /* denotes a Java VM */
JNIEnv *env;       /* pointer to native method interface */
JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
JavaVMOption* options = new JavaVMOption[1];
options[0].optionString = "-Djava.class.path=/usr/lib/java";
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**)&env, &vm_args);
delete options;
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("Main");
jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
env->CallStaticVoidMethod(cls, mid, 100);
/* We are done. */
jvm->DestroyJavaVM();

and that wasn't written by someone who programs in C++ for a living.

Here is my initial stab at it:

struct destroy_java_vm {
  void operator()(JavaVM* jvm)const{
    jvm->DestroyJavaVM();
  }
};
using up_java_vm = std::unique_ptr< JavaVM, destroy_java_vm >;

struct java_vm {
  up_java_vm vm;
  JNIEnv* env = 0;
};
struct java_vm_option {
  std::string string;
  std::shared_ptr<void> extra_info;
};
using java_vm_options = std::vector<java_vm_option>;

struct java_vm_init {
  unsigned version = JNI_VERSION_1_2;
  java_vm_options options;
  bool ignore_unrecognized = false;
  java_vm init() {
    std::vector<JavaVMOption> java_options(options.size());
    for (std::size_t i = 0; i < options.size(); ++i) {
      java_options[i].optionString = &options.string[0];
      java_options[i].extraInfo = options.extra_info.get();
    }
    JavaVMInitArgs args;
    args.version = version;
    args.options = java_options.data();
    args.nOptions = java_options.size();
    args.ignoreUnrecognized = ignore_unrecognized?TRUE:FALSE;
    java_vm retval;
    JavaVM* tmp = 0;
    auto res = JNI_CreateJavaVM(&tmp, (void **)&retval.env, &args);
    if (res < 0) {
      return {}; // error message?  How?
    }
    retval.vm.reset( tmp );
    return retval;
  }
}

Use:

java_vm_init init;
init.options.push_back("-Djava.class.path=.");
auto vm = init.init();
if (!vm.env) { return /* error case */; }
/* invoke the Main.test method using the JNI */
jclass cls = vm.env->FindClass("Main");
jmethodID mid = vm.env->GetStaticMethodID(cls, "test", "(I)V");
vm.env->CallStaticVoidMethod(cls, mid, 100);
/* We are done. */

By my use of unique_ptr, a java_vm object is a value type that is move-only. When it is destroyed, the java vm is destroyed automatically (if you didn't move it out).

We have to move over to C-style code when talking directly to the API, because many cross-language APIs are written using C-style interfaces. We wrap that C-style interface in a safer and easier to use C++ type.

Code not tested, probably contains typos.

Upvotes: 2

Related Questions