Change application type with Mono.Cecil? - c#

How can I modify an application from Console Application Type to Windows Application Type and vice versa with Mono.Cecil?

To convert a console .exe to windows .exe, you can use:
var file = "foo.exe";
var module = ModuleDefinition.ReadModule (file);
// module.Kind was previously ModuleKind.Console
module.Kind = ModuleKind.Windows;
module.Write (file);
The other way around is as simple as choosing the appropriate ModuleKind value. From Cecil's source:
public enum ModuleKind {
Dll,
Console,
Windows,
NetModule,
}

For people who needed more help on this like me :)
you may need the apt pacakge libmono-cecil-cil-dev
//mono-cecil-set-modulekind-windows.cs
using System;
using Mono.Cecil;
namespace CecilUtilsApp {
class CecilUtils {
static void Main(string[] args) {
var file = args[0];
var module = ModuleDefinition.ReadModule (file);
module.Kind = ModuleKind.Windows;
module.Write (file);
}
}
}
// -----
//Makefile
//mono-cecil-set-modulekind-eq-windows.exe:
// mcs $(shell pkg-config --libs mono-cecil) ./mono-cecil-set-modulekind-windows.cs
./mono-cecil-set-modulekind-windows.exe myprog.exe

Related

Codegeneration at runtime from a string to console exe is not working in C# .NET6

I have some code which must be able to generated a console application at runtime (Codegeneration with System.CodeDom). I did this already a lot, but in NET 6 now I am struggling with that and the new API. In the code below I try to compile simply from a string. See below the static class with method Start() which then should generates the application.
The compilations seems fine, no errors at the end. But when starting the generated AppCodegenerated.exe, it shows some reference exception with System.Runtime.
Please help, any Idea? Already researched a lot but could not find any useful solution..
//-
I used the Visual Studio 2022 / NET 6 and theses Nuget's:
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System.Text;
namespace CompilerSimplified
{
public static class Compiler
{
public static bool Start()
{
string FileName = "AppCodegenerated";
string ExePath = AppDomain.CurrentDomain.BaseDirectory + #"\" + FileName + ".exe";
string code = #"using System; Console.WriteLine(""Hello.""); Console.ReadLine(); ";
// ------- References -------------
// .net platform references
List<MetadataReference> References = new List<MetadataReference>();
foreach (var item in ReferenceAssemblies.Net60) // ReferenceAssemblies from Nuget: Basic.Reference.Assemblies;
References.Add(item);
// or tried this: loop manually through system platform
//string[] fileEntries = Directory.GetFiles(#"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\6.0.0\ref\net6.0\", "*.dll");
//foreach (string fileName in fileEntries)
// references.Add(MetadataReference.CreateFromFile(fileName));MetadataReference.CreateFromFile(fileName));
// ------- References END -------------
// delete existing file
if (File.Exists(ExePath))
File.Delete(ExePath);
// compiler options
CSharpCompilationOptions DefaultCompilationOptions =
new CSharpCompilationOptions(outputKind: OutputKind.ConsoleApplication, platform: Platform.AnyCpu)
.WithOverflowChecks(true).WithOptimizationLevel(OptimizationLevel.Release);
// encode soucre code
string sourceCode = SourceText.From(code, Encoding.UTF8).ToString();
// CSharp options
var parsedSyntaxTree = Parse(sourceCode, "", CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10));
// compilation
var compilation = CSharpCompilation.Create(FileName, new SyntaxTree[] { parsedSyntaxTree }, references: References, DefaultCompilationOptions);
var result = compilation.Emit(ExePath);
// return
if (result.Success)
return true;
else
return false;
}
private static SyntaxTree Parse(string text, string filename = "", CSharpParseOptions options = null)
{
var stringText = SourceText.From(text, Encoding.UTF8);
return SyntaxFactory.ParseSyntaxTree(stringText, options, filename);
}
}
}
Above code runs fine without error and exports the AppCodegenerated.exe into the project /bin folder.
Execution of this generated AppCodegenerated.exe shows following on the output console:
Unhandled exception: System.IO.FileNotFoundException:
The file or assembly "System.Runtime, Version = 6.0.0.0, Culture = neutral,
PublicKeyToken = b03f5f7f11d50a3a" or a dependency on it was not found.
The system can not find the stated file.
It is not possible to codegenerate directly a console application like the initial approach above. One possible solution is to generate first a dll (what I mentioned above in the example code is working fine), and from there include that .dll into a .exe, from where the functionality can run.

Teamcity - writing tests that access a separate JSON file

I have TeamCity running for a C# project. The Unit tests are written using MSTest and they include an external JSON file. They are loaded in because they're large and I don't want to have to escape them in C#.
I import them like this:
[TestInitialize]
public void Setup()
{
using (StreamReader r = new StreamReader(#".\currency2.json"))
{
_json = r.ReadToEnd();
}
...
They run fine locally. I have 'Copy always set' but when the tests are ran using Teamcity I get an error saying that it can't find them in a temp folder. They are copied over to the build server but they're not in this temp folder.
Could not find file 'E:\TeamCity\buildAgent\temp\buildTmp\SYSTEM_SERVER 2016-07-18 15_28_19\Out\currency2.json'
I have **\bin\release\*test*.dll setup as my Test File Names in the test build step.
Any help appreciated.
I had a similar problem.
I changed the properties of the test file to this
Build Action = Content
Copy to Output Directory = Copy always
Teamcity will copy the file to the build folder, but it does not seem to maintain the same structure you'd expect.
So I created a file lookup loop. That will step down the expected folder until it finds the text file in question.
var pathLookups = new string[]
{
"2ndFolder\\3rdFolder\\test.json", // folder that normally workes
"3rdFolder\\test.json",
"test.json"
};
foreach (var pathLookup in pathLookups)
{
try
{
jsonFileCollection = JsonFileLoader<TestJsonType>.LoadJson(pathLooksup);
if (jsonFileCollection!= null)
{
break;
}
}
catch (Exception)
{
Console.WriteLine("Attempted to load test json from path:" + pathLooksup);
}
}
It's not the cleanest solution, but it will get the job done. You could refactor this to look a little nicer.
You might pass the full pass by argument to your program (and value defined in TeamCity).
Something like this (this is a pseudo-code example only) :
string[] programArgs;
static void Main(string[] args)
{
programArgs = args
}
[TestInitialize]
public void Setup()
{
using (StreamReader r = new StreamReader(programArgs[1]))
{
_json = r.ReadToEnd();
}
...
}

Unity Cloud Build: post export method

Problem:
I can't seem to figure out the right signature for Unity cloud build's post export method. According to the documentation:
The fully-qualified name of a public static method you want us to call
after we finish the Unity build process (but before Xcode). For
example: ClassName.CoolMethod or NameSpace.ClassName.CoolMethod. No
trailing parenthesis, and it can't have the same name as your
Pre-Export method! This method must accept a string parameter, which
will receive the path to the exported Unity player (or Xcode project
in the case of iOS).
Here is my code:
public static void OnPostprocessDevBuildIOS(string ExportPath)
{
var projPath = ExportPath + "/Unity-iPhone.xcodeproj/project.pbxproj";
var proj = new PBXProject();
var nativeTarget =
proj.TargetGuidByName(PBXProject.GetUnityTargetName());
var testTarget =
proj.TargetGuidByName(PBXProject.GetUnityTestTargetName());
string[] buildTargets = {nativeTarget, testTarget};
proj.ReadFromString(File.ReadAllText(projPath));
proj.SetBuildProperty(buildTargets, "ENABLE_BITCODE", "NO");
File.WriteAllText(projPath, proj.WriteToString());
}
and here is the error:
I've tried multiple test method signatures and can't seem to get anything to work. I've even tried just a method that logs out the path.
Additional Information:
Unity Version: 5.3.1f
Unity Cloud Build: 5.3.1f
Target: iOS 8.0+
Also, my cloud build settings script is located in the editor folder as required.
Ok so I got the the bitCode disabling post process to work with the following code, but only when I build manually. When I build from cloud build, with no error the app freezes at the splash screen. When I build from my local machine, the app runs just fine.
[PostProcessBuild]
public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
{
if (buildTarget == BuildTarget.iOS)
{
string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
PBXProject proj = new PBXProject();
proj.ReadFromString(File.ReadAllText(projPath));
string nativeTarget = proj.TargetGuidByName(PBXProject.GetUnityTargetName());
string testTarget = proj.TargetGuidByName(PBXProject.GetUnityTestTargetName());
string[] buildTargets = new string[]{nativeTarget, testTarget};
proj.SetBuildProperty(buildTargets, "ENABLE_BITCODE", "NO");
File.WriteAllText(projPath, proj.WriteToString());
}
}
I too had the same issue "splash screen stuck" right after launch....
I solved this issue. Please use the below code.
Tested in Unity 5.4.1p2 and Xcode 7.3.
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;
using UnityEditor.Callbacks;
#if UNITY_IOS
using UnityEditor.iOS.Xcode;
#endif
public class Postprocessor : AssetPostprocessor
{
#if UNITY_IOS
[PostProcessBuild]
public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
{
if (buildTarget == BuildTarget.iOS)
{
string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
PBXProject proj = new PBXProject();
proj.ReadFromString(File.ReadAllText(projPath));
string target = proj.TargetGuidByName("Unity-iPhone");
proj.SetBuildProperty(target, "ENABLE_BITCODE", "false");
File.WriteAllText(projPath, proj.WriteToString());
// Add url schema to plist file
string plistPath = path + "/Info.plist";
PlistDocument plist = new PlistDocument();
plist.ReadFromString(File.ReadAllText(plistPath));
// Get root
PlistElementDict rootDict = plist.root;
rootDict.SetBoolean("UIRequiresFullScreen",true);
plist.WriteToFile(plistPath);
}
}
#endif
}
In fact OnPostprocessBuild is always called, so you don't have to put anything in post export method field, which is designed for more specific methods.

C# Generating dynamic executable from project

I wanna generate an exe file with some changes in code from another C# exe.
I know that can easy compile .cs single class using CodeDom.Compiler
The thing I want to know is how to compile a project with 'Resources', 'Settings', 'Forms' and other elements.
CSharpCodeProvider.CompileAssemblyFromSource(CompilerParameters, sources[]);
So, the question is where can I add all resources, settings and form (.resx)?
And can I do it with byte[] streams. Without unpacking project's zip.
Sorry for bad English and mby stupid questions. I wish somebody will help me...
For Example: I have byte[] array of resource file 'pic.png' and I wanna attach it to compiled exe as embedded resource.
You should learn about the new compiler service provided by Microsoft in Microsoft.CodeAnalysis code name "Roslyn".
Roslyn provides you the way to compile the code and everything on the fly including creating and compiling complete solution and projects in-memory.
I think what you're looking for can be achieved via Roslyn. See below sample:
class Program
{
static void Main()
{
var syntaxTree = SyntaxTree.ParseCompilationUnit(
#"using System;
using System.Resources;
namespace ResSample
{
class Program
{
static void Main()
{
ResourceManager resMan = new ResourceManager(""ResSample.Res1"", typeof(Program).Assembly);
Console.WriteLine(resMan.GetString(""String1""));
}
}
}");
var comp = Compilation.Create("ResTest.exe")
.AddReferences(new AssemblyNameReference("mscorlib"))
.AddSyntaxTrees(syntaxTree);
var resourcePath = "ResSample.Res1.resources"; //Provide full path to resource file here
var resourceDescription = new ResourceDescription(
resourceName: "ResSample.Res1.resources",
dataProvider: () => File.OpenRead(resourcePath),
isPublic: false);
var emitResult = comp.Emit(
executableStream: File.Create("ResTest.exe"),
manifestResources: new[] { resourceDescription });
Debug.Assert(emitResult.Success);
}
Original Source here
At line dataProvider: () => File.OpenRead(resourcePath), you can provide your own 'FileStream' like () => return _myResourceStream) for your resource file.

Changing the base path of an assembly from the bin directory to a different directory

I have a Test.cs file in C:\ This test file reads from an input file and writes the same to an output file.
Test.cs
public class Test
{
public static int Main(string[] args)
{
var reader = new StreamReader("in.txt");
string input = reader.ReadLine();
var writer = new StreamWriter("out.txt");
writer.WriteLine(input);
return 0;
}
}
Here it should be noted that the code only uses the filename and not the full file path, which means the file is expected to be in the directory where the program is running. And I have created the in.txt in C:\
Now, there is a c# code called Runner.cs in a solution in C:\Project\Runner.cs, that dynamically compiles the Test.cs code and runs it using reflection. Now, when the Test.cs runs, it expects the in.txt file to be in C:\Project\bin\Debug\in.txt , but it is actually present in C:\in.txt
So, my question is, is there a way to make the code to get the file from C:\in.txt and not from the bin directory without changing the path of the file in the Test.cs code file.
Edit: It is my bad that I forgot to mention why I am in need of this requirement.
The Test.cs file comes from over the wire. And I felt it will not be a good choice to edit this file and set the file path accordingly. I want to compile it and run it as it is.
I hope I am clear. If not, please feel free to ask for more information.
If it is as simple as you show in your code switching the CurrentDirectory works for this example:
var mainMembers = new CSharpCodeProvider()
.CreateCompiler()
.CompileAssemblyFromSource(
new CompilerParameters { GenerateInMemory = true }
, #"
using System;
using System.IO;
public class M {
public static int Main() {
Console.WriteLine(""CurDir = ""+ Environment.CurrentDirectory);
var reader = new StreamReader(""in.txt"");
string input = reader.ReadLine();
var writer = new StreamWriter(""out.txt"");
writer.WriteLine(input);
return 0;
}
}")
.CompiledAssembly
.GetType("M")
.GetMember("Main");
// inspect
Environment.CurrentDirectory.Dump("current");
// keep
var oldcd = Environment.CurrentDirectory;
// switch
Environment.CurrentDirectory = "c:\\temp";
// invoke external code
((MethodInfo) mainMembers[0]).Invoke(null,null);
// restore
Environment.CurrentDirectory = oldcd;
In a multi threaded scenario this becomes unreliable.

Categories