SharpKit Part 1: Building a SharpKit Plugin

It’s been quite a while since I’ve posted something so I thought I would take some time to do a series on working with SharpKit and developing a plug-in. I’ve been working with SharpKit while developing a plug-in for the Randori Framework and RandoriGuiceJs project the last few weeks here at Digital Primates, that had a requirement to do quite a bit of manipulation of the C# during compile time. The generated JavaScript had to automatically contain a variety of new properties, methods, etc. based on the interpretation of the C# class. It’s been a fantastic journey to say the least and I’ve learned a whole lot about compiler land and abstract syntax trees. Needless to say the projects are really exciting and the plugin worked great. Look forward to hearing more about the Randori Framework in the upcoming weeks. If you are unfamiliar with it, check out Mike Labriola’s presentation from 360 Stack: Randori Design Goals and Justifications.

SharpKit Background:

Simply put, SharpKit is a C# to JavaScript cross compiler. It’s produces simple, clean JavaScript. After working with it for a while now, I’m really starting to enjoy SharpKit. Why? There are numerous advantages for using C# to build JavaScript but the biggest one is working in a strong typed language to generate the weakly typed JavaScript. Then there is also a personal reason, that I loath JavaScript almost more than ActionScript 1. Moving on!

NRefactory:

One thing I will point out is that SharpKit is built on top of the NRefactory project located on GitHub. It is important to point that out because building a plugin will require you to work with some of the NRefactory classes if you need to interpret the compiled C# code. While I’m not going to take the time to explain NRefactory, here’s a great article that explains what is going on and this handy diagram. Also, before you think “Hey check this C# system out, I can manipulate the C# and make it do sweet stuff at compile time…” No. No you can’t. NRefactory is a immutable system, meaning you can’t change it. But that’s ok since you shouldn’t have to.  We’ve been working with SharpKit to iron out all sorts of kinks, now we can manipulate the JSAst A-OK.

SharpKit Plugins:

Since working with the developers of SharpKit to get support for building plug-ins competed and all of the kinks worked out, things are starting to look real good.  When you peel away the complexity, the SharpKit compiler is simply dispatching events throughout the compile time process notifying the loaded plug-ins about the current phases the compiler is in during the compilation of a C# project.  For example, BeforeSaveJSFiles is a event sent right before the JS files are written to the C# project output directory. At the time of this writing, the following events are available to be programmed against:

#region Assembly skc5.exe, v4.0.30319
// C:WindowsMicrosoft.NETFrameworkv4.0.30319SharpKit5skc5.exe
#endregion
event Action AfterApplyExternalMetadata;
event Action AfterConvertCsToJs;
event Action<IEntity, JsNode> AfterConvertCsToJsEntity;
event Action AfterEmbedResources;
event Action AfterInjectJsCode;
event Action AfterMergeJsFiles;
event Action AfterOptimizeJsFiles;
event Action AfterParseCs;
event Action AfterSaveJsFiles;
event Action AfterSaveNewManifest;
event Action BeforeApplyExternalMetadata;
event Action BeforeConvertCsToJs;
event Action<IEntity> BeforeConvertCsToJsEntity;
event Action BeforeEmbedResources;
event Action BeforeExit;
event Action BeforeInjectJsCode;
event Action BeforeMergeJsFiles;
event Action BeforeOptimizeJsFiles;
event Action BeforeParseCs;
event Action BeforeSaveJsFiles;
event Action BeforeSaveNewManifest;

What these events actually provide are great moments in time to manipulate what the compiler is doing in order to alter the generated JavaScript. It’s pretty easy to get a plugin created and running.

** Note: The following examples are using Visual Studio 2010, although there is talk of having SharpKit work on Mac’s, I personally haven’t looked into it.

Creating a SharpKit Plugin:

A SharpKit plugin is simply a compiled assembly (dll). Conveniently we can build this in C#.

In Visual Studio:

  • Create a new Class Library project. File > New > Visual C# > Class Library
  • I titled mine: “HelloWorldPlugin”
  • Rename your initial class to “ExamplePlugin.cs” or whatever you would like to call your plugin.

Next thing we need to do is make sure our ExamplePlugin class implements the ICompilerPlugin interface provided by SharpKit. To access that, you need to add a reference to the skc5.exe into your project. When SharpKit is installed on your computer, it installs a directory into the .Net framework directories in Windows. Add this skc5.exe as a reference. The path to this will vary from system to system, on my Windows 7 machine it’s located here: C:WindowsMicrosoft.NETFrameworkv4.0.30319SharpKit5skc5.exe

You always could copy the skc5.exe project into your visual studio project and reference it that way, but remember that you’ll have to manually maintain that file every time you upgrade SharpKit. I prefer not to do that which is why I reference it directly from where it’s installed and run from.

  • Add Reference skc5.exe
  • Implement the ICompiler interface on the plugin Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpKit.Compiler;
namespace HelloWorldPlugin
{
public class ExamplePlugin : ICompilerPlugin    
{
public ICompiler Compiler
{
get;
set;
}
public void Init(ICompiler compiler)
{
Compiler = compiler;
}
}
}
  • Add a function watching for SharpKit compiler event
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpKit.Compiler;
namespace HelloWorldPlugin
{
public class ExamplePlugin : ICompilerPlugin    
{
public ICompiler Compiler
{
get;
set;
}
public void Init(ICompiler compiler)
{
Compiler = compiler;
Compiler.AfterParseCs += new Action(Compiler_AfterParseCs);
}
void Compiler_AfterParseCs()
{
Console.WriteLine("ExamplePlugin.AfterParseCs >> Hello World!");
}
}
}

Build the project and make sure there are no build errors. If there are no problems you are all set. You have just created your first plug-in! Next let’s figure out how to use it.

Installing a SharpKit Plugin:

Much like referencing the skc5.exe in the plug-in project, I prefer to install my new SharpKit plug-in into the SharpKit installation directory. I’m sure there are many more people that are smarter about this than I am, but it works for me and makes sense. I’ve developed a plug-in for SharpKit and need to install that plug-in into where SharpKit is run from.

  • Copy your HelloWorldPlugin.dll into the directory on your computer where SharpKit is installed.
    • C:WindowsMicrosoft.NETFrameworkv4.0.30319SharpKit5
  • Tip: You can change your plugin output path to compile the dll directly into this directory by going to the project properties, Build tab and configuring the “Output path:” to point to this directory.

Compiling a Visual Studio Project to use a SharpKit Plugin:

Now that you have built a plug-in, are setup to watch for when AfterParseCs occurs, built the dll and installed it into SharpKit it’s time to put it to use. For right now, I am just going to show how to have another project build to include having the plug-in being invoked.  I’ll cover debugging your plug-in in the next section.

In order to have your plug-in get called at compile time, you’ll need to modify the project .csproj for the project that you are going to compile from C# into JavaScript. Make sure that you reference the plug-in by the fully qualified type name, and include the following XML. In this example when compiling a new Class Library project, I would add a ItemGroup that would look like this:

<ItemGroup>
  <SkcPlugin Include="HelloWorldPlugin.ExamplePlugin, HelloWorldPlugin"><InProject>false</InProject></SkcPlugin>
</ItemGroup>

Once that is done:

  • Reload the project you will be compiling into JavaScript
  • Open up your output window and build the project
  • You should see the your plug-in loaded and the Console.WriteLine call executed in the stack:
Compile complete -- 0 errors, 2 warnings
Parallel=False  
C:WindowsMicrosoft.NETFrameworkv4.0.30319SharpKit5skc5.exe 
/dir:"C:...Documents_projectsSharpKitPluginExampleSharpKitPluginExampleClassLibraryProj" 
/define:DEBUG;TRACE /reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0Microsoft.CSharp.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0mscorlib.dll" 
/reference:C:...Documents_projectsSharpKitPluginExampleSharpKitPluginExampleClassLibraryProjbinSharpKit.JavaScript.dll 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.Core.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.Data.DataSetExtensions.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.Data.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.Xml.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.Xml.Linq.dll" 
/out:objDebugClassLibraryProj.dll 
/target:library Project.cs User.cs PropertiesAssemblyInfo.cs "C:...AppDataLocalTemp.NETFramework,Version=v4.0.AssemblyAttributes.cs" 
/plugin:"HelloWorldPlugin.ExamplePlugin, HelloWorldPlugin" /rebuild /TargetFrameworkVersion:v4.0  
18:20:38.987: ParseArgs: Start:   
18:20:38.995: ParseArgs: End: 8ms  
18:20:38.995: CalculateMissingArgs: Start:   
18:20:38.996: CalculateMissingArgs: End: 1ms  
18:20:38.997: CheckManifest: Start:   
18:20:38.998: CheckManifest: End: 1ms  
18:20:38.998: VerifyNativeImage: Start:   
18:20:38.998: VerifyNativeImage: End: 0ms  
18:20:38.998: CheckActivation: Start:   SharpKit 5 Professional Edition v4.29.9000  
18:20:39.012: CheckActivation: End: 14ms  
18:20:39.012: LoadPlugins: Start:   
Loading plugin: HelloWorldPlugin.ExamplePlugin, HelloWorldPlugin  
Found plugin: HelloWorldPlugin.ExamplePlugin, HelloWorldPlugin  
Created plugin: HelloWorldPlugin.ExamplePlugin, HelloWorldPlugin  
Started: Initialize pluginHelloWorldPlugin.ExamplePlugin, HelloWorldPlugin  
Finished: Initialize plugin HelloWorldPlugin.ExamplePlugin, HelloWorldPlugin  
18:20:39.015: LoadPlugins: End: 2ms  
18:20:39.015: ParseCs: Start:   
18:20:39.028: 4 Source files  
18:20:39.248: Sources files End: 218  
18:20:39.248: 9 References  
18:20:40.011: 9 References End:  
18:20:40.014: CreateCompilation  
ExamplePlugin.AfterParseCs >> Hello World!  
18:20:40.029: ParseCs: End: 1012ms  
18:20:40.029: ApplyExternalMetadata: Start:   
18:20:40.155: ApplyExternalMetadata: End: 126ms  
18:20:40.155: ConvertCsToJs: Start:   
18:20:40.228: ApplyNavigator  
18:20:40.277: ApplyNavigator End:48  
18:20:40.368: ConvertCsToJs: End: 212ms  
18:20:40.368: MergeJsFiles: Start:   
18:20:40.369: GenerateCodeInjectionFile: Start:   
18:20:40.370: GenerateCodeInjectionFile: End: 1ms  
18:20:40.372: MergeJsFiles: End: 4ms  
18:20:40.372: InjectJsCode: Start:   
18:20:40.391: InjectJsCode: End: 18ms  
18:20:40.391: OptimizeJsFiles: Start:   
18:20:40.395: OptimizeJsFiles: End: 3ms  
18:20:40.395: SaveJsFiles: Start:       resClassLibraryProj.js  
18:20:40.418: SaveJsFiles: End: 23ms  
18:20:40.418: EmbedResources: Start:   
18:20:40.423: EmbedResources: End: 4ms  
18:20:40.423: SaveNewManifest: Start:   
18:20:40.434: SaveNewManifest: End: 10ms  
Total: 1466ms  
ClassLibraryProj -> C:...binDebugClassLibraryProj.dll
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========

Debugging The Code of a SharpKit Plugin:

Now that you have a plug-in in the works, most people want to be able to debug the plug-in during development. It took me a few to figure this out but in Visual Studio it’s pretty easy to setup. Essentially when you debug your plug-in project you will need to execute the skc5.exe and pass in a handful of compiler arguments to get it to work.

To configure debugging edit the properties of your plug-in project:

  • Open the Debug tab
  • Set the “Start Action” to “Start External Program:”
  • Browse to the skc5.exe file in the install directory. ( C:WindowsMicrosoft.NETFrameworkv4.0.30319SharpKit5skc5.exe )

Command line arguments: The easiest way to get these are to copy them right out of the output window of the project you just compiled in the previous step. Make sure there are no line breaks, I added them here to make it more readable in this post. Once you have added them, you should be able to debug your application.

/dir:"C:...Documents_projectsSharpKitPluginExampleSharpKitPluginExampleClassLibraryProj" 
/define:DEBUG;TRACE 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0Microsoft.CSharp.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0mscorlib.dll" 
/reference:C:...Documents_projectsSharpKitPluginExampleSharpKitPluginExampleClassLibraryProjbinSharpKit.JavaScript.dll 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.Core.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.Data.DataSetExtensions.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.Data.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.Xml.dll" 
/reference:"C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.0System.Xml.Linq.dll" 
/out:objDebugClassLibraryProj.dll 
/target:library Project.cs User.cs PropertiesAssemblyInfo.cs "C:...AppDataLocalTemp.NETFramework,Version=v4.0.AssemblyAttributes.cs" 
/plugin:"HelloWorldPlugin.ExamplePlugin, HelloWorldPlugin" 
/rebuild 
/TargetFrameworkVersion:v4.0

Note: If you debug the plug-in and notice the console window opens and closes, there is a configuration problem somewhere.

Set a break and debug your the app, and you should see you hit your break point in Visual Studio.

Conclusion:

Hopefully that will get people started on working with SharpKit plug-ins. Coming up will be some tutorials on working with SharpKit‘s JSAst to manipulate the AST to alter the generated JavaScript. Let me know if you have any questions.

When I get time I’ll put together part 2: Modifying the SharpKit AST to alter the generated JS.

There are no comments.

Leave a Reply