Custom Development

Privileged Client .NET Code in a Browser - Part 3

Bob Snyder

Detailed Steps To Build the Solution Stack - Build Steps 4-6

This final installment of our odyssey of configuring a stack to invoke a privileged DLL from a web page covers the remaining elements of the stack, and a quick description of how to step through the code to observe it in action and of course, debug.

  1. Create the mixed-mode C++ DLL / code module (.netmodule). I will refer to my example as PrintModule.netmodule.
    • This .netmodule too must be signed: just set the information on the signing tab of the project properties, just as in the PrintManager project.
    • Create the mixed-mode (/clr compiler switch) DLL, referencing the PrintManager assembly in the usual fashion (add the reference via the file system, it will resolve to the GAC at runtime). I copy the SummaDemo.Client.PrintManager.dll file into the PrintModule project folder and reference it from there. Since the code is just a pass-through, there is not much to it:
      <code>namespace PrintModule
      {
           public class PrintJNIToManaged
          {
              SummaDemo.Client.PrintManager.PrintManager _printManager = null;
              public PrintJNIToManaged()
              {
                  _printManager = new SummaDemo.Client.PrintManager.PrintManager();
              }
      
              public void ShowFileQueue(string encodedData)
              {
                  if (_printManager != null)
                  {
                      _printManager.ShowFileQueue(encodedData);
                  }
              }
          }
      }</code>
    • In the post-build step, create the code module (PrintModule.netmodule) file from the mixed-mode DLL, including the functional DLL from Step 1 (here: PrintManager.dll) as a resource.
      • The syntax is (use the full path to csc.exe, or ensure that it is on your path – example location is: C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe):

csc.exe /target:module /r:$(ProjectDir)SummaDemo.Client.PrintManager.dll /out:$(TargetDir)PrintModule.netmodule $(ProjectDir)PrintJNIToManaged.cs
CallPrivCode-netmod postbuild

  • Notes about .netmodules:
    • A code module is essentially a module of C# compiled code without the full set of framework/overhead to make it an executable assembly. The extension for those C# code modules (described below), sometimes used as their name, is .netmodule.
    • Ideally, for new development, you might expect that you could implement your desired functionality here in the .netmodule. However, if you are integrating with existing code, you need this to bridge to your DLLs. In addition, it turns out to be impractical to implement in the .netmodule: there are limitations associated with the required compiler settings, and making a code module out of anything with any real complexity is a serious pain. Specifically, making a .netmodule out of a WinForms or WPF solution is at a minimum non-trivial. I’m not even sure it’s possible. Besides, maybe you have other uses for that assembly. Maybe you have multiple assemblies. You won’t want to compromise the design of your functional code so that you can make a .netmodule. Fortunately, it is very easy to make a .netmodule out of a single C# source file, which can then call multiple assemblies. Adding resources to a .netmodule is easy also. Therefore, the only reason this .netmodule must exist is as a pass-through to the public functionality of the ultimately desired functional DLL or DLLs.
    • An important aspect of Microsoft’s implementation of a .netmodule is that its use is analogous to an include file. You must deploy the .netmodule, and its code can only be used externally. No code in the same DLL / project as the .netmodule can call the .netmodule. Any code that you want to create to use the functionality of a .netmodule, must be in separate DLL from the .netmodule itself.
  1. Create the mixed-mode C++ JNI DLL. I will refer to my example as PrintJNI.dll.
    • This DLL implements the JNI method as defined in the .h file generated by javah, which should of course be included in the .cpp file. Note that the name of the method (see code below) is Java_PrintApplet_invokePrintManager(), but it is "perceived" by the Applet layer as being named simply invokePrintManager().
    • Copy the .h file generated in Step 3 above to this project folder, and include it. Your public method(s) must match the declaration(s) in this include file.
    • Reference the .netmodule created above. I copy PrintModule.netmodule into the PrintJNI project folder and reference it from there.
    • The Project’s VC++ Directories must be edited to include the Java .h and .lib folders.
      CallPrivCode-JNI Directories
    • It is mixed-mode (/clr compiler switch) so it can in turn, use the separate C# code in PrintModule.netmodule.
      CallPrivCode-JNI Compile
    • Copy the .netmodule (since it is a dependency) to the output folder for signing.
      CallPrivCode-JNI PreBuild
    • Make sure the manifest file is generated as an external file, not embedded.
      CallPrivCode-JNI Manifest
    • Set the Key File in the Linker Advance Settings. The Debug build worked with or without the previous setting and this one, but without them the release build complained that the DLL was not strongly named.
      CallPrivCode-JNI Link
    • Sign with a post-build command: sn -Ra "$(TargetPath)" "$(ProjectDir)$(ProjectName).snk"
      CallPrivCode-JNI PostBuild
    • Here is a sample implementation to illustrate the correct declarations and data marshalling.
      <code>#include "PrintApplet.h"
      #include &lt;msclr/marshal.h&gt;
      
      //Use #using, do not use the linker switch
      //     /ASSEMBLYMODULE:"PrintModule.netmodule"
      // That seems odd, but embedding a .netmodule only helps
      // the NEXT assembly that uses this assembly.
      // For this assembly,
      // use #using; the compiler needs the info first anyway.
      // Note that he .netmodule needs to be deployed then
      // with this DLL. And yes, you have to use the .netmodule,
      // not a .net assembly/dll you make from the same source.
      // If you "#using" the dll, it will build, but it won't run.
      #using "PrintModule.netmodule"
      
      using namespace System::Runtime::InteropServices;
      using namespace msclr::interop;
      using namespace std;
      
      JNIEXPORT void JNICALL Java_PrintApplet_invokePrintManager(JNIEnv *jniEnvPtr, jobject javaObj, jint type, jstring encodedData)
      {
          ofstream outputFile;
          outputFile.open("C:\\TestJNIOutput.txt"); //need a full path or location will be where browser thinks it is.
          outputFile &lt;&lt; "writing to file\n";
      
          if (type == 1)
          {
              outputFile &lt;&lt; "type with C# 1\n";
          }
      
          jboolean isCopy; const char * str = jniEnvPtr-&gt;GetStringUTFChars(encodedData, &amp;isCopy);
      
          //MessageBox(NULL, str, "String Param", MB_OK);
          outputFile &lt;&lt; "String Param: ";
          outputFile &lt;&lt; str;
          outputFile &lt;&lt; "\n";
      
          System::String^ clrString;
          try
          {
              //MessageBox(NULL, "About to Instantiate PrintJNIToManaged...", "Progress", MB_OK);
              outputFile &lt;&lt; "About to Instantiate PrintJNIToManaged...\n";
              ref class PrintModule::PrintJNIToManaged ^myPrinter = gcnew PrintModule::PrintJNIToManaged();
              //MessageBox(NULL, "Instantiated PrintJNIToManaged...", "Progress", MB_OK);
              outputFile &lt;&lt; "Instantiated PrintJNIToManaged...\n";
      
              //char* to std::string
              std::string standardStr(str);
      
              //std:string to System::String^
              clrString = gcnew System::String(standardStr.c_str());
      
              //MessageBox(NULL, "Calling ShowFileQueue()...", "Progress", MB_OK);
              outputFile &lt;&lt; "Calling ShowFileQueue()...\n"; myPrinter-&gt;ShowFileQueue(clrString);
          }
          catch (exception ex) // Catch native exceptions first.
          {
              MessageBox(NULL, ex.what(), "Exception", MB_OK);
          }
          catch (System::Exception ^e)
          {
              char *exceptionText = new char[e-&gt;ToString()-&gt;Length + 2];
              if (exceptionText != NULL)
              {
                  marshal_context ^ context = gcnew marshal_context();
      
                  strcpy(exceptionText, context-&gt;marshal_as(e-&gt;ToString()));
                  MessageBox(NULL, exceptionText, "CLR Exception", MB_OK);
                  outputFile &lt;&lt; exceptionText;
                  delete exceptionText;
                  delete context;
              }
              else
              {
                  MessageBox(NULL, "Couldn't extract exception text.", "CLR Exception", MB_OK);
                  outputFile &lt;&lt; "Couldn't extract exception text.\n";
              }
          }
          finally
          {
              outputFile.close(); jniEnvPtr-&gt;ReleaseStringUTFChars(encodedData, str);
          }
      }
      </code>
    • Aside - If you want to throw an exception up to the Applet from the JNI layer, the basic code needed is:
      jclass jc = env->FindClass("java/lang/Error");
      if (jc) env->ThrowNew(jc, e.what());
  1. Build an installer to set everything up to run. For simplicity, I used the Microsoft installer, with an installer project in the same solution as the PrintManager project.
    • Copy the PrintJNI.dll and the PrintModule.netmodule into the PrintManager project folder. These files should be included in the PrintManager solution, and be marked as Content files.
    • Build the installer project.
      • Install by right-clicking on the installer project in the Solution Explorer, and selecting Install.

To Test

To test the entire stack:

  1. Install as described above; installing the SummaDemo.Client.PrintManager.dll into the GAC, and putting the PrintJNI.DLL and the PrintModule.netmodule in a folder that you must include in your PATH.
  2. Open TestPage.html (source in the previous installment) using a Java-enabled browser, and click the button.

Debugging:

The simplest way to debug is actually to run the Applet from Eclipse. This allows you to debug the Applet itself, and the native code stack. You can do this by selecting from the Menu: Run/Debug As->/Java Applet, or you can remotely debug the Applet running in the browser using Eclipse or other Java debugger (see: http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/index.html). Applet debugging would be done directly in Eclipse. To debug the native code / CLR stack, you simply have to attach Visual Studio to the correct Java process.

To debug the native code / CLR portions of the call stack when running from your application or from the TestPage.html, again start by attaching Visual Studio to the correct Java process. To make it easy to do this, enable the Java console in the Java plugin, and the Java Console titlebar text will appear with the correct process in the window listing processes in Visual Studio. Once you have attached to the correct Java process, you will be able to set breakpoints in the JNI DLL, the .netmodule and your .NET assembly.

Debugging Note

I found that sometimes the Visual Studio debugger would not correctly initialize in the process. This appeared to happen when I attached to the Java process before the Applet loaded the JNI library. You may have to pause after the Applet loads the JNI DLL before attaching to the Java process. You can do this with a breakpoint in the Applet, or sometimes more conveniently by the crude expedient of putting an alert() in the Applet code after the System.LoadLibrary() call.

 

In Conclusion

I hope this "guide" has been helpful. I have used this approach successfully in a production web application. It has been reliable and was very easy to implement. However, it was very difficult to discover all of the settings across in the various projects required even though I had done this previously with C++ and with earlier versions of .NET and knew it would work. With any luck, this information in this series has saved you some time so you can concentrate on the important parts of your application.

Bob Snyder
ABOUT THE AUTHOR
Bob Snyder, Summa

Senior Technical Consultant