diff --git a/Java.Interop.sln b/Java.Interop.sln
index b4366ee6d..2abb51d62 100644
--- a/Java.Interop.sln
+++ b/Java.Interop.sln
@@ -59,7 +59,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "generator-Tests", "tests\ge
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{D5A93398-AEB1-49F3-89DC-3904A47DB0C7}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hello", "samples\Hello\Hello.csproj", "{F3ECB73D-9263-4E42-A5B4-3FC0D1D829F9}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hello-Java.Base", "samples\Hello-Java.Base\Hello-Java.Base.csproj", "{F3ECB73D-9263-4E42-A5B4-3FC0D1D829F9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hello-Core", "samples\Hello-Core\Hello-Core.csproj", "{0E6DE9F9-35B1-4DFB-BB8B-7E4A2D362461}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build-Tools", "Build-Tools", "{172B608B-E6F3-41CC-9949-203A76BA247C}"
EndProject
@@ -214,6 +216,10 @@ Global
{F3ECB73D-9263-4E42-A5B4-3FC0D1D829F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3ECB73D-9263-4E42-A5B4-3FC0D1D829F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3ECB73D-9263-4E42-A5B4-3FC0D1D829F9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0E6DE9F9-35B1-4DFB-BB8B-7E4A2D362461}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0E6DE9F9-35B1-4DFB-BB8B-7E4A2D362461}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E6DE9F9-35B1-4DFB-BB8B-7E4A2D362461}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0E6DE9F9-35B1-4DFB-BB8B-7E4A2D362461}.Release|Any CPU.Build.0 = Release|Any CPU
{6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -331,6 +337,7 @@ Global
{891F2E04-5614-4A26-A78F-3778025ECF43} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
{4EEAB1A7-99C1-4302-9C18-01A7B481409B} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
{F3ECB73D-9263-4E42-A5B4-3FC0D1D829F9} = {D5A93398-AEB1-49F3-89DC-3904A47DB0C7}
+ {0E6DE9F9-35B1-4DFB-BB8B-7E4A2D362461} = {D5A93398-AEB1-49F3-89DC-3904A47DB0C7}
{6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A} = {172B608B-E6F3-41CC-9949-203A76BA247C}
{D18FCF91-8876-48A0-A693-2DC1E7D3D80A} = {0998E45F-8BCE-4791-A944-962CD54E2D80}
{D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD} = {0998E45F-8BCE-4791-A944-962CD54E2D80}
diff --git a/samples/Hello-Core/Hello-Core.csproj b/samples/Hello-Core/Hello-Core.csproj
new file mode 100644
index 000000000..8ea4c642d
--- /dev/null
+++ b/samples/Hello-Core/Hello-Core.csproj
@@ -0,0 +1,21 @@
+
+
+
+ $(DotNetTargetFramework)
+ Exe
+ Hello
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Hello-Core/Program.cs b/samples/Hello-Core/Program.cs
new file mode 100644
index 000000000..157aa80b2
--- /dev/null
+++ b/samples/Hello-Core/Program.cs
@@ -0,0 +1,87 @@
+using Java.Interop;
+
+using Mono.Options;
+
+bool showHelp = false;
+
+var jreOptions = new JreRuntimeOptions {
+};
+
+var options = new OptionSet {
+ "Using the JVM from C#!",
+ "",
+ "Options:",
+ { "jvm=",
+ $"{{PATH}} to JVM to use.",
+ v => jreOptions.JvmLibraryPath = v },
+ { "cp=|classpath",
+ $"Add {{JAR-OR-DIRECTORY}} to JVM classpath.",
+ v => jreOptions.ClassPath.Add (v)},
+ { "J=",
+ $"Pass the specified option to the JVM.",
+ v => jreOptions.AddOption (v) },
+ { "h|help",
+ "Show this message and exit.",
+ v => showHelp = v != null },
+};
+options.Parse (args);
+
+if (showHelp) {
+ options.WriteOptionDescriptions (Console.Out);
+ return;
+}
+
+if (string.IsNullOrEmpty (jreOptions.JvmLibraryPath) || !File.Exists (jreOptions.JvmLibraryPath)) {
+ Error ("Option -jvm=PATH is required. PATH is a full path to the JVM native library to use, e.g. `libjli.dylib`.");
+ return;
+}
+
+var jre = jreOptions.CreateJreVM ();
+
+// We now have a JVM!
+// The current thread is implicitly attached to the JVM.
+// Access of `JniEnvironment` members on other threads will implicitly attach those threads to the JVM.
+
+//
+// Useful background info: the JNI documentation! https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html
+//
+
+var Object_class = JniEnvironment.Types.FindClass ("java/lang/Object");
+Console.WriteLine ($"Object_class={Object_class}");
+var Object_ctor = JniEnvironment.InstanceMethods.GetMethodID (Object_class, "", "()V");
+var Object_val = JniEnvironment.Object.NewObject (Object_class, Object_ctor);
+
+Console.WriteLine ($"Object_val={Object_val}");
+
+// Invoke `Object.toString()`
+var Object_toString = JniEnvironment.InstanceMethods.GetMethodID (Object_class, "toString", "()Ljava/lang/String;");
+unsafe {
+ var Object_desc = JniEnvironment.InstanceMethods.CallObjectMethod (Object_val, Object_toString, null);
+ Console.WriteLine ($"Object_val.toString()={JniEnvironment.Strings.ToString (Object_desc)}");
+
+ // When JNI returns a `jobject` or `jclass` value, JNI returns a *JNI Object Reference*.
+ // The `JniObjectReference` struct is used to store JNI Local, Global, and Weak Global references.
+ //
+ // When an object reference is no longer required, it should be explicitly deleted.
+
+ JniObjectReference.Dispose (ref Object_desc);
+}
+
+JniObjectReference.Dispose (ref Object_class);
+JniObjectReference.Dispose (ref Object_val);
+
+// There are some OO wrappers over the core `JniEnvironment` members. `JniType` is useful.
+var Object_type = new JniType ("java/lang/Object");
+var Object_ctor2 = Object_type.GetConstructor ("()V");
+
+unsafe {
+ var Object_val2 = Object_type.NewObject (Object_ctor2, null);
+ var Object_desc = JniEnvironment.InstanceMethods.CallObjectMethod (Object_val2, Object_toString, null);
+ Console.WriteLine ($"Object_val.toString()={JniEnvironment.Strings.ToString (Object_desc)}");
+}
+
+void Error (string message)
+{
+ var app = Path.GetFileNameWithoutExtension (Environment.GetCommandLineArgs ()[0]);
+ Console.Error.WriteLine ($"{app}: {message}");
+}
diff --git a/samples/Hello-Core/README.md b/samples/Hello-Core/README.md
new file mode 100644
index 000000000..30893d6b8
--- /dev/null
+++ b/samples/Hello-Core/README.md
@@ -0,0 +1,35 @@
+# Hello-Core
+
+Use as little of `Java.Interop.dll` as possible. No object mapping.
+
+Usage:
+
+```
+Options:
+ --jvm=PATH PATH to JVM to use.
+ --cp, --classpath=JAR-OR-DIRECTORY
+ Add JAR-OR-DIRECTORY to JVM classpath.
+ -J=VALUE Pass the specified option to the JVM.
+ -h, --help Show this message and exit.
+```
+
+`-J` can be used to add runtime options to the JVM instance, e.g.
+
+```shell
+# Enable verbose JNI logging from the JVM
+% dotnet run -- --jvm /Library/Java/JavaVirtualMachines/microsoft-11.jdk/Contents/Home/lib/jli/libjli.dylib -J-verbose:jni
+[Dynamic-linking native method java.lang.Object.registerNatives ... JNI]
+[Registering JNI native method java.lang.Object.hashCode]
+[Registering JNI native method java.lang.Object.wait]
+…
+```
+
+The sample will create a `java.lang.Object` instance and invoke `Object.toString()` on it.
+
+```
+% dotnet run -- --jvm /Library/Java/JavaVirtualMachines/microsoft-11.jdk/Contents/Home/lib/jli/libjli.dylib
+Object_class=0x7ff04f105b98/L
+Object_val=0x7ff04f105ba8/L
+Object_val.toString()=java.lang.Object@5cbc508c
+Object_val.toString()=java.lang.Object@3419866c
+```
diff --git a/samples/Hello/Hello.csproj b/samples/Hello-Java.Base/Hello-Java.Base.csproj
similarity index 100%
rename from samples/Hello/Hello.csproj
rename to samples/Hello-Java.Base/Hello-Java.Base.csproj
diff --git a/samples/Hello/Program.cs b/samples/Hello-Java.Base/Program.cs
similarity index 100%
rename from samples/Hello/Program.cs
rename to samples/Hello-Java.Base/Program.cs