Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[Released] Roslyn C# - Runtime C# compiler

Discussion in 'Assets and Asset Store' started by scottyboy805, Mar 27, 2019.

  1. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Would you be able to try a new project and fresh import to rule out any issues with left over assets. Alos if you are able/willing to, you can send us the project to info(at)trivialinteractive.co.uk and we will take a look at what might be happening.
     
  2. alvaro_unity903

    alvaro_unity903

    Joined:
    Jan 20, 2020
    Posts:
    21
    I made a "Reset packages to default" because another problem and it seems fixed. Let me time, because I have 169 third errors now to deal with.
     
  3. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Ok great. Let me know you have any more issues.
     
  4. Crazycarpet

    Crazycarpet

    Joined:
    Dec 5, 2015
    Posts:
    47
    I've noticed that everytime I compile the exact same files in the exact same way the dll file I export is different, even a hash in the generated dll.... I was wondering is this to be expected? Is there any way I can prevent this? The reason I would like it to generate an identical DLL every time is because I'm sending this to clients and I don't want them to have to redownload it if they have a matching DLL already.

    Currently everytime the server is restarted all clients will have to redownload the DLL since it generates a different DLL everytime the server is started (and it is generating a different DLL every time). If this isn't possible I can live with it as well since the DLL file isn't too big and sends quickly, just curious.
     
    Last edited: Jun 1, 2020
  5. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    When you say that the dll is different, I assume you are talking about the generated file name and assembly guid stored in the metadata? The file name is generated by our asset and is specifically designed to to overwrite any existing files so the guid file name will always be unique. If required, you can change the output file name before you call one of the compile methods to choose the output name:

    Code (CSharp):
    1. domain.RoslynCompilerService.OutputName = "MyAssembly";
    As for the metadata guid: I believe the compiler will regenerate this as part of the compile process so that each build is unique. Ie it is intended behaviour. There is a bit more information about that here.
     
  6. Crazycarpet

    Crazycarpet

    Joined:
    Dec 5, 2015
    Posts:
    47
    Hey, sorry I forgot to get back to you because this is not a priority problem for me, but no I mean when I write the dll file using
    Code (CSharp):
    1. stream.Write(result.assemblyImageData, 0, result.assemblyImageData.Length);
    I actually get a different file every time the same source is compiled. Everything works, but I was hoping it might be possible to make this always generate the exact same assemblyImageData given the same inputs.

    So yeah, in short the problem is result.assemblyImageData is different everytime the application runs. (It does produce the desired working assembly, just a different one every time.)

    I don't know enough about what's going on under the hood to know if this is the intended behaviour or not, or if there's a way around it... I figured if anyone would know it'd be you so that is why I am asking here.
     
  7. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi again,
    Thanks for clarifying.
    I believe this is caused by the compiler regenerating the assembly guid on evey build which it is likley doing because there is no specified guid value. Usually in visual studio or similar there would be a special source file located under the properties foldout called 'AssemblyInfo.cs' which contains metadata like assembly tille, version, guid, etc. You sould be able to add a similar source file to your compile request with a fixed guid and hopefully that will cause the same output assmebly every time although I am not completley sure it if would be the same or not. Here is an example assembly info source file:

    Code (CSharp):
    1. using System.Reflection;
    2. using System.Runtime.CompilerServices;
    3. using System.Runtime.InteropServices;
    4.  
    5. // General Information about an assembly is controlled through the following
    6. // set of attributes. Change these attribute values to modify the information
    7. // associated with an assembly.
    8. [assembly: AssemblyTitle("ExampleAssembly")]
    9. [assembly: AssemblyDescription("")]
    10. [assembly: AssemblyConfiguration("")]
    11. [assembly: AssemblyCompany("")]
    12. [assembly: AssemblyProduct("UltimateModdingToolkit")]
    13. [assembly: AssemblyCopyright("Copyright ©  2020")]
    14. [assembly: AssemblyTrademark("")]
    15. [assembly: AssemblyCulture("")]
    16.  
    17. // Setting ComVisible to false makes the types in this assembly not visible
    18. // to COM components.  If you need to access a type in this assembly from
    19. // COM, set the ComVisible attribute to true on that type.
    20. [assembly: ComVisible(false)]
    21. [assembly: Guid("12b8c257-89a2-472b-3e5b-6ca0a6e68d30")]
    22.  
    23. // Version information for an assembly consists of the following four values:
    24. //
    25. //      Major Version
    26. //      Minor Version
    27. //      Build Number
    28. //      Revision
    29. //
    30. // You can specify all the values or you can default the Build and Revision Numbers
    31. // by using the '*' as shown below:
    32. // [assembly: AssemblyVersion("1.0.*")]
    33. [assembly: AssemblyVersion("1.0.0.0")]
    34. [assembly: AssemblyFileVersion("1.0.0.0")]
    Line 21 is what you would want to keep consistent between compiles which will hopefully allow the output to be the same.
     
  8. Crazycarpet

    Crazycarpet

    Joined:
    Dec 5, 2015
    Posts:
    47
    Thank you for the suggestion, I will definitely give this a shot!
     
    scottyboy805 likes this.
  9. Crazycarpet

    Crazycarpet

    Joined:
    Dec 5, 2015
    Posts:
    47
    Do you know of a better way to do this as opposed to creating a file to add to the compilation request? Something like
    ScriptDomain.AddAttribute()
    or similar? My plan is to generate a MD5 hash of the concatenated hashes of the source files and convert that into a GUID to ensure it's the same every time... I was hoping their was a faster way to add the assembly than making a file every time.

    For now I will start implementing it with the file just to test and see if it works.
     
  10. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi again,
    There is no easy way to do this at the moment apart from manually creating the file and adding it to the compile request. We could possibly add an AssemblyInfo type or similar where you can set various assembly properties from code and then generate the script on our end if that could be something useful.
     
  11. Crazycarpet

    Crazycarpet

    Joined:
    Dec 5, 2015
    Posts:
    47
    That could definitely be very useful, but at least in my case only if this actually works in generating consistent output. I'm a bit busy with work right now so I haven't had a chance to test this but I will definitely do so eventually and let you know of the result. As always I appreciate the help.
     
  12. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Ok no problem. Let me know if it fixes your problem and then we can look at adding that feature in to the asset.
     
  13. Crazycarpet

    Crazycarpet

    Joined:
    Dec 5, 2015
    Posts:
    47
    So I managed to make it compile with the same GUID every time but it still produces a different file every time (although now the file contains the GUID I set every time too, there is just other differences). It seems like there's a ton of other GUIDs referenced in the generated DLL file (like 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.dll.pdb') so I'm assuming it'd be a real pain to try and make each compilation match.

    Either way this is a thing I can live with, it just means users who connect to servers will have to download a small clientside assembly everytime the server restarts. This is the price I pay for having the server compile the clientside code files instead of sending them to the client for compilation.

    It was also no hassle at all to just make a temporary file to add the compilation, I don't see it being worth your time adding it to the asset.
     
    Last edited: Jun 25, 2020
  14. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Thanks for the update. Sorry to hear it didn't work as expected and it does indeed sound like it would be too much effort to generate deterministic outputs. It seems like the compiler acutally attempts to prevent this, probably for a good reason so if you have a work around then that is probably best. Let me know if there is anything else I can help with.
     
    Crazycarpet likes this.
  15. Crazycarpet

    Crazycarpet

    Joined:
    Dec 5, 2015
    Posts:
    47
    Is it possible
    ScriptAssembly::LoadAssembly(string dllPath, ScriptSecurityMode securityMode);
    is leaving a file handle opened? I seem to be having an issue where once I call it I can no longer modify the file even after I unload the assembly. Also, should it even keep the file locked til the ScriptAssembly that is returned is deleted? If this is the case it's possible I'm not unloading the assembly properly and will look into it deeper.

    Getting the 1224 or whatever it is error that means 'file in use by another program'.
    What I am doing is: 1) Loading a DLL file and compiling it. | 2) Unloading the script assembly | 3) Edit the DLL file before reloading and compiling it.
    That's why I'm thinking I'm either messing up step #2 or 'LoadAssembly' is leaving a file handle opened.
     
  16. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    This is actually intended behaviour of the CLR. when loading an assembly from file (which is what happens during that method call) the assembly file is locked until the application exits. There is also no way to unload assemblies once they have been loaded into the Unity app domain. You would have to destroy the app domain instead which ofcourse is not possible because all your game scripts would also be destroyed. There is a way around the file locking issue though by loading the assembly from memory:

    Code (CSharp):
    1. byte[] assemblyBytes = File.ReadAllBytes("Example/Assembly.dll");
    2.  
    3. scriptDomain.LoadAssembly(assemblyBytes, ScriptSecurityMode.UseSettings);
    Let me know if you have any more questions.
     
    Crazycarpet likes this.
  17. Crazycarpet

    Crazycarpet

    Joined:
    Dec 5, 2015
    Posts:
    47
    I think that's it, everything seems to be working and I think I'm finally done implementing Roslyn C# into my project. As always, thanks for the support.

    Great asset, great support, extremely easy to use. I appreciate your comments as they helped me improve the overall design of my implementation by helping me understand what's going on under the hood and hopefully anyone who runs into the same situations will be-able to find them helpful too.
     
    scottyboy805 likes this.
  18. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Glad to be of assistance. Let me know if there is anything else I can help you with.
     
  19. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Roslyn C# version 1.3.2 has been submitted to the asset store. This version adds further checks to the code security verification engine to prevent the usage of pinvoke. Using the blacklisting feature to disallow 'System.Runtime.InteropServices' or the 'DllImportAttribute' will not prevent pinvoke code from running as brought to our attentiion by a user. The reason for this is that the compiler will detect the DllImport attribute and emit pinvoke IL instructions and the attribute will be stripped allowing the code to slip though security verification. Further explicit pinvoke checks will now run to prevent external code from being called. There is also a new option in the settings window to reflect this 'Allow PInvoke' which we recommend remains disbaled.

    settings.png
    DLLImportUsage.png
    PInvokeError.png
     
    Last edited: Aug 31, 2020
  20. Saafris

    Saafris

    Joined:
    Nov 22, 2017
    Posts:
    5
    Hello

    Whenever I try to set the Roslyn settings, it resets to default any time I change the codebase. I do not want to have to select my preset every time I make a code change.

    I cannot find in the documentation how to add a blacklisted namespace through code, or how to load settings from a preset through code. How do I do these things?
     
  21. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    Could you let me know which settings you are trying to change and the version of Unity you are using and we will look into the settings issue.

    All settings can be accessed at runtime though the 'RoslynCSharp' type:

    Code (CSharp):
    1. RoslynCSharp.Settings...
    To blacklist a namespace at runtime, you could use the following code:

    Code (CSharp):
    1. RoslynCSharp.Settings.SecurityRestrictions.NamespaceReferences.AddEntryName("System.IO.*", CodeSecurityRestrictions.CodeSecurityBehaviour.Deny);
     
  22. Saafris

    Saafris

    Joined:
    Nov 22, 2017
    Posts:
    5
    I'm not sure I understand.

    I'm using Roslyn through code with ScriptDomain, adding reference assemblies, (hopefully) adding security, and then compiling and loading files.

    Code (CSharp):
    1. var domain = ScriptDomain.CreateDomain("ExampleName", true);
    2.             domain.RoslynCompilerService.ReferenceAssemblies.Add( AssemblyReference.FromNameOrFile(". . ."));
    3.  
    4. ScriptAssembly assembly = domain.CompileAndLoadFiles(new[]
    5.             {
    6.                 ". . ."
    7.             });
    How do I add the Roslyn settings to this flow, or change the flow to be more correct? I wasn't able to use your above code, I had to adjust it to:

    Code (CSharp):
    1.  
    2. var roslyn = new RoslynCSharp.RoslynCSharp();
    3. roslyn.SecurityRestrictions.NamespaceReferences.AddEntryName("System.IO.*", CodeSecurityRestrictions.CodeSecurityBehaviour.Deny);
    4.  
    But I'm not sure how to make sure that applies.

    As for unity version, 2020.1.8f1
     
  23. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    I am not exactly sure what you want to do.
    The settings that you modify via the settinw window are auto-loaded and applied to the compiler when creating your script domain via 'CreateDomain'. You can then use the 'RoslynCompilerService' property of the domain to access the compiler if you need to add additional references via code.

    If you want to access the settings that are assigned from the settings window then you can use the following code at runtime:

    Code (CSharp):
    1. RoslynCSharp.Settings
    That will load and return the active RoslynCSharp settings for the game.
     
  24. Saafris

    Saafris

    Joined:
    Nov 22, 2017
    Posts:
    5
    Ah, I see, it was unclear that editing those settings would affect my created domain. I assumed I was meant to pass it in, or modify the domain somehow.
     
  25. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    The settings are global to all script domains arnd are applied automatically when creating the domain as mentioned. If you need to change the settings after a domain has been created but still want to apply those changes, you can use the following method:

    Code (CSharp):
    1. class Example : MonoBehaviour
    2. {
    3.     void Start()
    4.     {
    5.         // Create a domain as usual
    6.         ScriptDomain domain = ScriptDomain.CreateDomain("Example", true);
    7.      
    8.         // Change some settings
    9.         RoslynCSharp.Settings.AllowUnsafeCode = true;
    10.         RoslynCSharp.Settings.AllowOptimizeCode = true;
    11.      
    12.         // Apply the modified settings to the compiler
    13.         domain.ApplyCompilerServiceSettings();
    14.     }
    15. }
     
  26. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Roslyn C# version 1.3.3 has been submitted to the asset store. This version includes:
    • Fixed a bug in the code security engine where wildcard namespaces would not be handled correctly.
    If you have any feature requests or feedback for the asset then we would be happy to hear it :).
     
    Last edited: Nov 16, 2020
  27. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    IL2CPP Update

    We have been working on IL2CPP support on and off for a little while now but I can finally share a working proof of concept that allows you to compile and execute C# source code at runtime on IL2CPP platforms, including WebGL.

    We have uploaded a WebGL demo of the included mouse maze game where you write code to control the movement direcition of a mouse in order to escape the maze. You can find the demo here.
    demo.png

    Important
    This is a proof of concept demo only at the moment and by no means complete, or even close to being production ready. Many aspects of executable code are partially implement or not implemented at all so you may enounter some errors when tyring to run different code. For example: Exception handlers are not implemented so there may be unexpected behaviour when throwing exceptions. We will continue to work on this to support as many featues as possible.

    How does it work?
    As you may already know, IL2CPP does not support loading of external assemblies such as the ones produced by the roslyn compiler which our asset uses. This is because all C# code is compiled ahead of time into C++ code and calls such as 'AppDomain.Load' can only work with assemblies that were compiled as part of this AOT process. The only feasable way to get around this limitation was to create an emulator for CIL bytecode which is exactly what we did. We created a CIL bytecode interpreter in pure C# code that fully supports the object model including types, members, generics ect. We use Mono.Cecil to read assembly definitions and extract the object metadata as well as method instruction sets to fully replicate the reflection system. IE. We have created our own implementation of System.Type, MethodInfo, FieldInfo etc. and you can use this replicated system as you would expect. For example, Invoking a method using the interpreter can be done like so:

    Code (CSharp):
    1. // Create an app domain where interpreted types/code will exist. Note this is not a 'System.AppDomain' instance
    2. AppDomain domain = new AppDomain();
    3.  
    4. // Replcement for 'Assembly.Load' or 'System.AppDomain.Load'
    5. CLRModule module = domain.LoadModuleStream(...);
    6.  
    7. // Find type with specified name
    8. Type t = module.GetRuntimeType("MyExampleType");
    9.  
    10. // Find method declared on type with specified name
    11. MethodBase method = t.GetMethod("MyExampleMethod", BindingFlags.Instance | BindingFlags.Public);
    12.  
    13. // Allocate a new instance of type 't'
    14. object instance = domain.CreateInstance(t);
    15. object someArgument = "ExampleString";
    16.  
    17. // Invoke an instance method
    18. method.Invoke(instance, new object[]{ someArgument});

    We will continue to work on this project in our spare time and update this thread with any news.

    IL2CPP project is renamed to dotnow-interpreter and is now open source on github under the MIT license: https://github.com/scottyboy805/dotnow-interpreter
     
    Last edited: Dec 19, 2021
  28. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Roslyn C# 1.4.0 has been submitted to the asset store for review. This version includes:
    • Added new feature - Assembly Reference assets
    • Ammended demo game to use assembly reference assets.
    • Fixed a bug in the compiler where passing a null IMetadateReferenceProvider would thrown an exception instead or handling it properly.
    • Removed 'System.Collections' from the code security namespace blacklist (Added in error as part of the previous udpate) which could cause the demo game to fail code sedcurity verification.
    Assembly Reference Assets
    Assembly reference assets are a new feature that we have added to make it easier and more robust to add references to other pre-compiled assemblies. As the name suggests, these references are implemented as Unity assets and you will need one asset per assembly that you wish to reference. You can create these assets by right clicking in the project window and selecting 'Create -> Rosyn C# -> Assembly Reference Asset'.

    You can then select an assembly file to reference, or select an already loaded assembly in the current AppDomain and the asset is ready to use.

    AssemblyReferenceAsset.png

    The asset works by storing the raw assembly image data as an embedded resource meaning that the assembly reference will be available on all platforms, even if the file path is unreachable. The asset will also auto-update its embedded image whenever it detects a change to the source assembly file. This means that it is now much easier to support platforms such as Android where external file access can cause issues.

    Once you have setup a valid assembly reference asset, you can provide it to the compiler in one of two ways.

    Once when your game starts up:


    Code (CSharp):
    1. using UnityEngine;
    2. using RoslynCSharp;
    3.  
    4. public class Example : MonoBehaviour
    5. {
    6.     // Assign in inspector
    7.     public AssemblyReferenceAsset referenceAsset;
    8.  
    9.     void Start()
    10.     {
    11.         ScriptDomain domain = ScriptDomain.CreateDomain("Example");
    12.        
    13.         domain.RoslynCompilerService.ReferenceAssemblies.Add(referenceAsset);
    14.     }
    15. }
    On a per compile basis:

    Code (CSharp):
    1. using UnityEngine;
    2. using RoslynCSharp;
    3.  
    4. public class Example : MonoBehaviour
    5. {
    6.     string sourceCode = ...;
    7.  
    8.     // Assign in inspector
    9.     public AssemblyReferenceAsset referenceAsset;
    10.  
    11.     void Start()
    12.     {
    13.         ScriptDomain domain = ScriptDomain.CreateDomain("Example");
    14.  
    15.         domain.CompileAndLoadMainSource(sourceCode, ScriptSecurityMode.UseSettings, new IMetadataReferenceProvider[]{
    16. referenceAsset });
    17.     }
    18. }

    Let us know if there are any other features you would like to see in the future.
     
    Yavvn likes this.
  29. ldlework

    ldlework

    Joined:
    Jan 22, 2014
    Posts:
    46
    I am trying to use AssemblyReferenceAsset but I am finding that the name, path and image fields are never saved to the asset file on disk. And so reloading the project, I find the asset details are empty each time.
     
  30. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    Thanks for reporting this problem. Changing the file path seems to work OK forus but could you try the following to see if it makes a difference:

    Open the source file at 'Assets/RoslynCSharp/Scripts/Runtime/AssemblyReferenceAsset.cs' and find the method named 'UpdateAssemblyReference' at around the line 60 mark. Then add the following code to the bottom of that method body:

    Code (CSharp):
    1. #if UNITY_EDITOR
    2.             UnityEditor.EditorUtility.SetDirty(this);
    3. #endif
    Let me know if that changes anything or whether the problem persists.
     
  31. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Roslyn C# 1.4.1 has been submitted to the asset store for review. This version drops support for older Unity versions so that we can offer better compatibility with newer unity versions. This means that we can now elminate the compilation errors on import that some users may have experienced if using newer unity versions/certain packages.
     
  32. benzy54n

    benzy54n

    Joined:
    Dec 21, 2013
    Posts:
    34
    This looks really cool. I have a question:

    1) With this package/structure can I somehow reference an external function as a static function pointer that I can pass around my program? Specifically I would like to have the user define the function outside the game and then I execute using the unity Jobs system. This means I need to remove all overhead of this function being tied to some object and being static I can use a function pointer within the jobs system.
     
  33. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    Glad you like the look of the asset :).

    Once you have compiled and loaded external code using our asset, you can then use reflection as you would normally to access static methods (or instance methods too if needed). Once you have a MethoInfo object for a particular external method, you can then invoke it using the reflection API, or even create a delegate from it so that you can call it directly. Is this the sort of function pointer you are refering to? or do you mean a native address to the JIT compiled method?
     
  34. benzy54n

    benzy54n

    Joined:
    Dec 21, 2013
    Posts:
    34
    So fairly good success:

    I was able to get a Unity.Burst function pointer working and working in the unity Jobs architecture. So super happy about that. The timing of it was a bit slower than not using a function pointer with the external script but I was extremely happy with it. I originally tried using MoonSharp w/ LUA.

    My Timing is as follows:

    ~4.3 million function calls.

    ~35ms No external script with the static function compiled in with the unity project. Using 6 cores Unity Job system w/ Unity.Burst
    ~260ms No external script with the static function compiled in with the unity project. Using 6 cores Unity Job system no Unity.Burst
    ~3000ms W/ Roslyn C external script being compiled at runtime and main project using function pointer. Using 6 cores Unity Job system no Unity.Burst
    ~300ms W/ Roslyn C external script being compiled at runtime and main project using function pointer. Using 6 cores Unity Job system w/ Unity.Burst
    ~17000ms W/ Moonsharp Lua external script being compiled at runtime and main project using function pointer. Using 6 cores Unity Job system no Unity.Burst. Cannot use BURST w/ Moonsharp. Its also very shaky with multiple threads.

    The fact that I get near normal time with Roslyn w/ Burst is what I was looking for.

    This is how I had to setup the function pointer to work with Burst and pass it around to Jobs.

    Code (CSharp):
    1.  
    2. public delegate int RosylnFunctionDelegate(int input);
    3. public void DoRosylnTest()
    4.     {
    5.         ScriptDomain domain = null;
    6.         const string sourceCode = @"
    7.        class Example
    8.        {
    9.            static int ExampleMethod(int input)
    10.            {
    11.                return input + 4;
    12.            }
    13.        }";
    14.  
    15.         // Create domain
    16.         domain = ScriptDomain.CreateDomain("Example Domain");
    17.  
    18.         // Compile and load code - Note that we use 'CompileAndLoadMainSource' which is the same as 'CompileAndLoadSource' but returns the main type in the compiled assembly
    19.         ScriptType type = domain.CompileAndLoadMainSource(sourceCode, ScriptSecurityMode.UseSettings);
    20.  
    21.         MethodInfo myFunc = type.FindCachedMethod("ExampleMethod", true);
    22.  
    23.         if (myFunc == null)
    24.         { Debug.Log("Did not find Method");}
    25.         else
    26.         { Debug.Log("Found Method");}
    27.  
    28.         FunctionPointer<RosylnFunctionDelegate> functionPointer = new FunctionPointer<RosylnFunctionDelegate>(Marshal.GetFunctionPointerForDelegate(myFunc.CreateDelegate(typeof(RosylnFunctionDelegate))));
    29.  
    30.         int result = functionPointer.Invoke(5);
    31.  
    32.         Debug.Log(result.ToString());
    33.  
    34.     }
     
    Kirsche likes this.
  35. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi again,
    That is great news.
    I guess the overhead of the delegate is the only thing slowing you down slightly as opposed to a direct method call. It sounds like you are happy with the performance though so not a big issue :)
     
  36. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Roslyn C# version 1.4.2 has been submitted to the asset store for review. This version adds many assembly reference assets for most of Unity assembly modules and also some common modules such as 'mscorlib'. We have also added a helper guide for Android platforms, although it is still not an officially supported platform.
     
  37. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Android Update

    Android platforms are not officially supported. However, a number of users have been able to get the asset to work well on Android platforms, and were kind enough to report to us the steps they took to achieve this. The following section will list some crucial steps to get the asset up and running on Android:

    Requirements

    The most important factor for Android platforms is that Assembly Reference Assets must be used (Requires version 1.4.0 and newer). This is because assembly reference assets allow you to reference assemblies when compiling code without requiring file paths, which can be an issue on Android. Roslyn C# includes a number of Assembly Reference Assets that you can use which can be found at ‘Assets/RoslynCSharp/AssemblyReferences/’. You can also create your own reference assets for any other assembly you may like to use.

    Configuration

    There is a little bit of setup required for Android platforms. First, open the Roslyn C# settings window by going to ‘Tools -> Roslyn C# -> Settings’ and preforming the following:
    1. Enable the option named ‘Generate In Memory’ if it is not already. This will cause the compiler to emit assemblies in memory streams as opposed to file streams and is quite important.
    2. Remove All default compiler references that are displayed in the ‘References’ collection. These assembly references are auto-added to the compiler via file path only, which can cause issues on Android. If you need to reference any of these assemblies in your external code, then you can add an equivalent Assembly Reference Asset. Take a look at the ‘Assembly References’ section for information on how to create a reference asset, or alternatively, you can use an already existing reference asset if one exists.
    3. Remove All In-code reference expressions such as:
    Code (CSharp):
    1. AssemblyReference.FromNameOrFile(...)
    2. AssemblyReference.FromAssembly(...)
    3.  
    These reference expressions will attempt to reference assemblies via file paths, which can be an issue. The following expressions are OK since they do not use file paths:

    Code (CSharp):
    1. AssemblyReference.FromStream(...) // Cannot be FileStream
    2. AssemblyReference.FromImage(...)
    3.  
    Hopefully by following this mini guide, you can get the asset running on your target Android platform.
     
  38. benzy54n

    benzy54n

    Joined:
    Dec 21, 2013
    Posts:
    34
    I have a scenario where I am defining structs in external scripts to be used at runtime. When I try to access that externally how do I treat it as a struct and not a class? Seems like documentation is dealing with classes quite a bit.
     
  39. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    Yes, the documentation only really discusses classes as it is what most users will need. With that said, the exact same approach can be used for struct types and things will work just fine. There is nothing special you need to do to work with structs. Structs are still represented by the ScriptType object, and all the API's still apply (CreateInstance, SafeCall, SafeCallStatic, Fields[..], Properties[...] etc). The only real difference will be the runtime behaviour (value type instead of reference type) and that you cannot treat them as a Unity object.

    I hope this helps you. Let me know if there is anything else.
     
  40. nycucumber

    nycucumber

    Joined:
    Feb 16, 2014
    Posts:
    12
    hi, @scottyboy805

    just download the asset yesterday, very useful and does exactly what I need.

    Initially, the runtime-loaded script complains that it cannot find any class defined by me. It only has access to Unity's system and core assemblies. I solved this by adding an Assembly Definition in the folder that has all my own classes, essentially created a new assembly that enclosed all my own script. And at the end added the assembly that has my own code into a list of assembly reference and loads the list of references in the domain.

    But I realize this is not an optimal route as whenever I need to access a non-system nor unity assembly (3rd party assets) from my own scripts. My IDE throws our errors telling me they cannot find references to those 3rd party classes.

    I am by no means a seasoned dev but my gut feeling is that I should not have to add my own script to a new assembly to make it work. Can you tell me how to fix this?
     
  41. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    If you want to allow external compiled code to use you game scripts, then you will need to add an additional assembly reference. If you are not using assembly definitions in you project, then your game scripts will be compiled by Unity into an assembly named ‘Assembly-CSharp’. You can simply create a new assembly reference asset that targets this assembly and everything should then work as expected. It should be displayed under the assembly reference menu that is shown when you click ‘Select Loaded Assembly’.
    You can also use assembly definitions if that would be easier. The included example game has uses assembly definitions to allow access to the game code so it might be worth taking a look at how that is setup.

    I hope this helps you. Let me know if you are still having issues.
     
  42. nycucumber

    nycucumber

    Joined:
    Feb 16, 2014
    Posts:
    12
    Thank you. Turns out what I needed is just to add "Assembly-CSharp" into the reference list.
    Well resolved!
     
    scottyboy805 likes this.
  43. Yavvn

    Yavvn

    Joined:
    May 8, 2019
    Posts:
    18
    I know it’s asking a lot but any updates or progress on your insanely cool IL2CPP project? I would love to help out whenever it reaches beta stages, and you could do an opinion poll but I know I would be more than willing to pay the price of this asset again just for IL2CPP support. It’s a lot of work and I’d love to support that. Keep up the amazing work regardless.
     
    scottyboy805 likes this.
  44. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    @Yavvn Good timing. We were just about ready to give another update on IL2CPP.

    Read update #1 here.

    IL2CPP Update #2

    We have been working on and off on our side project to support IL2CPP platforms and have made some progress since the last update. Most of the msil instructions have now been implemented (atleast, the ones we plan to support) meaning that most standard C# code can run just fine after it has been compiled by Roslyn C#. We have also managed to get generics working for the most part meaning that the use of generic collections and custom generic types are working OK. Nested generics will need much more testing as we have not really touched those yet.

    We have also been working on an integration aspect so that the system can plug straight into our Roslyn C# asset, and we have just about finished that. Here is the code that the maze crawler example uses to run code in interpreter mode:

    Code (CSharp):
    1. // Compile and load the code
    2.                     ScriptAssembly asm = domain.CompileAndLoadSourceInterpreted(cSharpSource);
    3.  
    4.                     // Find the maze crawler type that the user created
    5.                     ScriptType gameType = asm.FindSubTypeOf<MazeCrawler>();
    6.  
    7.                     // Simple error message
    8.                     if (gameType == null)
    9.                         throw new Exception("Maze crawler code does not define a class that inherits from 'MazeCrawler");
    10.  
    11.                     // Create instance
    12.                     ScriptProxy instance = gameType.CreateInstance(mazeMouse);
    13.  
    14.                     instance.Fields["breadcrumbPrefab"] = breadcrumbPrefab;
    15.                     instance.Fields["moveSpeed"] = mouseSpeed;
    It should look pretty standard to most Rosyn C# users which is the aim. We want users to use an almost identical API as the main Rosyn C# asset so that there is no new learning curve. The only slight change you might notice is that a new compile method named 'CompileAndLoadSourceInterpreted' is used instead. This is all that is required to load source code into an interpreted environment. You can then use the ScriptType/ScriptProxy system as normal to find types, create instances, set field and propert values, invoke methods etc.

    There is a bit of special sauce going on in the background to make this work. The main one is that the standard unity 'AddComponent' method cannot be used (As would be expected), and infact, calling that method and passing an interpreted type would cause a hard crash of the editor. Instead we create our own add component method which hooks into the runtime seamlessly:

    Code (CSharp):
    1. [CLRMethodBinding(typeof(GameObject), "AddComponent", typeof(Type))]
    2.         public static object AddComponentOverride(AppDomain domain, MethodInfo overrideMethod, object instance, object[] args)
    3.         {
    4.             // Get instance
    5.             GameObject go = instance as GameObject;
    6.  
    7.             // Get argument
    8.             Type componentType = args[0] as Type;
    9.  
    10.             // Check for clr type
    11.             if (componentType.IsCLRType() == false)
    12.             {
    13.                 // Use default unity behaviour
    14.                 return go.AddComponent(componentType);
    15.             }
    16.  
    17.             // Handle add component manually
    18.             Type proxyType = domain.GetCLRProxyBindingForType(componentType.BaseType);
    19.  
    20.             // Validate type
    21.             if (typeof(MonoBehaviour).IsAssignableFrom(proxyType) == false)
    22.                 throw new InvalidOperationException("A type deriving from mono behaviour must be provided");
    23.  
    24.             // Create proxy instance
    25.             ICLRProxy proxyInstance = (ICLRProxy)go.AddComponent(proxyType);
    26.  
    27.             // Create clr instance
    28.             return domain.CreateInstanceFromProxy(componentType, proxyInstance);
    29.         }
    You will never need to see or use this code directly, but it is important that it exists. The 'CLRMethodBinding' attribute binds this method to the standard 'AddComponent' in the runtime app domain, meaning that all calls to 'GameObject.AddComponent' will be re-routed to this method that we provide. We then do some necessary safety checks and construct component instances that are usable by our runtime environment.

    You may notice the 'GetCLRProxyBindingForType' call, which is another thing that we needed to add to support inheritance of types defined in non-interpreted assemblies. Basically, anytime you need to inherit from a type that exists outside the scope of the current assembly (such as MonoBehaviour), you will need to create an inheritance binding.it is not ideal but it is the only possible way to allow inheritance to work.
    Lets say we have the following MonoBehaviour script that we want external code to derive from:

    Code (CSharp):
    1. public abstract class ExampleBehaviour : MonoBehaviour
    2. {
    3.     public abstract int DoSomething(string someString);
    4. }
    We would need to create the following inheritance binding, so that the abstract method can be invoked on the base type, but call the method declared in the derived type (In external code):

    Code (CSharp):
    1. [CLRProxyBinding(typeof(ExampleBehaviour))]
    2. public class ExampleBehaviourProxy : ExampleBehaviour, ICLRProxy
    3. {
    4.     TrivialCLR.AppDomain domain;
    5.     TrivialCLR.CLRInstance instance;
    6.  
    7.     public void InitializeProxy(TrivialCLR.AppDomain domain, TrivialCLR.CLRInstance instance)
    8.     {
    9.         this.domain = domain;
    10.         this.instance = instance;
    11.     }
    12.  
    13.  
    14.     MethodInfo doSomethingMethod = null;
    15.     public override int DoSomething(string someString)
    16.     {
    17.         if(doSomethingMethod == null)
    18.             doSomethingMethod = instance.Type.GetMethod(nameof(DoSomething), BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
    19.          
    20.         return (int)doSomethingMethod.Invoke(instance, new object[]{ someString });
    21.     }
    22. }
    Hopefully you can get an idea of how that works and why it is required. We did hope to find a nicer solution but after extensive experimentation, it appeard to be the only viable approach. Hopefully in the future, we could create a simple tool to generate these bindings automatically for a set of give types that could be derived from.

    It is probably as good a time as ever to mention some of the limitations this system will inevitably have:
    1. Performance (Obviously :)) - The runtime is interpreting msil instructions in software with no JIT compilation, so it is definitley going to run much slower than your games code, probably anywhere from 10-50x slower depending upon the operations. We have not measured timings or even optimized any aspects of the system yet. We just want to get something working up and running before we wory about that.
    2. Unsafe code is not supported - There are just too many hurdles with this to overcome so its not an option at the moment.
    3. PInvoke is not supported - This would also require alot of work to support due to marshalling etc. It is not worth our time to persue.
    4. Propbably some more that I can't think of at the moment.
    That's all for now. We will continue to work on this as and when we can. Hopefully we can get something usable out for beta testing in the next couple of months. The testing in real world use cases would be valuable.

    IL2CPP project is renamed to dotnow-interpreter and is now open source on github under the MIT license: https://github.com/scottyboy805/dotnow-interpreter
     
    Last edited: Dec 19, 2021
    buzmez_simbt and Yavvn like this.
  45. BeastBomber24

    BeastBomber24

    Joined:
    Jun 10, 2019
    Posts:
    5
    Everything works fine except for when i try to run code that gets a component from gameobject it says "The type or namespace name 'Main' could not be found (are you missing a using directive or an assembly reference?)"
     
  46. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    I assume that the type named 'Main' is one of your game types defined in your Unity project? If that is the case, then you will simply need to add a new assembly reference to allow external code access to these types. There are a few ways to do this but the simplets would be to do the following:
    1. Open the settings window by going to 'Tools -> Roslyn C# -> Settings'
    2. Find the list view labeled 'References' and click the '+' (Add) button to add a new reference to the collection.
    3. An input dialog will be displayed where you can enter the following assembly name (assuming you are not using assembly defintiions) 'Assembly-CSharp.dll'
    4. Re-test your game and you should see that the compile errors are now gone and you can access the deisred types.
    If you are still having issues, let me know as there are a few other reasons this can occur.

    I hope this helps you.
     
  47. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    We have just setup a discord server for Roslyn C# and a few other assets of ours so that we can offer better and faster support. We would love for you to come and join and hang our with other users of the asset and maybe share any cool projects you are working on.
     
    Last edited: Mar 21, 2021
  48. BeastBomber24

    BeastBomber24

    Joined:
    Jun 10, 2019
    Posts:
    5
    Still not working here is the source script im trying to run


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Examples : MonoBehaviour
    6. {
    7.  
    8.     private GameObject _player;
    9.     private GameObject _main;
    10.  
    11.     public void OnEnabled()
    12.     {
    13.         foreach (GameObject obj in Object.FindObjectsOfType(typeof(GameObject)))
    14.         {
    15.             if(obj.name == "Player")
    16.             {
    17.                 _player = obj;
    18.             }
    19.  
    20.             if(obj.name == "main")
    21.             {
    22.                 _main = obj;
    23.             }
    24.         }
    25.  
    26.         _main.transform.GetComponent<Main>().gravity = -1f;
    27.  
    28.     }
    29.  
    30. }
     
  49. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    OK, no problem.
    Here are a few other things to check:
    1. Is the 'Main' type marked as public? It will need public visibilty in order to be accessible because your external code will be compiled into a different assembly.
    2. Does the 'Main' type exist inside a namespace? If so, you will need to add a using statement to the top of your external code such as 'using MyTypeNamespace'. I would expect this to generate a different error message though so probably not the cause of the problem.
    3. Are you using assembly definitions inside your project? If so, then you will need to add an assembly reference to your assembly definition output name. You can do this again via the settings window, using assembly reference assets or though code if you prefer.
    Let me know if you still cannot get it working as expected.
     
  50. BeastBomber24

    BeastBomber24

    Joined:
    Jun 10, 2019
    Posts:
    5
    This is all main is. It just holds game variables
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Main : MonoBehaviour
    6. {
    7.  
    8.     public float seaLevel, gravity;
    9.     public int dayLastingTime;
    10.     public string[] idNames;
    11.     public Sprite[] idImages;
    12.     public GameObject[] idPrefabs;
    13.  
    14.     public string[] structureIdNames;
    15.     public GameObject[] structureIdPrefabs;
    16.     public Sprite[] structureIdImages;
    17.     public craftingRecipes[] craftingRecipes;
    18.  
    19. }