Need to replace a constant of another application with a new value using the dnlib library, so that when you run the application the new value will be displayed in place of the old (default)
Stub class:
StubDnlib namespace
{
using System;
public static class MyClass
{
public const string READ = "Console";
public const int MyConst = 10;
public static void InizializeTest()
{
Console.WriteLine($"Const: {READ} - default = Console");
Console.WriteLine($"Const: {MyConst} - default = 10");
}
}
}
Builder class:
using var module = ModuleDefMD.Load(Resources.StubDnlib); // StubDnlib - stub class (exe)
foreach (var type in module.GetTypes())
{
foreach (FieldDef field in type.Fields)
{
if (field.HasConstant && field.ElementType == ElementType.String)
{
field.Constant.Value = "newValue";
}
}
}
module.Write("Edited.exe");
The value of the constant changes, but if I run the application, it shows the old value and the constant breaks
I looked into DnSpy and looked at the values:
Console.WriteLine("Const: Console - default = Console");
public const string READ = "newValue";
Instead of "Const: {READ}" the value of "Console" was substituted, but not READ =(
Related
I have two classes:
public class Stats : MonoBehaviour
{
// Primary Stats
public int strength;
public int agility;
public int intellect;
public int stamina;
public int spirit;
}
and
public class EquipmentProperties : ItemProperties
{
public Stats stats;
}
public enum Stats
{//variables from "Stats" class to be in this enum
}
I am trying to get all the variables from the Stats class to be in the stats enum without having to manually enter them..
"I am trying to get all the variables from the Stats class to be in the stats enum without having to manually enter them"
Enums must be specified at compile time, you can't dynamically add enums during run-time. If you would like to stablish the fields of your enum dynamically with your class variables, guess that because the Stats class might change along the app's development, you would need to store that enum somewhere, because if not you would need to access the fields of the dynamic enum according to that generic way of setting the enumeration, in a kind of meta-programming templated way that would not make much sense.
So along with your question comes the question of how to store that enum to be used afterwards I guess. For that you can check EnumBuilder class.
Extending that example, you can build the enum according to the specific Stats class like this:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
public class Stats
{
// Primary Stats
public int strength;
public int agility;
public int intellect;
public int stamina;
public int spirit;
}
class Example
{
public static List<string> getFields(Type type) {
var propertyValues = type.GetFields();
var result = new Stats[propertyValues.Length];
var retStr = new List<string>();
for (int i = 0; i < propertyValues.Length; i++) {
retStr.Add(propertyValues[i].Name);
}
return retStr;
}
public static void Main() {
// Get the current application domain for the current thread.
AppDomain currentDomain = AppDomain.CurrentDomain;
// Create a dynamic assembly in the current application domain,
// and allow it to be executed and saved to disk.
AssemblyName aName = new AssemblyName("TempAssembly");
AssemblyBuilder ab = currentDomain.DefineDynamicAssembly(
aName, AssemblyBuilderAccess.RunAndSave);
// Define a dynamic module in "TempAssembly" assembly. For a single-
// module assembly, the module has the same name as the assembly.
ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");
// Define a public enumeration with the name "Elevation" and an
// underlying type of Integer.
EnumBuilder eb = mb.DefineEnum("Stats", TypeAttributes.Public, typeof(int));
int fieldCount = 0;
getProperties(typeof(Stats)).ForEach(field => {
eb.DefineLiteral(field, fieldCount);
fieldCount++;
});
// Define two members, "High" and "Low".
//eb.DefineLiteral("Low", 0);
//eb.DefineLiteral("High", 1);
// Create the type and save the assembly.
Type finished = eb.CreateType();
ab.Save(aName.Name + ".dll");
foreach (object o in Enum.GetValues(finished)) {
Console.WriteLine("{0}.{1} = {2}", finished, o, ((int)o));
}
Console.ReadLine();
}
}
Output:
Stats.strength = 0
Stats.agility = 1
Stats.intellect = 2
Stats.stamina = 3
Stats.spirit = 4
Prolog
This is of course not what you are asking directly since it is not automatic but I would suggest a Dictionary<Stats, int> and do e.g.
public class StatsComponent : MonoBehaviour
{
// Make these only assignable via the Inspector
[SerializeField] private int strength;
[SerializeField] private int agility;
[SerializeField] private int intellect;
[SerializeField] private int stamina;
[SerializeField] private int spirit;
public readonly Dictionary<Stats, int> stats = new Dictionary<Stats, int>();
private void Awake ()
{
// Initialize once with values from the Inspector
stats.Add(Stats.Strength, strength);
stats.Add(Stats.Agility, agility);
stats.Add(Stats.Intellect, intellect);
stats.Add(Stats.Stamina, stamina);
stats.Add(Stats.Spirit, spirit);
}
}
public enum Stats
{
Strength,
Agility,
Intellect,
Stamina,
Spirit
}
Of course there are ways to automatize that via reflection but I'm sure it will bring you more headaches and issues then it is solving - that's only an opinion of course.
Intermediate Solution
If you don't want to type things twice you could instead of an enum rather go by index or strings e.g. using SerializedDictionary you could simply have a
public SerializedDictionary<string, int> stats;
and fill it in the Inspector and not have your fields at all.
Enum Generator Window
However, if you still want at least automation with a minimal effort to build on top of this answer I made this a tool EditorWindow you can directly use within Unity.
Just place this script anywhere in your project.
#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
public class EnumGeneratorWindow : EditorWindow
{
// This is of course optional but I thought it makes sense to filer for a specific field type
private enum FieldType
{
Int,
Float,
Bool,
// more could of course be added
}
private MonoScript sourceScript;
private MonoScript targetScript;
private FieldType fieldType;
private Type GetFieldType()
{
return fieldType switch
{
FieldType.Int => typeof(int),
FieldType.Float => typeof(float),
FieldType.Bool => typeof(bool),
// according to the enum add more cases
_ => null
};
}
[MenuItem("Window/ENUM GENERATOR")]
private static void Init()
{
var window = GetWindow<EnumGeneratorWindow>();
window.Show();
}
private void OnGUI()
{
EditorGUILayout.LabelField("ENUM GENERATOR", EditorStyles.boldLabel);
sourceScript = EditorGUILayout.ObjectField("Source", sourceScript, typeof(MonoScript), false) as MonoScript;
if (!sourceScript)
{
EditorGUILayout.HelpBox("Reference the script where to fetch the fields from", MessageType.None, true);
return;
}
var sourceType = sourceScript.GetClass();
if (sourceType == null)
{
EditorGUILayout.HelpBox("Could not get Type from source file!", MessageType.Error, true);
return;
}
targetScript = EditorGUILayout.ObjectField("Target", targetScript, typeof(MonoScript), false) as MonoScript;
if (!targetScript)
{
EditorGUILayout.HelpBox("Reference the script where write the generated enum to", MessageType.None, true);
return;
}
if (targetScript == sourceScript)
{
EditorGUILayout.HelpBox("The source and target script should probably rather not be the same file ;)", MessageType.Error, true);
return;
}
var targetType = targetScript.GetClass();
if (targetType == null)
{
EditorGUILayout.HelpBox("Could not get Type from target file!", MessageType.Error, true);
return;
}
fieldType = (FieldType)EditorGUILayout.EnumPopup("Field Type", fieldType);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
var fields = sourceType.GetFields().Where(f => f.FieldType == GetFieldType()).Select(f => f.Name).ToArray();
var fileContent = new StringBuilder("public enum ").Append(targetType.Name).Append(" { ");
for (var i = 0; i < fields.Length; i++)
{
if (i != 0)
{
fileContent.Append(", ");
}
fileContent.Append(fields[i]);
}
fileContent.Append(" }");
EditorGUILayout.LabelField(fileContent.ToString());
var color = GUI.color;
GUI.color = Color.red;
GUILayout.BeginVertical();
{
EditorGUILayout.LabelField("! DANGER ZONE !", EditorStyles.boldLabel);
EditorGUILayout.Space();
if (GUILayout.Button("GENERATE ENUM"))
{
var targetID = targetScript.GetInstanceID();
// e.g. Assets/SomeFolder/MyStats.cs
var targetAssetPath = AssetDatabase.GetAssetPath(targetID);
// just as a safety net
if (EditorUtility.DisplayDialog("Generate and overwrite with enum?", $"Attention\n\nThis will overwrite any content of {targetAssetPath} with the new content.\n\nAre you sure?", "Yes generate", "OMG NO! Cancel this!"))
{
// a bit of a hack but we need to convert the Unity asset path into a valid system path by erasing one duplicate "Assets"
var pathParts = targetAssetPath.Split('/').ToArray();
// overwrite the "Assets" with the full path to Assets
pathParts[0] = Application.dataPath;
// re-combine all path parts but this time use the according system file path separator char
var targetSystemPath = Path.Combine(pathParts);
// Write the content into the file via the normal file IO
File.WriteAllText(targetSystemPath, fileContent.ToString());
// trigger a refresh so unity re-loads and re-compiles
AssetDatabase.Refresh();
}
}
}
GUILayout.EndVertical();
GUI.color = color;
}
}
#endif
How it works:
Open the window via the header menu -> Window -> ENUM GENERATOR
Drag in your MonoBehaviour script into "Source"
Create a new empty script with the enum name you like
Drag your enum target script into "Target"
Select the type of fields we are looking for
Finally hit generate
Here is a little demo ;)
I just start of using Example.cs
public class Example : MonoBehaviour
{
public int someInt, anotherInt;
public float someFloat, anotherFloat, andOnMore;
public bool someBool, yetAnotherBool;
}
and ExampleEnum.cs
public enum ExampleEnum
{
}
I have a project called FoodPantryGlobal, which contains all of the constants I will use.
I have another project called RegisterInput which uses FoodPantryGlobal.
In FoodPantryGlobal is an array called CityNames[], that RegisterInput needs to use in order to load a Combobox.
The problem I am having is CityNames[] seems to be empty to RegisterInput when called.
If I declare a local array, and use that every thing works fine.
I am betting it is something simple that I am missing.
FoodPantryGlobal has been added to the resources.
using System;
The code I am using for the Global.
namespace FoodPantryGlobals
{
public class GlobalConst
{
public const string excelFileName = "Food Pantry Registry.xlsm";
public const int cityNamesMembers = 4;
public readonly string[] cityNames = new string[cityNamesMembers] {"Hollans", "Roanoke", "Salem", "Vinton" };
}
public class GlobalProcedures
{
}
}
The code making trying to use CityNames.
using FoodPantryGlobals;
private void CboxCity_GotFocus(Object sender, EventArgs e)
{
const int members = GlobalConst.cityNamesMembers;
//string[] cityNames = new string[members] { "Hollans", "Roanoke", "Salem", "Vinton" };
//string city = FoodPantryGlobals.GlobalConst.cityNames[0];
int index = 0;
while (index < members)
{
string city = GlobalConst.cityNames[index];
CboxCity.Items.Add(city);
index = index + 1;
}
}
Just make cityNames static
public static readonly string[] cityNames = new string[] { "Hollans", "Roanoke", "Salem", "Vinton" };
and IMHO you can use foreach
foreach (var city in GlobalConst.cityNames) CboxCity.Items.Add(city);
or maybe even better
CboxCity.Items.AddRange( GlobalConst.cityNames);
maybe you should clear previous items before adding new ones
CboxCity.Items.Clear();
I created a new class called DashObject, then immediately created an example DashObject. But when I try to access it, the IDE says it doesn't exist.
I'm trying to manage Amazon Dash devices using this class, but I can't even access them once defined for some reason.
class DashObject
{
// Definitions
string DashName;
string DashIdentifier;
int DashFunction;
string DashFunctionInfo;
// Constructor
public DashObject(string dashName, string dashIdentifier, int dashFunction, string dashFunctionInfo)
{
DashName = dashName;
DashIdentifier = dashIdentifier;
DashFunction = dashFunction;
DashFunctionInfo = dashFunctionInfo;
}
}
//Create example DashObject
DashObject example = new DashObject("Example", "44650DFD85E9", 1, "prsl");
//Attempt to access example (this line causes build to fail)
MessageBox.Show(example.DashName);
Make sure you have a public or internal access modifier on it. Change your code to:
public class DashObject
{
// Definitions
public string DashName;
public string DashIdentifier;
public int DashFunction;
public string DashFunctionInfo;
// Constructor
public DashObject(string dashName, string dashIdentifier, int dashFunction, string dashFunctionInfo)
{
DashName = dashName;
DashIdentifier = dashIdentifier;
DashFunction = dashFunction;
DashFunctionInfo = dashFunctionInfo;
}
}
in order to access the field you must set it to public or internal:
public string DashName;
or
internal string DashName;
There seems to be a bug / inconsistency in the Microsoft XmlSerializer: If you have a property marked with a System.ComponentModel.DefaultValue attribute, this does not get serialized. Fair enough - this could be seen as an expected behavior.
The problem is the same attribute is not respected when deserializing. The code below illustrates the issue.
Question is how could I bypass this? I have potentially hundreds of business classes with default values used in the UI tier (Views), so default value initialization in constructor is not an option. It has to be something generic. I could create a completely new default attribute, but it seems like duplicate work. Do you see a way to override the XmlSerializer behavior or should I use just another serializer that does the job better?
The example code:
public class DefaultValueTestClass
{
[System.ComponentModel.DefaultValue(10000)]
public int Foo { get; set; }
}
[TestMethod]
public void SimpleDefaultValueTest()
{
// Create object and set the property value TO THE DEFAULT
var before = new DefaultValueTestClass();
before.Foo = 10000;
// Serialize => xml
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(DefaultValueTestClass));
string xml;
using (var stream = new System.IO.StringWriter())
{
serializer.Serialize(stream, before);
xml = stream.ToString();
}
// Deserialize the same object
DefaultValueTestClass after;
using (var reader = new System.IO.StringReader(xml))
{
after = (DefaultValueTestClass)serializer.Deserialize(reader);
}
// before.Foo = 10000
// after.Foo = 0
Assert.AreEqual(before.Foo, after.Foo);
}
It is your job to implement the defaults; [DefaultValue] merely says "this is the default, you don't need to worry about this" - it doesn't apply it. This applies not just to XmlSerializer, but to the core System.ComponentModel API to which [DefaultValue] belongs (which drives things like the bold / not-bold in PropertyGrid, etc)
Basically:
public class DefaultValueTestClass
{
public DefaultValueTestClass()
{
Foo = 10000;
}
[DefaultValue(10000)]
public int Foo { get; set; }
}
will work in the way you expect. If you want it to serialize whether or not it is that particular value, then the correct implementations is:
public class DefaultValueTestClass
{
public DefaultValueTestClass()
{
Foo = 10000;
}
public int Foo { get; set; }
}
If you want to preserve the [DefaultValue], but want it to always serialize, then:
public class DefaultValueTestClass
{
[DefaultValue(10000)]
public int Foo { get; set; }
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public bool ShouldSerializeFoo() { return true; }
}
Where ShouldSerialize* is another pattern from System.ComponentModel that is recognised by several serializers.
And here's some UI code to show that XmlSerializer is actually doing exactly the same things that the UI code (built on System.ComponentModel) has always done:
using System.ComponentModel;
using System.Windows.Forms;
static class Program
{
[System.STAThread]
static void Main()
{
Application.EnableVisualStyles();
using (var form = new Form())
using (var grid = new PropertyGrid())
{
grid.Dock = DockStyle.Fill;
var obj = new DefaultValueTestClass
{ // TODO - try with other numbers to
// see bold / not bold
Foo = 10000
};
// note in the grid the value is shown not-bold; that is
// because System.ComponentModel is saying
// "this property doesn't need to be serialized"
// - or to show it more explicitly:
var prop = TypeDescriptor.GetProperties(obj)["Foo"];
bool shouldSerialize = prop.ShouldSerializeValue(obj);
// ^^^ false, because of the DefaultValueAttribute
form.Text = shouldSerialize.ToString(); // win title
grid.SelectedObject = obj;
form.Controls.Add(grid);
Application.Run(form);
}
}
}
public class DefaultValueTestClass
{
[System.ComponentModel.DefaultValue(10000)]
public int Foo { get; set; }
}
I'm trying to write a function in C# that takes in a string containing typescript code and returns a string containing JavaScript code. Is there a library function for this?
You can use Process to invoke the compiler, specify --out file.js to a temporary folder and read the contents of the compiled file.
I made a little app to do that:
Usage
TypeScriptCompiler.Compile(#"C:\tmp\test.ts");
To get the JS string
string javascriptSource = File.ReadAllText(#"C:\tmp\test.js");
Full source with example and comments:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
try
{
// compiles a TS file
TypeScriptCompiler.Compile(#"C:\tmp\test.ts");
// if no errors were found, read the contents of the compile file
string javascriptSource = File.ReadAllText(#"C:\tmp\test.js");
}
catch (InvalidTypeScriptFileException ex)
{
// there was a compiler error, show the compiler output
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
public static class TypeScriptCompiler
{
// helper class to add parameters to the compiler
public class Options
{
private static Options #default;
public static Options Default
{
get
{
if (#default == null)
#default = new Options();
return #default;
}
}
public enum Version
{
ES5,
ES3,
}
public bool EmitComments { get; set; }
public bool GenerateDeclaration { get; set; }
public bool GenerateSourceMaps { get; set; }
public string OutPath { get; set; }
public Version TargetVersion { get; set; }
public Options() { }
public Options(bool emitComments = false
, bool generateDeclaration = false
, bool generateSourceMaps = false
, string outPath = null
, Version targetVersion = Version.ES5)
{
EmitComments = emitComments;
GenerateDeclaration = generateDeclaration;
GenerateSourceMaps = generateSourceMaps;
OutPath = outPath;
TargetVersion = targetVersion;
}
}
public static void Compile(string tsPath, Options options = null)
{
if (options == null)
options = Options.Default;
var d = new Dictionary<string,string>();
if (options.EmitComments)
d.Add("-c", null);
if (options.GenerateDeclaration)
d.Add("-d", null);
if (options.GenerateSourceMaps)
d.Add("--sourcemap", null);
if (!String.IsNullOrEmpty(options.OutPath))
d.Add("--out", options.OutPath);
d.Add("--target", options.TargetVersion.ToString());
// this will invoke `tsc` passing the TS path and other
// parameters defined in Options parameter
Process p = new Process();
ProcessStartInfo psi = new ProcessStartInfo("tsc", tsPath + " " + String.Join(" ", d.Select(o => o.Key + " " + o.Value)));
// run without showing console windows
psi.CreateNoWindow = true;
psi.UseShellExecute = false;
// redirects the compiler error output, so we can read
// and display errors if any
psi.RedirectStandardError = true;
p.StartInfo = psi;
p.Start();
// reads the error output
var msg = p.StandardError.ReadToEnd();
// make sure it finished executing before proceeding
p.WaitForExit();
// if there were errors, throw an exception
if (!String.IsNullOrEmpty(msg))
throw new InvalidTypeScriptFileException(msg);
}
}
public class InvalidTypeScriptFileException : Exception
{
public InvalidTypeScriptFileException() : base()
{
}
public InvalidTypeScriptFileException(string message) : base(message)
{
}
}
}
Perhaps you could use a JavaScript interpreter like JavaScriptDotNet to run the typescript compiler tsc.js from C#.
Something like:
string tscJs = File.ReadAllText("tsc.js");
using (var context = new JavascriptContext())
{
// Some trivial typescript:
var typescriptSource = "window.alert('hello world!');";
context.SetParameter("typescriptSource", typescriptSource);
context.SetParameter("result", "");
// Build some js to execute:
string script = tscJs + #"
result = TypeScript.compile(""typescriptSource"")";
// Execute the js
context.Run(script);
// Retrieve the result (which should be the compiled JS)
var js = context.GetParameter("result");
Assert.AreEqual(typescriptSource, js);
}
Obviously that code would need some serious work. If this did turn out to be feasible, I'd certainly be interested in the result.
You'd also probably want to modify tsc so that it could operate on strings in memory rather than requiring file IO.
The TypeScript compiler file officially runs on either node.js or Windows Script Host - it is written in TypeScript itself (and transpiled to JavaScript). It requires a script host that can access the file system.
So essentially, you can run TypeScript from any language as long as you can wrap it in a script engine that supports the file system operations required.
If you wanted to compile TypeScript to JavaScript purely in C#, you would end up writing a C# clone of the compiler.