Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
First and formost, I am not a native english speaker, sorry for any communication errors this question might emit. Also, couldn't really understand some of your guidelines for asking a question, so followed what I understood of it. As a consequence, please tell me if I violate any custom.
background
I have been making an app that trys to implement something like xaml hot reload for code. As I can't directly make it with compiled languages like c#, I decided I will rely on a third-party scripting language, thinking I could write a bit of my app logic in it, then deploy it faster to the device because I would only need to press a button to send the code across. So, one can think of it like the expo client in the react-native world.
I decided to use lua as the scripting language, not only because it's very simple to write code in, but also because it has a nice binding for .net, I'm talking about Nlua, of course. And, as ironpython3 is not ready for production yet, what choices do I have honestly?
the problem I am facing
So, as I have to bind some xamarin.essentials stuff to make it easier for me to write the lua side of the app, I have to interface with some objects that have, among many other things, static methods. I thought it would be very intuitive and straight-forward to use static methods in Nlua, though it seemns I was mistaken so far.
how to reproduce the issue
So, in visual studio, create a xamarin.forms project, name it however you like, it doesn't matter.
Next, add the Nlua nuget package to all your projects. It needs to be integrated in all of them because it, like some xamarin.forms plugins, relies on native libraries to work, so they need to be included in the proper places for the OS to find them. This is automatically done by the package when installed in a supported project type, so do that.
Add the Nlua namespace to the top of the codebehind of your MainPage content page, like this:
using NLua;
For easy use going forward, add a class level property to the MainPage class, like this:
Lua LuaState{get; set;}
Now, initialise it in the page constructor, for simplicity's sake:
LuaState = new Lua();
In the xaml of the page, add an edit field in which to input the text and a button to be able to run it:
<StackLayout>
<Editor
x:Name="edCode"
Placeholder="type your code here"
/>
<Button
x:Name="btnStartCode"
Text="compile"
HorizontalOptions="End"
VerticalOptions="EndAndExpand"
Clicked="btnStartCode_Clicked"
/>
</StackLayout>
In the button clicked handler, add code to load the things in the edit box, then make nlua execute it, something like this:
async void btnStartCode_Clicked(object sender, EventArgs e)
{
try
{
LuaState.DoString(edCode.Text);
}
catch (Exception exc)
{
await DisplayAlert("error", $"A fatal error was incountered while running your code\nException details:\n\tException type: {exc.GetType().ToString()}\n\terror message from interpreter:{exc.Message}.", "OK");
}
}
Now, add a new class, call it however you want, I called mine tts. Delete everything inside the file vs generated, then add the following content in it:
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Essentials;
namespace autoaccess.scriptables
{
public class tts
{
public async void speak(params object[] messages)
{
foreach(var message in messages)
{
if (message == null)
{
await TextToSpeech.SpeakAsync("null value");
return;
}
await TextToSpeech.SpeakAsync(message.ToString());
}
}
}
}
Back in the main content class, either in the constructor or in a dedicated method called from the constructor, register the lua type, like this:
LuaState["text_to_speech"=typeof(TextToSpeech);
expected behaviour
It should speak the params given to the function if using . notation, e.g tts.speak("hello world")
actual behaviour
When a call with . notation is executed, the lua interpreter throws the following error:
[string "chunk"]:1: attempt to call a nil value (field 'speak').
what I tryed so far
First, I tried to get the type of the static object with the typeof keyword, then registering it directly as a lua accessible value, like I have shown above. This threw an error, as you could see.
If, for example, I try to invoke the type like a function to get a new object like shown in the documentation, it fails again.
If I use the State.UseClrPackage(); method, then importing the stuff manually, it doesn't work either, can't access any of the methods or variables, lua says they're all "nill".
Note: Upon closer inspection, I see the variable lua sees is actually the real clr type, since the str function of lua shows the full type qualifier, as present in .net. However, the type function returns userdata.
Instead of making the entire type be visible to lua as it is, I tried to make a lua table with the name, then add key-value pairs where the key is the method name, and the value is a static object's method, as returned by typeof(thing).GetMethod(MethodName) cast to the LuaFunction type.
This worked to a certain point, though it quickly grue too complicated and overwhelmingfor me, so I stopped using that approach, as my codebase grue very intangled.
Another thing I tryed is to make proxi objects for static ones in which nothing is actually static, even though the methods return void and are acting on static properties.
That, again, kind of worked, but I then necessarely had to use the object:thing, when the norm for static methods is object.thing. If I tryed to use the object.thing notation, I got a pretty long error, though I'm sure it's content is not relevant to the problem at hand, so not going to include it here.
conclusion
So, officially, I tryed everything I could, even did a search on here to see if I would be able to find anything to enlighten me, though no luck, unfortunately. Now, what do you recommend I use? Is there something I overlooked somewhere?
Related
I am very new at this, I have no idea what I'm actually doing and currently the fact that what I "created" works is nothing more than a miracle to me.
Put simply, I was playing among us (look at me go..), a mod was made for it (town of us, you might have heard of it, I dunno), I tried it out and it was pretty cool, I wanted to make some additions/edits to it so I did, it all worked out, perfectly (which.. as I say, a miracle)
However due to the nature of it, it sort of becomes a pain for me to share this version with the people using the original since that means I'm making a separate version and I'd rather not pull away from the original, what I did however requires a bunch of what already exists.
Is there a way for me to create a separate dll which just adds my edits as a separate thing (in a way where it just sort of, puts them into the existing file) while it still pulls from the original one to get the information it needs
For example there's a list of things like this in the original
public static float RecentKill => Generate.RecentKill.Get();
public static bool DetectiveReportOn => Generate.DetectiveReportOn.Get();
public static float DetectiveRoleDuration => Generate.DetectiveRoleDuration.Get();
public static float DetectiveFactionDuration => Generate.DetectiveFactionDuration.Get();
and what I want to do is make it so when my plugin is loaded it just kind of.. adds my entry to it
(I would need to do this with multiple different things but I could probably figure that out if I know if what I currently want to do is actually possible.
So.. is this possible and if so how? (What I have tried currently just insists to me that there's conflicts and I sort of get that)
Please note this is like.. the first thing I've every actually attempted to do this with so I have no full idea of limitations and such
(Super sorry if this makes no sense, as I say, I'm new to any of this and I'm still surprised what I did in the first place even works)
I've deleted my first attempt at asking this question, as I wasn't quite up to speed on the semantics. Now I'm not much better. Basically here's the issue. I have a small block of online demo code that I'm analyzing that is showing how to access assembly-related functions in VB from C#. Basically loading and executing an assembly in C# using CallbyName to VB functions. Here is the relevant code, with all variables defined:
// the only non-default usings for this code are:
using Microsoft.VisualBasic;
using System.Reflection;
private void Form1_Load(object sender, EventArgs e)
{
string exefile = Properties.Resources.binaryfile;
// Convert base64 to bytes
byte[] exe_bytes = Convert.FromBase64String(exefile);
// okay, the load call takes as an argument the byte array exe_bytes,
// this makes sense
object loaded = Interaction.CallByName(AppDomain.CurrentDomain, "load",CallType.Method, exe_bytes);
// next, the entrypoint is taken from the loaded assembly, and
// passed to the object entry
object entry = Interaction.CallByName(loaded, "entrypoint", CallType.Get);
// finally, invoke is called to execute it, being passed the
// entrypoint. But what are the null, null for? This seems
// to be what is throwing the runtime error.
object invocation = Interaction.CallByName(entry, "invoke", CallType.Method, null, null);
Okay, let me stop here to note a few things. This builds successfully in C# (.NET 4.5), but throws a runtime error for parameter mismatch at the invocation line. It seems to me from looking at the code that someone just put that into three separate lines (load,entrypoint,invoke) to demonstrate what they were doing, as we could substitute object definitions to create one line. I add and subtract "nulls" from the 2 listed, and the error message remains, either saying it doesn't take 3 arguments, or that I erroneously have no arguments.
In my previous ask of the question a few hours ago, a helpful post suggested that I (quoting) "load the assembly (Reflection.Assembly.Load) and then inspect the parameters on the Assembly.EntryPoint property (MethodInfo) by calling its GetParameters method. Once you know what you are dealing with, then you can play with CallByName to start it." (credit to user TnTinMn)
So after about two hours of reading online docs with sparse examples, I've come up with only this line:
object loaded = System.Reflection.Assembly.Load(exe_bytes);
to replace the loaded definition in the code. After that, I have absolutely no idea what I am reading or doing! Assembly.Entrypoint, GetParameters, MethodInfo, oh my! Basically, I just want "how many parameters do you need? please print to console!" translated into C# .NET OO speak. Because I am utterly lost. Thank you!
The above seemed a good start, but alternative courses to a solution are certainly welcome. If you can, please please provide code in your answer, as my ability translating tasks from concept to OO programming is noticeably deficient.
EDIT: This may be relevant for examining the number of parameters?
MethodInfo[] methods = BUT_WHAT_OBJECT_GOES_HERE.GetMethods();
foreach (MethodInfo info in methods)
{
Console.WriteLine(info.Name);
}
In the example I took the code from, the object for the method GetMethods was a Program class. I tried using the assembly object exefile and it won't take it.
Having spent the last few days reading everything I can find about C# reflection on COM objects, trying many experiments in code, and analyzing sample code to try and improve my understanding, I am now forced to admit that I just don't know enough, so I am requesting help from the Community.
I need to be able to access and update the properties of a late bound COM object that is wrapped as System._COM Object.
I tried all the standard refection stuff without success and I looked through using IDispatch, but I'm not comfortable with using the pointers involved, so I'm hoping I have missed something pretty simple in the normal interface. I found papers on MSDN that DO show how to do what I need, but all the examples are in C++ and it is over my head.
It would be really helpful if someone could explain why the following simple C# code just doesn't work as expected:
try
{
// late binding:
// localCB is a COM object (System._COMObject) created by Activator.CreateInstance() from
// the ProgID of a registered COM .DLL.
//
// The original .DLL has a string PROPERTY called
// "TESTEXTERNAL1". localCB has an IDispatch Interface. The original COM .DLL has a separate Typelib,
// and, although I did not register the typelib separately, I can see from OLEView on the COM object
// that the GUID for the typelib is included in it.
// Here's the code that is puzzling me...
var vv = localCB.GetType().InvokeMember("TESTEXTERNAL1", BindingFlags.GetProperty,
null, localCB, null);
string rt = vv.ToString();
// works exactly as expected and returns the value of TESTEXTERNAL1 - OK.
// now try and update the SAME PROPERTY, on the SAME COM object...
Parameters = new Object[1];
Parameters[0] = "Hello, World!";
localCB.GetType().InvokeMember("TESTEXTERNAL1", BindingFlags.SetProperty,
null, localCB, Parameters);
// throws an (inner) exception: HRESULT 0x8002003 DISP_E_MEMBERNOTFOUND !!!
}
catch (Exception xa)
{
string xam = xa.Message;
}
Is it unreasonable to expect an object that has already found and provided a property, to be able to update the same property? Is there some "alternative update" strategy that I am not aware of?
Many thanks for any help,
Pete.
UPDATE:
in response to Jon's request, here are snippets of the OleView:
(I had to use images because Oleview would not let me cut & paste, sorry...)
OleView of the COM .DLL
OLEView typelib view
Jon, I think you have correctly identified that the problem is with a setter method. The DLL is written in Fujitsu COBOL and provides an "under the covers" GET and SET for fields identified as PROPERTY. Accessing the COM component from C# or COBOL, it works fine, but, as you can see, it doesn't work when I try and access it for SET with reflection. Because I am unfamiliar with using reflection I was doubtful whether I had the syntax right, so I tried to make the SET as close as possible to the GET. I think I will need to generate my own SET methods (for each PROPERTY) into the COBOL and then change my "BindingFlags.SetProperty" to be "BindingFlags.InvokeMember". (I did the homework on BindingFlags and found that if you specify "SetProperty" it automatically implies the other 2 flags you mentioned.)
I think the key to it all is in recognizing that the problem is with the Fujitsu *COM Class SET, and it took your experienced eye to see that. Many thanks. If you have any other comments after seeing the OLEView, or can suggest any alternative approach in order to get the properties set, I'd be very interested. (I'm not looking forward to having to generate SETter methods for every property; it smacks of brute force... :-))
Thanks again,
Pete.
Hans was correct. The problem was with the setter method. I have written code to generate a setter for each of the properties, back in the original COBOL COM component. It wasn't as tedious or ugly as I thought it would be (about 7 lines of COBOL for each PROPERTY) and it is all working very well now. Many thanks to the community and particularly Hans Passant for support.
The question in short is: How do you reference a second script containing reusable script code, under the constraints that you need to be able to unload and reload the scripts when either of them changes without restarting the host application?
I'm trying to compile a script class using the CS-Script "compiler as service" (CSScript.Evaluator), while referencing an assembly that has just been compiled from a second "library" script. The purpose is that the library script should contain code that can be reused for different scripts.
Here is a sample code that illustrates the idea but also causes a CompilerException at runtime.
using CSScriptLibrary;
using NUnit.Framework;
[TestFixture]
public class ScriptReferencingTests
{
private const string LibraryScriptCode = #"
public class Helper
{
public static int AddOne(int x)
{
return x + 1;
}
}
";
private const string ScriptCode = #"
using System;
public class Script
{
public int SumAndAddOne(int a, int b)
{
return Helper.AddOne(a+b);
}
}
";
[Test]
public void CSScriptEvaluator_CanReferenceCompiledAssembly()
{
var libraryEvaluator = CSScript.Evaluator.CompileCode(LibraryScriptCode);
var libraryAssembly = libraryEvaluator.GetCompiledAssembly();
var evaluatorWithReference = CSScript.Evaluator.ReferenceAssembly(libraryAssembly);
dynamic scriptInstance = evaluatorWithReference.LoadCode(ScriptCode);
var result = scriptInstance.SumAndAddOne(1, 2);
Assert.That(result, Is.EqualTo(4));
}
}
To run the code you need NuGet packages NUnit and cs-script.
This line causes a CompilerException at runtime:
dynamic scriptInstance = evaluatorWithReference.LoadCode(ScriptCode);
{interactive}(7,23): error CS0584: Internal compiler error: The invoked member is not supported in a dynamic assembly.
{interactive}(7,9): error CS0029: Cannot implicitly convert type '<fake$type>' to 'int'
Again, the reason for using CSScript.Evaluator.LoadCode instead of CSScript.LoadCode is so that the script can be reloaded at any time without restarting the host application when either of the scripts changes. (CSScript.LoadCode already supports including other scripts according to http://www.csscript.net/help/Importing_scripts.html)
Here is the documentation on the CS-Script Evaluator: http://www.csscript.net/help/evaluator.html
The lack of google results for this is discouraging, but I hope I'm missing something simple. Any help would be greatly appreciated.
(This question should be filed under the tag cs-script which does not exist.)
There is some slight confusion here. Evaluator is not the only way to achieve reloadable script behavior. CSScript.LoadCode allows reloading as well.
I do indeed advise to consider CSScript.Evaluator.LoadCode as a first candidate for the hosting model as it offers less overhead and arguably more convenient reloading model. However it comes with the cost. You have very little control over reloading and dependencies inclusion (assemblies, scripts). Memory leaks are not 100% avoidable. And it also makes script debugging completely impossible (Mono bug).
In your case I would really advice you to move to the more conventional hosting model: CodeDOM.
Have look at "[cs-script]\Samples\Hosting\CodeDOM\Modifying script without restart" sample.
And "[cs-script]\Samples\Hosting\CodeDOM\InterfaceAlignment" will also give you an idea how to use interfaces with reloading.
CodeDOM was for years a default CS-Script hosting mode and it is in fact very robust, intuitive and manageable. The only real drawback is the fact that all object you pass to (or get from) the script will need to be serializable or inherited from MarshalByRef. This is the side effect of the script being executed in the "automatic" separate domain. Thus one have to deal with the all "pleasures" of Remoting.
BTW this is the only reason why I implemented Mono-based evaluator.
CodeDOM model will also automatically manage the dependencies and recompile them when needed. But it looks like you are aware about this anyway.
CodeDOM also allows you to define precisely the mechanism of checking dependencies for changes:
//the default algorithm "recompile if script or dependency is changed"
CSScript.IsOutOfDateAlgorithm = CSScript.CachProbing.Advanced;
or
//custom algorithm "never recompile script"
CSScript.IsOutOfDateAlgorithm = (s, a) => false;
The quick solution to the CompilerException appears to be not use Evaluator to compile the assembly, but instead just CSScript.LoadCode like so
var compiledAssemblyName = CSScript.CompileCode(LibraryScriptCode);
var evaluatorWithReference = CSScript.Evaluator.ReferenceAssembly(compiledAssemblyName);
dynamic scriptInstance = evaluatorWithReference.LoadCode(ScriptCode);
However, as stated in previous answer, this limits the possibilities for dependency control that the CodeDOM model offers (like css_include). Also, any change to the LibraryScriptCode are not seen which again limits the usefulness of the Evaluator method.
The solution I chose is the AsmHelper.CreateObject and AsmHelper.AlignToInterface<T> methods. This lets you use the regular css_include in your scripts, while at the same time allowing you at any time to reload the scripts by disposing the AsmHelper and starting over. My solution looks something like this:
AsmHelper asmHelper = new AsmHelper(CSScript.Compile(filePath), null, false);
object obj = asmHelper.CreateObject("*");
IMyInterface instance = asmHelper.TryAlignToInterface<IMyInterface>(obj);
// Any other interfaces you want to instantiate...
...
if (instance != null)
instance.MyScriptMethod();
Once a change is detected (I use FileSystemWatcher), you just call asmHelper.Dispose and run the above code again.
This method requires the script class to be marked with the Serializable attribute, or simply inherit from MarshalByRefObject.
Note that your script class does not need to inherit any interface. The AlignToInterface works both with and without it. You could use dynamic here, but I prefer having a strongly typed interface to avoid errors down the line.
I couldn't get the built in try-methods to work, so I made this extension method for less clutter when it is not known whether or not the interface is implemented:
public static class InterfaceExtensions
{
public static T TryAlignToInterface<T>(this AsmHelper helper, object obj) where T : class
{
try
{
return helper.AlignToInterface<T>(obj);
}
catch
{
return null;
}
}
}
Most of this is explained in the hosting guidelines http://www.csscript.net/help/script_hosting_guideline_.html, and there are helpful samples mentioned in previous post.
I feel I might have missed something regarding script change detection, but this method works solidly.
I am attempting to build (for learning purposes) my own event logger; I am not interested in hearing about using a non-.net frameworks instead of building my own as I am doing this to better understand .net.
The idea is to have an event system that I can write out to a log file and/or pull from while inside the program. To do this I am creating an LogEvent class that will be stored inside of a Queue<LogEvent>.
I am planning on using the following fields in my LogEvent class:
private EventLogEntryType _eventType //enum: error, info, warning...
private string _eventMessage
private Exception _exception
private DateTime _eventTime
What I am not sure is the best way to capture the object that caused the event to be called. I thought about just doing a private Object _eventObject; but I am thinking that is not thread safe or secure.
Any advice on how to best store the object that called the event would be appreciated. I am also open to any other suggestions you may have.
Thanks, Tony
First off, nothing wrong with writing your own. There are some good frameworks our there, but sometimes you reach the point where some bizarre requirement gets you rolling your own, I've been there anyway...
I don't think you should be using text messages. After doing this type of logging in several projects, I have come the the conclusion that the best approach is to have a set of event types (integer IDs) with some type of extra information field.
You should have an enum of LogEvetTypes that looks something like this:
public enum LogEventTypes
{
//1xxx WS Errors
ThisOrThatWebServiceError = 1001,
//2xxx DB access error
//etc...
}
This, from my experience will make your life much easier when trying to make use of the information you logged. You can also add an ExtraInformation field in order to provide event instance specific information.
As for the object that caused the event, I would just use something like typeof(YourClass).ToString();. If this a custom class you created, you can also implement a ToString override that will name sense in your logging context.
Edit: I am adding several details I wrote about in the comments, since I think they are important. Passing objects, which are not immutable, by ref to service methods is generally not a good idea. You might reassigne the same variable in a loop (for example) and create a bug that is near-impossible to find. Also, I would recommend doing some extra work now to decouple the logging infrastructure from the implementation details of the application, since doing this later will cause a lot of pain. I am saying this from my own very painful experience.