Library for Inline C# in MSH

In the last post, we got nearly as far as we could in improving the performance of our MSH script.  We used the profiler to help target our performance optimizations.  After we were finished, setting variables, incrementing counters, and comparing colour values took up the vast majority of our time.  We can’t make these statements more efficient, nor can we execute them less frequently.


To really push the performance of this script, we’ll write the highly critical sections using inline C#, rather than MSH.


This interop idea is something that many people end implement on their own after playing with .Net (and MSH) for any length of time.  I use it extensively in my C# Performance Comparison tool, MOW wrote it for VB in MSH, Jeffrey Snover wrote it for C# in MSH (as did half of the rest of the Monad team,) and Bruce Payette even wrote one for MSIL in MSH.


Normally, I would just link to an implementation of “C# in MSH,” but I’m going to offer a library function that goes a bit further:



  1. The inline C# accepts dynamic arguments, and returns dynamic values.  Other implementations hard-code the parameters, and can’t interact with the rest of the script.
  2. The inlining mechanism caches the temporary compiled C# class.  No matter how often you call the same inline code in a script, you will only suffer the (relatively minor) compilation burden once.
  3. The inlined code does not require any class definitions, namespace imports, or other template code.

It is commented in excruciating detail below.  Don’t worry, you’ll have a nice speedy fire warming up your console in no time flat.


[Edit: A better way to get the installation path of PowerShell is $psHome]
[Edit: Updated to work with RTM builds]
[Edit: Added ability to reference other DLLs]




################################################################################ 
## Invoke-Inline.ps1
## Library support for inline C#
##
## Usage
## 1) Define just the body of a C# method, and store it in a string. “Here
## strings” work great for this. The code can be simple:
##
## $codeToRun = “Console.WriteLine(Math.Sqrt(337));”
##
## or more complex:
##
## $codeToRun = @”
## string firstArg = (string) ((System.Collections.ArrayList) arg)[0];
## int secondArg = (int) ((System.Collections.ArrayList) arg)[1];
##
## Console.WriteLine(“Hello {0} {1}”, firstArg, secondArg );
##
## returnValue = secondArg * 3;
## “@
##
## 2) (Optionally) Pack any arguments to your function into a single object.
## This single object should be strongly-typed, so that PowerShell does
## not treat it as a PsObject.
## An ArrayList works great for multiple elements. If you have only one
## argument, you can pass it directly.
##
## [System.Collectionts.ArrayList] $arguments =
## New-Object System.Collections.ArrayList
## [void] $arguments.Add(“World”)
## [void] $arguments.Add(337)
##
## 3) Invoke the inline code, optionally retrieving the return value. You can
## set the return value in your inline code by assigning it to the
## “returnValue” variable as shown above.
##
## $result = Invoke-Inline $codeToRun $arguments
##
##
## If your code is simple enough, you can even do this entirely inline:
##
## Invoke-Inline “Console.WriteLine(Math.Pow(337,2));”
##
################################################################################
param(
[string] $code,
[object] $arg,
[string[]] $reference = @()
)

## Stores a cache of generated inline objects. If this library is dot-sourced
## from a script, these objects go away when the script exits.
if(-not (Test-Path Variable:\lee.holmes.inlineCache))
{
${GLOBAL:lee.holmes.inlineCache} = @{}
}

## The main function to execute inline C#.
## Pass the argument to the function as a strongly-typed variable. They will
## be available from C# code as the Object variable, “arg”.
## Any values assigned to the “returnValue” object by the C# code will be
## returned to MSH as a return value.

function main
{
## See if the code has already been compiled and cached
$cachedObject = ${lee.holmes.inlineCache}[$code]

## The code has not been compiled or cached
if($cachedObject -eq $null)
{
$codeToCompile =
@”
using System;

public class InlineRunner
{
public Object Invoke(Object arg)
{
Object returnValue = null;

$code

return returnValue;
}
}
“@

## Obtains an ICodeCompiler from a CodeDomProvider class.
$provider = New-Object Microsoft.CSharp.CSharpCodeProvider

## Get the location for System.Management.Automation DLL
$dllName = [PsObject].Assembly.Location

## Configure the compiler parameters
$compilerParameters = New-Object System.CodeDom.Compiler.CompilerParameters

$assemblies = @(“System.dll”, $dllName)
$compilerParameters.ReferencedAssemblies.AddRange($assemblies)
$compilerParameters.ReferencedAssemblies.AddRange($reference)
$compilerParameters.IncludeDebugInformation = $true
$compilerParameters.GenerateInMemory = $true

## Invokes compilation.
$compilerResults =
$provider.CompileAssemblyFromSource($compilerParameters, $codeToCompile)

## Write any errors if generated.
if($compilerResults.Errors.Count -gt 0)
{
$errorLines = “”
foreach($error in $compilerResults.Errors)
{
$errorLines += “`n`t” + $error.Line + “:`t” + $error.ErrorText
}
Write-Error $errorLines
}
## There were no errors. Store the resulting object in the object
## cache.
else
{
${lee.holmes.inlineCache}[$code] =
$compilerResults.CompiledAssembly.CreateInstance(“InlineRunner”)
}

$cachedObject = ${lee.holmes.inlineCache}[$code]
}

## Finally invoke the C# code
if($cachedObject -ne $null)
{
return $cachedObject.Invoke($arg)
}
}

. Main


[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

One Response to “Library for Inline C# in MSH”

  1. Vincent Hoogendoorn writes:

    I created an enhanced version of this script that also offers:
    <ul>
    <li>The C# code you specify is completely self-contained; no more C# wrapper code where your code is pasted into. This improves readability and maintainability of inline C# code.</li>
    <li>Simpler parameter passing; no more wrapper code needed for parameters and return values.</li>
    <li>Automatically un-wraps parameters that PowerShell wraps in a PSObject. This eliminates the need to explicitly cast parameters to make them strongly typed on each call to InvokeCSharp, and to always wrap them in @().
    Figuring this one out was interesting; it required diving in the innards of the <a href="http://msdn.microsoft.com/en-us/library/ms714419(VS.85).aspx">PowerShell Extended Type System</a> (ETS) which gets in the way when you work with .NET reflection classes from PowerShell. PowerShell tries to make the ETS transparent to the PowerShell user, which in general is a good thing but in this case meant it was hard to see what was happening.</li>
    <li>Adds support for calling static methods, including overloaded methods, without creating a class instance.</li>
    <li>Adds support for creating a class instance and providing constructor parameters.</li>
    <li>Caches compiled assemblies instead of class instances. This improves performance because it eliminates duplicate compilation when the same C# code is called more than once. In addition to assembly caching you can of course also cache the object instances using any of the standard PowerShell mechanisms; however this is better handled outside the InvokeCSharp function.</li>
    <li>Support for inline C# and separate C# source files; the latter allows you to easily edit the C# in Visual Studio, e.g. to use IntelliSense and compile the source to catch compile time errors.</li>
    </ul>

    See http://vincenth.net/blog/archive/2009/10/27/call-inline-c-from-powershell-with-invokecsharp.aspx for details.

Leave a Reply