self-compiled .net hybrids

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
npocmaka_
Posts: 512
Joined: 24 Jun 2013 17:10
Location: Bulgaria
Contact:

Re: self-compiled .net hybrids

#16 Post by npocmaka_ » 24 Jun 2015 08:03

Without redundant output:



Code: Select all

// 2>nul||@goto :batch
/*
:batch
@echo off

setlocal
:: find csc.exe
set "frm=%SystemRoot%\Microsoft.NET\Framework\"
for /f "tokens=* delims=" %%v in ('dir /b /a:d  /o:-n "%SystemRoot%\Microsoft.NET\Framework\v*.*"') do (
   set netver=%%v
   goto :break_loop
)
:break_loop
set csc=%frm%%netver%\csc.exe
:: csc.exe found
if not exist %~n0.exe (
   %csc% /nologo /out:"%~n0.exe" "%~dpsfnx0" >nul
)
%~n0.exe
echo -- redundant output has been cleared --

endlocal
exit /b 0
*/
public class Hello
{
   public static void Main() {
      ClearC();
      System.Console.WriteLine("Hello, C# World!");
   }

   public static void ClearC() {
      try {
         System.Console.CursorTop = System.Console.CursorTop - 1;
         System.Console.Write(new string(' ', System.Console.BufferWidth));
         System.Console.CursorTop = System.Console.CursorTop - 1;
      } catch (System.IO.IOException e) {
      }
   }
}


though if there are `echo`-es before the c# part you'll need to change the cursor position.And wont work if the file is redirected to a file.Also I'm not sure if Console.CursorTop is available in .net 2 and 3

Ben Mar
Posts: 22
Joined: 03 May 2015 10:51

Re: self-compiled .net hybrids

#17 Post by Ben Mar » 24 Jun 2015 14:37

npocmaka_ wrote:Without redundant output:

You can do that trick with "cls" too :-)

Code: Select all

//>nul 2>nul||@goto :batch
/*
:batch
@echo off
setlocal
cls
:: find csc.exe
set "frm=%SystemRoot%\Microsoft.NET\Framework\"
for /f "tokens=* delims=" %%v in ('dir /b /a:d  /o:-n "%SystemRoot%\Microsoft.NET\Framework\v*"') do (
   set netver=%%v
   goto :break_loop
)
:break_loop
set csc=%frm%%netver%\csc.exe
:: csc.exe found

call %csc% /nologo /out:"%~n0.exe" "%~dpsfnx0"
%~n0.exe
endlocal
exit /b 0
*/
public class Hello
{
   public static void Main()
   {
      System.Console.WriteLine("Hello, C# World!");
   }
}

npocmaka_
Posts: 512
Joined: 24 Jun 2013 17:10
Location: Bulgaria
Contact:

Re: self-compiled .net hybrids

#18 Post by npocmaka_ » 21 Mar 2016 10:56

I've just found this:

Code: Select all

https://gist.github.com/subTee/28b7439d3dfa07053b61


Looks like something that will allow platform invoke from JScript.net.
Not tested yet.
It will be good it works because in some cases will be a good replacement of C# and its redundant output.
Though it will be not so powerful as jscript does not allow passing by reference.

npocmaka_
Posts: 512
Joined: 24 Jun 2013 17:10
Location: Bulgaria
Contact:

Re: self-compiled .net hybrids

#19 Post by npocmaka_ » 07 Oct 2016 06:15

using msbuild (without redundant output and without temp files ! ) :

Code: Select all

<!-- :
    @echo off


        echo -^- FROM BATCH

        set "CMD_ARGS=%*"
        ::::::  Starting C# code :::::::
        :: searching for msbuild location
        for /r "%SystemRoot%\Microsoft.NET\Framework\" %%# in ("*msbuild.exe") do  set "msb=%%#"

        if not defined  msb (
           echo no .net framework installed
           exit /b 10
        )

        rem ::::::::::  calling msbuid :::::::::
        call %msb% /nologo  /noconsolelogger "%~dpsfnx0"  /property:"H=From C#"
        rem ::::::::::::::::::::::::::::::::::::
        exit /b %errorlevel%
-->


<Project ToolsVersion="$(MSBuildToolsVersion)" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="_">
    <_/>
  </Target>
  <UsingTask
    TaskName="_"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll" >

    <ParameterGroup  >
         <Z ParameterType="System.String">$(H)</Z>
    </ParameterGroup>

    <Task>
      <Using Namespace="System" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[
            String CMD_ARGS=Environment.GetEnvironmentVariable("CMD_ARGS");
            System.Console.WriteLine("-- "+"$(H)"); 
        ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>



Will have to experiment more with this to see how P/Invoke can be used (something I can't do neat with jscript.net)

https://msdn.microsoft.com/en-us/library/dd722601.aspx

npocmaka_
Posts: 512
Joined: 24 Jun 2013 17:10
Location: Bulgaria
Contact:

Re: self-compiled .net hybrids

#20 Post by npocmaka_ » 06 Jan 2017 16:02

More notes on the msbuild technique.
If you need a reference to a dll you'll need to include it in the task node in the xml (alike the simple /r:some.dll in csc compiler)

you cant use the "--" directly in batch part because it will broke the xml parsing (I'll have to think how the batch part can be put in CDATA section... may be will be not possible)

The type can be fragment (as in the previous example) or class or method

Using a class needs more effort than a fragment - you need to implement ITask interface . Though it will be easier to inherit Task class which has some pipe work done. These are in Microsoft.Build.Framework and Microsoft.Build.Utilities namespaces. Doing so you'll have to override Execute method.

The class must be public (though you can have more non public classes) and it should have the same name as the task you want to execute.

Here's an example:

Code: Select all

<!-- :
    @echo off


        echo -^- FROM BATCH

        set "CMD_ARGS=%*"
        ::::::  Starting C# code :::::::
        :: searching for msbuild location
        for /r "%SystemRoot%\Microsoft.NET\Framework\" %%# in ("*msbuild.exe") do  set "msb=%%#"

        if not defined  msb (
           echo no .net framework installed
           exit /b 10
        )

        rem ::::::::::  calling msbuid :::::::::
        call %msb% /nologo  /noconsolelogger "%~dpsfnx0"  /property:"H=From C#"
        rem ::::::::::::::::::::::::::::::::::::
        exit /b %errorlevel%
      
-->


<Project ToolsVersion="$(MSBuildToolsVersion)" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="Program">
    <Program/>
  </Target>
  <UsingTask
    TaskName="Program"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll" >

    <ParameterGroup  >
         <Z ParameterType="System.String">$(H)</Z>
    </ParameterGroup>

    <Task>
     <Reference Include="$(MSBuildToolsPath)\System.Windows.Forms.dll"/>
      <Using Namespace="System" />
 
      <Code Type="Class" Language="cs">
        <![CDATA[
   using Microsoft.Build.Framework;
   using Microsoft.Build.Utilities;
   using System;
   
   public class Program:Task, ITask
    {
      public override bool Execute(){
         Console.WriteLine("Whoa");
         String CMD_ARGS=Environment.GetEnvironmentVariable("CMD_ARGS");
                        System.Console.WriteLine("-- "+"$(MSBuildToolsVersion)");
         return true;
      }
   }
        ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>


Also the MSBuildToolsVersion and MSBuildToolsPath are no more hard coded in the xml.

As a reference I've used this - https://gist.github.com/subTee/6fa2b0cc ... 0e6d93f31a (in the subtee's gists there are pretty interesting things)

Still not tried to implement a method The msdn does not tell much -
If the value of Type is Method, then the code defines an override of the Execute method of the ITask interface.


I feel a little lonely in my excitement about this - but for sure will try and share the method type task too.


EDIT. Example with method (the msdn explanation was more than enough):

Code: Select all

<!-- :
    @echo off


        echo -^- FROM BATCH

        set "CMD_ARGS=%*"
        ::::::  Starting C# code :::::::
        :: searching for msbuild location
        for /r "%SystemRoot%\Microsoft.NET\Framework\" %%# in ("*msbuild.exe") do  set "msb=%%#"

        if not defined  msb (
           echo no .net framework installed
           exit /b 10
        )

        rem ::::::::::  calling msbuid :::::::::
        call %msb% /nologo  /noconsolelogger "%~dpsfnx0"
        rem ::::::::::::::::::::::::::::::::::::
        exit /b %errorlevel%
      
-->


<Project ToolsVersion="$(MSBuildToolsVersion)" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="_">
    <_/>
  </Target>
  <UsingTask
    TaskName="_"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll" >

    <Task>
     <Reference Include="$(MSBuildToolsPath)\System.Windows.Forms.dll"/>
      <Using Namespace="System" />
 
      <Code Type="Method" Language="cs">
        <![CDATA[

      public override bool Execute(){
         MyMethod();
         return true;
      }
      
      void MyMethod(){
         Console.WriteLine("Whoa");
         String CMD_ARGS=Environment.GetEnvironmentVariable("CMD_ARGS");
            System.Console.WriteLine("-- "+"$(MSBuildToolsVersion)");
      }
        ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>

LotPings
Posts: 10
Joined: 26 Oct 2016 22:45

Re: self-compiled .net hybrids

#21 Post by LotPings » 07 Jan 2017 08:58

Nice one npocmaka,

two questions
1. why do you call msbuild.exe?
2. are there remnant files from the build, if yes where are they located?

Wish you a happy new year.

npocmaka_
Posts: 512
Joined: 24 Jun 2013 17:10
Location: Bulgaria
Contact:

Re: self-compiled .net hybrids

#22 Post by npocmaka_ » 07 Jan 2017 09:13

LotPings wrote:Nice one npocmaka,

two questions
1. why do you call msbuild.exe?
2. are there remnant files from the build, if yes where are they located?

Wish you a happy new year.


1.MSBuild works with xml files which can be used in batch files for comment lines without redundant output. the "<!-- :" will be executed as ":<!--" i.e. label and there will be nothing displayed in the console.

2.This is even more beautiful - there are no build files , no temp files - everything is loaded in the memory (may be this mean that it will be faster but I'n not sure). Except there's a compilation error - there will be some source files in the %temp% where you can see in more details what msbuild is doing.


WRONG:The only impediment is that you can't exit with custom error codes if the build is successful it returns 0 otherwise 1 , depending on what is returned from Execute() method.

npocmaka_
Posts: 512
Joined: 24 Jun 2013 17:10
Location: Bulgaria
Contact:

Re: self-compiled .net hybrids

#23 Post by npocmaka_ » 01 Aug 2018 05:39

Probably the inline tasks for msbuild will remain the best method for .net hybridization. But this will require .net framework 4.6 (can it be installed on something different than windows 10?) so it is not so portable.

Using c# without msbuild will produce redundant output because of the '// 2>nul||@goto :batch' line (vb.net also will produce something similar). JScript.net does not support syntax sugaring for platform invokes but it is still possible (http://cx20.main.jp/blog/hello/2013/03/ ... net-world/):

Code: Select all

@if (@X)==(@Y) @end /* JScript comment
@echo off
setlocal

set "jsc="
for /r "%SystemRoot%\Microsoft.NET\Framework\" %%# in ("*jsc.exe") do  set "jsc=%%#"

if not exist "%jsc%" (
   echo no .net framework installed
   exit /b 10
)


::if not exist "%~n0.exe" (
	call "%jsc%"  /r:"System.Windows.Forms.dll" /nologo /out:"%~n0.exe" "%~dpsfnx0"||(
		exit /b %errorlevel%
	)
::)

"%~n0.exe"

endlocal & exit /b %errorlevel%

*/

import System;
import System.Reflection;
import System.Reflection.Emit;
import System.Runtime;
import System.Text;
 
// Invoke a Win32 P/Invoke call.
// http://www.leeholmes.com/blog/2006/07/21/get-the-owner-of-a-process-in-powershell-%e2%80%93-pinvoke-and-refout-parameters
function InvokeWin32(dllName:String, returnType:Type,
  methodName:String, parameterTypes:Type[], parameters:Object[])
{
  // Begin to build the dynamic assembly
  var domain = AppDomain.CurrentDomain;
  var name = new System.Reflection.AssemblyName('PInvokeAssembly');
  var assembly = domain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
  var module = assembly.DefineDynamicModule('PInvokeModule');
  var type = module.DefineType('PInvokeType',TypeAttributes.Public + TypeAttributes.BeforeFieldInit);
 
  // Define the actual P/Invoke method
  var method = type.DefineMethod(methodName, MethodAttributes.Public + MethodAttributes.HideBySig + MethodAttributes.Static + MethodAttributes.PinvokeImpl, returnType, parameterTypes);
 
  // Apply the P/Invoke constructor
  var ctor = System.Runtime.InteropServices.DllImportAttribute.GetConstructor([Type.GetType("System.String")]);
  var attr = new System.Reflection.Emit.CustomAttributeBuilder(ctor, [dllName]);
  method.SetCustomAttribute(attr);
 
  // Create the temporary type, and invoke the method.
  var realType = type.CreateType();
  return realType.InvokeMember(methodName, BindingFlags.Public + BindingFlags.Static + BindingFlags.InvokeMethod, null, null, parameters);
}
 
function MessageBox(hWnd:Int32, lpText:String, lpCaption:String, uType:Int32) 
{ 
   var parameterTypes:Type[] = [Type.GetType("System.Int32"),Type.GetType("System.String"),Type.GetType("System.String"),Type.GetType("System.Int32")];
   var parameters:Object[] = [hWnd, lpText, lpCaption, uType];
 
   return InvokeWin32("user32.dll", Type.GetType("System.Int32"), "MessageBoxA", parameterTypes,  parameters );
} 
 
MessageBox( 0, "Hello, Win32 API World!", "Hello, World!", 0 );
the interesting part is in the InvokeWin32 function (nothing so complicated and can't be used directly without knowing too much about reflection ).

Despite it is no more supported by MS jscript.net is the most backward compatible .net tool installed by default on Windows machines (it is not installed on win XP by default though it is highly probable ,but it is time to let XP die anyway).

With pure batch scripts and with WSH/jscript/vbscript there are limits that can't be overcome without accessing windows system libraries - so jscript.net is a worthy thing to dig in.

Probably I'll try to 'translate' the tools I've wrote with C# to jscript.net and if possible to update the examples in http://pinvoke.net/

Ruchipatil
Posts: 1
Joined: 24 Jul 2019 00:11
Contact:

Re: self-compiled .net hybrids

#24 Post by Ruchipatil » 24 Jul 2019 03:06

:lol: Thank you for the information it was very useful.

jfl
Posts: 226
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: self-compiled .net hybrids

#25 Post by jfl » 28 Jul 2019 09:53

One small improvement to the batch initialization:
During the JS development phase, it's necessary to recompile the source script every time it's updated.
The following code automates that:

Code: Select all

@echo off
setlocal

set "jsc="
for /r "%SystemRoot%\Microsoft.NET\Framework\" %%# in ("*jsc.exe") do  set "jsc=%%#"
if not exist "%jsc%" echo No .NET framework installed & exit /b 10

set "EXE=%~dpn0.exe" &:# Full pathname of the compiled executable
if exist "%EXE%" ( :# If the EXE exists and the BAT is newer, then delete the EXE
  xcopy /d /y /l "%~f0" "%EXE%" | findstr "%~d0" >NUL && del "%EXE%"
)
if not exist "%EXE%" (
  "%jsc%" /nologo /out:"%EXE%" "%~f0" || exit /b
)

"%EXE%" %*

endlocal & exit /b %errorlevel%

penpen
Expert
Posts: 1991
Joined: 23 Jun 2013 06:15
Location: Germany

Re: self-compiled .net hybrids

#26 Post by penpen » 15 Feb 2020 11:38

I got rid of that unwanted echo ("//>nul 2>nul||") when compiling a c# sourcecodefile contained in a batch at the cost of a temporary (compiler) executable (created and deleted by powershell - but like that more than the echo - the c# source is also easy to read):

Code: Select all

@echo off
setlocal enableExtensions disableDelayedExpansion
goto :init

:main
for /f "tokens=1 delims=:" %%a in ('findstr  /o /c:"%~1" "%~f0"') do set /a "offset=%%~a"
for /f "tokens=1 delims=:" %%a in ('findstr  /n /r /c:"^.Source = @" "%~f0"') do set /a "sourceOffset=%%~a"
for %%a in ("%ComSpec%") do set "ps.exe=%%~dpaWindowsPowerShell\v1.0\powershell.exe"
if not exist "%ps.exe%" exit /b 2 ERROR_FILE_NOT_FOUND
call rem init ERROR_SUCCESS
"%ps.exe%" "-Command" "$SourceOffset = "%sourceOffset%"; Invoke-Expression $([System.IO.File]::ReadAllText('%~f0').substring(%offset%))"
exit /b %errorLevel%

:init
call :main ^
#powershell
<# <nul exit /b %errorLevel%
	hybrid batch function/powershell comment
#>
Function Get-CurrentLine {
    $Myinvocation.ScriptlineNumber
}

$CompilerAssemblies = (
	"System",
	"Microsoft.CSharp"
)

$CompilerSource = @"
using System;
using System.Collections.Generic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

namespace Penpen {
	public static class Compiler {
		public static void Compile(string[] assemblies, string source, string mainClass, string exeFileName) {
			var options = new Dictionary<string, string> { { "CompilerVersion", "v4.0" } };
			CSharpCodeProvider compiler = new CSharpCodeProvider(options);
			System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters();

			parameters.OutputAssembly = exeFileName;
			if (mainClass != null) {
				parameters.MainClass = mainClass;		// set main entry point of the executable
			}
			if (assemblies != null) {
				for (int i = 0; i < assemblies.Length; ++i) {
					parameters.ReferencedAssemblies.Add(assemblies[i] + ".dll");
				}
			}
			parameters.GenerateExecutable = true;		// true: generate exe; false: generate dll
			parameters.GenerateInMemory = false;		// true: memory; false: disk
			parameters.IncludeDebugInformation = true;	// true: debug; false: release
			parameters.WarningLevel = 4;			// 0: no; 1: severe; 2: +normal; 3: +less severe; 4(default): +informational
			parameters.TreatWarningsAsErrors = true;

			CompilerResults results = compiler.CompileAssemblyFromSource(parameters, source);
			if (results.Errors.Count > 0) {
				foreach(CompilerError CompErr in results.Errors) {
					Console.WriteLine(
						"Line number {0}, Error Number: {1}, '{2}';\r\n",
						(CompErr.Line + 
"@
$CompilerSource += $sourceOffset
$CompilerSource += @"
),
						CompErr.ErrorNumber,
						CompErr.ErrorText
					);
				}
			}
		}
	}
}
"@

$Assemblies = (
	"System"
)

$Source = @"
using System;

namespace Penpen {
	public static class Test {
		public static void Write(string message) {
			Console.Write(message);
		}

		public static void WriteLine(string message) {
			Console.WriteLine(message);
		}

		public static void Main(string[] messages) {
			if (messages == null) {
			} else if (messages.Length <= 0) {
			} else {
				for (int i = 0; i < messages.Length-1; ++i) {
					Write(messages[i] + " ");
				}
				WriteLine(messages[messages.Length-1]);
			}
		}
	}
}
"@

#Add-Type -ReferencedAssemblies $Assemblies -TypeDefinition $Source -Language CSharp
#[Penpen.Test]::Write("Hello")
#[Penpen.Test]::WriteLine(" world!");

Add-Type -ReferencedAssemblies $CompilerAssemblies -TypeDefinition $CompilerSource -Language CSharp
[Penpen.Compiler]::Compile($Assemblies, $Source, "Penpen.Test", "test.exe")
penpen

Post Reply