Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!

C# Run

Aug
2,799
144
The C#Run library function will compile a C# .NET Framework 4.8 .cs file,
and then execute the compiled .exe file.

This is configured to run on my system,
so you will have to make changes in order for it to run on your system.

I keep my library files in my C:\Program Files\JPSoft\TCMD36\library folder.
Update the _libraryname environment variable in this library function to match your system.

I am using the C# Compiler from Microsoft Visual Studio 2022.
Change the library code to the location of your C# Compilter.

Code:
c#run {
@setlocal
@echo off

if isdir r:\temp set temp=r:\temp

set _libraryfunction=%0

rem _library is defined in TCSTART.BTM
rem C:\...\TCMD36>echo %_library
rem "C:\Program Files\JPSoft\TCMD36\library"
set _libraryname=%_library\utils.library

alias csc="C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\Roslyn\csc.exe" -nologo
set theEXE=%temp\%@name[%1].exe

iff %# eq 0 then
  echo USAGE: %_libraryfunction test.csx
  quit
endiff

set HashValue=%@iniread[%_libraryname:%@name[%_libraryname].ini,Main,hashvalue]

iff not defined HashValue then
  Gosub CreateHash
  set HashValue=%@iniread[%_batchname:%@name[%_batchname].ini,Main,hashvalue]
endiff

Gosub CompareHash

iff %CompareHash eq Y then
  Gosub RunEXE
else
  Gosub CompileEXE
  iff exist %theEXE then
    Gosub RunEXE
    Gosub CreateHash
    set CompareHash=Y
  endiff
endiff

if isalias csc unalias csc

endlocal
quit

:CreateHash
  echo %@iniwrite[%_libraryname:%@name[%_libraryname].ini,Main,hashvalue,%@md5[%1]] > nul
Return

:CompareHash
debugstring .ini Hash Value: %HashValue
debugstring @MD5 Hash Value: %@md5[%1]
iff %HashValue eq %@md5[%1] then
  set CompareHash=Y
else
  set CompareHash=N
endiff
debugstring CompareHash: %CompareHash
Return

:RunEXE
iff exist %theEXE then
  %theEXE %2$
else
  Gosub CompileEXE
  %theEXE %2$
  Gosub CreateHash
endiff
Return

:CompileEXE
  csc %1 -out:%theEXE
  iff %? ne 0 then
    echo Error compiling %theEXE
    quit
  endiff
Return
}

Here is a sample .cs file you can use to test the C#Run library function.

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

public class Example
{
   public static void Main()
   {
      var os = Environment.OSVersion;
      Console.WriteLine("Current OS Information:\n");
      Console.WriteLine("Platform: {0:G}", os.Platform);
      Console.WriteLine("Version String: {0}", os.VersionString);
      Console.WriteLine("Version Information:");
      Console.WriteLine("   Major: {0}", os.Version.Major);
      Console.WriteLine("   Minor: {0}", os.Version.Minor);
      Console.WriteLine("Service Pack: '{0}'", os.ServicePack);
   }
}
// If run on a Windows 10 system, the example displays output like the following:
//       Current OS Information:
//
//       Platform: Win32NT
//       Version String: Microsoft Windows NT 6.2.9200.0
//       Version Information:
//          Major: 6
//          Minor: 2
//       Service Pack: ''
//
// If run on a Windows 7 system, the example displays output like the following:
//       Current OS Information:
//
//       Platform: Win32NT
//       Version String: Microsoft Windows NT 6.1.7601 Service Pack 1
//       Version Information:
//          Major: 6
//          Minor: 1
//       Service Pack: 'Service Pack 1'

Running the example;

Code:
U:\>c#run os.cs
Current OS Information:

Platform: Win32NT
Version String: Microsoft Windows NT 6.2.9200.0
Version Information:
   Major: 6
   Minor: 2
Service Pack: ''

First time usage will be slower,
as the CSharp code must be compiled to an .exe file.

If C#Run detects no changes to the os.cs source code,
C#Run os.cs will be much faster,
since %temp\os.exe is executed,
with no initial compilation required.

Ref: LIBRARY - Create, modify, delete, or display library functions

Joe
 
When you invoke the C# compiler,
csc.exe must be loaded and initialized for each compilation.

Windows may keep some of its files in the system cache,
and it may create a Prefetch entry,
but neither is guaranteed.

However, csc.exe also supports a shared compiler process.

For example:

Code:
csc -nologo /shared /keepalive:600 whatever.cs -out:whatever.exe

With /shared, the compiler can remain running after the compilation finishes.

/keepalive:600 tells it to stay alive for up to 600 seconds while waiting for another compilation.

A subsequent compilation can then reuse the existing compiler process instead of starting csc.exe from scratch.

I've modified the code for c#run to take advantage of this.

Note well that c#run will use the created .exe once created,
unless the source code for the .exe has been changed.

So, this is really only beneficial if you’re often changing the source code and running it as a script.

Code:
c#run {
@setlocal
@echo off

if isdir r:\temp set temp=r:\temp

set _libraryfunction=%0

rem _library is defined in TCSTART.BTM
rem C:\...\TCMD36>echo %_library
rem "C:\Program Files\JPSoft\TCMD36\library"
set _libraryname=%_library\utils.library

set csc="C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\Roslyn\csc.exe"

set theEXE=%temp\%@name[%1].exe

iff %# eq 0 then
  echo USAGE: %_libraryfunction test.csx
  quit
endiff

iff defined rebuild then
  iff exist %theEXE then
    del /q %theEXE
  endiff
endiff

set HashValue=%@iniread[%_libraryname:%@name[%_libraryname].ini,Main,hashvalue]

iff not defined HashValue then
  Gosub CreateHash
  set HashValue=%@iniread[%_batchname:%@name[%_batchname].ini,Main,hashvalue]
endiff

Gosub CompareHash

iff %CompareHash eq Y then
  Gosub RunEXE
else
  Gosub CompileEXE
  iff exist %theEXE then
    Gosub RunEXE
    Gosub CreateHash
    set CompareHash=Y
  endiff
endiff

if isalias csc unalias csc

endlocal
quit

:CreateHash
  echo %@iniwrite[%_libraryname:%@name[%_libraryname].ini,Main,hashvalue,%@md5[%1]] > nul
Return

:CompareHash
debugstring .ini Hash Value: %HashValue
debugstring @MD5 Hash Value: %@md5[%1]
iff %HashValue eq %@md5[%1] then
  set CompareHash=Y
else
  set CompareHash=N
endiff
debugstring CompareHash: %CompareHash
Return

:RunEXE
iff exist %theEXE then
  %theEXE %2$
else
  Gosub CompileEXE
  %theEXE %2$
  Gosub CreateHash
endiff
Return

:CompileEXE
  rem You can remove VBCSCompiler.exe from memory by doing
  rem taskkill.exe /im VBCSCompiler.exe /f
  if %@pid[VBCSCompiler.exe] eq 0 echo /shared /keepalive:600 not active.
  rem /keepalive:600 is about 10 minutes.
  rem Initial csc.exe time is 00:02.112
  rem Subsiqent csc.exe times average 00:01.320
  %csc -nologo /shared /keepalive:600 %1 -out:%theEXE
  iff %? ne 0 then
    echo Error compiling %theEXE
    quit
  endiff
  if %@pid[VBCSCompiler.exe] eq 0 echo /shared /keepalive:600 not active.
Return
}

Thanks to the @Charles Dye plugin TimeTrials,
Ref: TimeTrials Plugin
which I used to verify the run results of /shared and /keepalive.

Joe
 
Last edited:
For future reference,
here are links to the C# Compiler source code,
specifically in reference for /shared and /keepalive.

BuildServerController.cs: roslyn/src/Compilers/Server/VBCSCompiler/BuildServerController.cs at main · dotnet/roslyn
CommandLineParser.cs: roslyn/src/Compilers/Core/Portable/CommandLine/CommandLineParser.cs at main · dotnet/roslyn

Note that these arguments are NOT documented,
and could disappear or change in a future version of Visual Studio.

If you do csc.exe /? you will not see /shared or /keepalive displayed.

Thanks to @rconn for adding the ability to create C# plugins for TCC.

Without this,
I would have had no reason to pursue C# as deeply as I have been doing.

Joe
 
Back
Top