JoshClose / CsvHelper

Library to help reading and writing CSV files
http://joshclose.github.io/CsvHelper/
Other
4.74k stars 1.06k forks source link

Can't build in Unity #1390

Open oOtroyOo opened 4 years ago

oOtroyOo commented 4 years ago

I've import dll from example Untiy project. Version : Unity 2017.4.15 with .NET46 It's works in Editor . But can't pass build . Maybe because the dlls requeires net45 or netstandard I reprogrammed the CsvHelper project with Net4.6 and disabled System.ValueTuple . Reimported to Unity ,then it works.

JoshClose commented 4 years ago

I believe the netstandard2.0 version should work. Try that one.

wiverson commented 4 years ago

I'm getting this same error with Unity whenever I try to build with ILL2CPP.

InvalidOperationException: No public parameterless constructor found. at CsvHelper.ReflectionHelper.CreateInstanceDelegate (System.Type type, System.Object[] args) [0x00000] in <00000000000000000000000000000000>:0 at CsvHelper.ReflectionHelper.CreateInstanceWithoutContractResolver (System.Type type, System.Object[] args) [0x00000] in <00000000000000000000000000000000>:0 at System.Func3[T1,T2,TResult].Invoke (T1 arg1, T2 arg2) [0x00000] in <00000000000000000000000000000000>:0 at CsvHelper.Configuration.Configuration.AutoMap (System.Type type) [0x00000] in <00000000000000000000000000000000>:0 at CsvHelper.CsvReader.ValidateHeader (System.Type type) [0x00000] in <00000000000000000000000000000000>:0 at CsvHelper.CsvReader+d__65.MoveNext () [0x00000] in <00000000000000000000000000000000>:0 `

Changing the build target to use Mono + .NET 4.x works fine.

I have experimented with trying to use Unity's Preserve attribute to try to see if this has anything to do with bytecode stripping to no apparent effect.

I saw some references to having to do all of the field binding manually when using ILL2CPP here: https://github.com/JoshClose/CsvHelper/issues/971

... man, that is going to be a huge bummer if I have to manually bind every single field by hand. Is that the only current workaround for using ILL2CPP...? There are other bits of reflection that seem to work with ILL2CPP...?

wiverson commented 4 years ago

This occurs with both the net45 and netstandard2.0 libs from CsvHelper.12.1.2.nupkg.

JoshClose commented 4 years ago

I don't know anything about IL2CPP, but it's probably not reflection that is the problem if you can do reflection in Unity and that still works. CsvHelper builds an expression tree and compiles it at runtime. I would guess the issue stems from that.

JoshClose commented 4 years ago

https://forum.unity.com/threads/are-c-expression-trees-or-ilgenerator-allowed-on-ios.489498/

JoshClose commented 4 years ago

Maybe I can help with making the manual binding easier for you. Where is the pain point when doing this for you?

wiverson commented 4 years ago

So, I have roughly a dozen or so CSV files, which I'm using for configuration of my game (and also eventually i18n/l13n). I was able to get things working by doing the binding manually via GetField/TryGetField. As an aside, I ran into a similar issue with the JSON parser/generation I was using, and wound up having to use a Unity-specific port. Interestingly, the Unity JSON library port was able to do all of the necessary reflection stuff to just work as a drop-in replacement - I didn't have to change anything to get stuff working.

It looks like System.Linq.Expressions relies on System.Reflection.Emit, which is the unsupported part. Would it be possible to just look up the necessary reflection, or use hint attributes? It looks like the main thing that's not working is looking up a constructor, I think where the argument expression is failing...?

var constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, argumentTypes, null); So maybe just a fallback mechanism for here would be sufficient...? I'm more familiar with Java reflection - static reflection compiled to iOS works fine, but the bytecode generators all blow up.

If you need any support debugging, testing, etc. let me know! :)

JoshClose commented 4 years ago

The problem is reflection is extremely slow. You don't notice it when doing it here and there, but when you're doing it hundreds of thousands, if not millions of times, it gets really slow. CsvHelper needs to create a class, create properties for each column, then set values to each of those properties. If you have 10 columns, that's already at 21 reflection calls at least (and probably more). Times that by how many rows you have. It'll take reading a file from milliseconds to like 10 minutes. There is no way to cache the reflection lookup either. If you save and instance of a property setter, the next time you call it it has to do the lookup again to set.

I'm open to suggestions on other ways of fixing this, but reflection only isn't worth it.

wiverson commented 4 years ago

This is exactly the kind of distraction from what I should be working on that is total catnip. LOL.

What do you think about this?

https://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/

I found this on this page, if I'm reading this right it might be faster than using expression trees...?

https://mattwarren.org/2016/12/14/Why-is-Reflection-slow/

wiverson commented 4 years ago

Also this https://internetexception.com/post/2016/08/05/Faster-then-Reflection-Delegates

JoshClose commented 4 years ago

Looks like the only possibility would be to use use delegates along with Skeet's MagicMethodHelper so those could be created dynamically at runtime. I would have to do some testing to see the performance difference. If it seems like a viable solution, I would need to see how that completely different way of generating the delegates would fit into the code and if everything I need to do is available. If it's fast and I can do everything, maybe it would be possible to switch completely over to that, but my guess would be it won't.

Is there a way to run IL2CPP without having Unity installed and a project set up? I currently don't have Unity installed.

JoshClose commented 4 years ago

If it's easier to just run from a project, I can install Unity too. If you could give a sample project and instructions on how to cause the failure, that would be helpful.

wiverson commented 4 years ago

I don't believe you can run IL2CPP outside of the Unity editor toolchain. I believe that Xamarin has a similar technology with similar restrictions (specifically no runtime code gen).

Unity is a somewhat complicated beast if you haven't done anything with it.

In brief:

If all of that sounds like something you are up for, I can do create the project and send it along. (e.g. creating the Test.cs, a few test csv files, etc.).

Unity (and Godot, and Unreal, etc etc) are all kind of big like that.

oOtroyOo commented 4 years ago

I recently

oOtroyOo commented 4 years ago

Sorry , I clicked a wrong button. I recently found that newer version of Unity do supports netstandard2.0. and works fine. Cause I'm mainly using 2017 ,that does not supports so well.

JoshClose commented 4 years ago

I'll use this to track @wiverson's issue with IL2CPP.

wiverson commented 4 years ago

csvhelper-test.zip

Here's a basic test. If you open SampleScene and click Play in the Editor, it runs fine. Go to File -> Build and generate a build with IL2CPP and you'll get the No public parameterless constructor found error.

You can confirm IL2CPP is turned on from File->Build Settings->Player Settings->Other Settings->Configuration->Scripting Backend->IL2CPP. (whew)

IL2CPP builds are required to ship on iOS, and possibly some consoles - I don't have the SDKs for the consoles to test. IL2CPP builds can take a while to generate.

Let me know if you need any help. Unity does a LOT, but it is VERY fiddly!

spvn commented 3 years ago

Sorry to dig up this thread again @JoshClose, this is the longest thread on Unity/il2cpp issues. Main thing is it seems like System.Linq.Expressions.Interpreter.LightLambdais being stripped that CsvHelper requires. There're several other threads like #1337, where you suggested manually creating classes when reading from a CSV would probably solve this issue.

However all the advice I've seen so far only relates to READING from a CSV. Any idea how I could work around this problem when WRITING to a CSV? Because I'm getting this error in my Unity build when trying to use WriteRecord

NullReferenceException: Object reference not set to an instance of an object. at System.Linq.Expressions.Interpreter.LightLambda.MakeRunDelegateCtor (System.Type delegateType) [0x00000] in <00000000000000000000000000000000>:0 at System.Linq.Expressions.Interpreter.LightLambda.GetRunDelegateCtor (System.Type delegateType) [0x00000] in <00000000000000000000000000000000>:0 at System.Linq.Expressions.Interpreter.LightDelegateCreator.CreateDelegate () [0x00000] in <00000000000000000000000000000000>:0 at System.Linq.Expressions.Expression1[TDelegate].Compile (System.Boolean preferInterpretation) [0x00000] in <00000000000000000000000000000000>:0 at CsvHelper.ObjectCreator.CreateInstance (System.Type type, System.Object[] args) [0x00000] in <00000000000000000000000000000000>:0 at CsvHelper.ObjectResolver+<>c__DisplayClass17_0.<.cctor>b__1 (System.Type type, System.Object[] args) [0x00000] in <00000000000000000000000000000000>:0 at System.Func3[T1,T2,TResult].Invoke (T1 arg1, T2 arg2) [0x00000] in <00000000000000000000000000000000>:0 at CsvHelper.ObjectResolver.Resolve[T] (System.Object[] constructorArgs) [0x00000] in <00000000000000000000000000000000>:0 at System.Func1[TResult].Invoke () [0x00000] in <00000000000000000000000000000000>:0 at System.Lazy1[T].CreateValue () [0x00000] in <00000000000000000000000000000000>:0 at System.Lazy1[T].LazyInitValue () [0x00000] in <00000000000000000000000000000000>:0 at System.Lazy1[T].get_Value () [0x00000] in <00000000000000000000000000000000>:0 at CsvHelper.CsvWriter.WriteRecord[T] (T record) [0x00000] in <00000000000000000000000000000000>:0

spvn commented 3 years ago

ok right after posting the previous comment I've found the answer on a different github repo's issues forum.

For anyone trying to use Unity IL2CPP with CsvHelper, you need to add the following link.xml file to your Assets folder (can be in a subfolder).

<linker>
    <assembly fullname="CsvHelper" preserve="all"/>
    <assembly fullname="System.Core">
        <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
    </assembly>
</linker>

The link.xml file tells IL2CPP to not strip these particular assemblies. Honestly you could probably improve the xml to only not strip certain parts of CsvHelper but I'm too lazy to figure out how/which parts.

Here's the relevant page from Unity docs: https://docs.unity3d.com/Manual/ManagedCodeStripping.html

And here's a different repo's wiki page that goes into further detail regarding the link.xml file: https://github.com/jilleJr/Newtonsoft.Json-for-Unity/wiki/Fix-AOT-using-link.xml

This should probably be pinned/noted somewhere for Unity and IL2CPP users...?

Tolgor commented 2 years ago

Thanks @spvn ! Really useful, and yes, this should probably be pinned for Unity users using the ILCPP scripting backend.

alexandrucatalinene commented 2 years ago

Is there another branch that works with unity? Now with the introduction of records and IAsyncEnumerable it doesn't work on Unity

JoshClose commented 2 years ago

I believe the recommended method is to use https://github.com/GlitchEnzo/NuGetForUnity Or just copy the source.

stef-t commented 10 months ago

I'm sorry to open this up again. Unfortunately, I'm having little luck with the link.xml as posted above. Even with this link.xml I'm still having trouble running my Unity application (Unity 2021.3) containing CsvHelper 30.0.1 (netstandard 2.1) on my Android device.

Does anyone have experience running more recent versions in Unity? If so, posting the updated link.xml would be greatly appreciated.

ok right after posting the previous comment I've found the answer on a different github repo's issues forum.

For anyone trying to use Unity IL2CPP with CsvHelper, you need to add the following link.xml file to your Assets folder (can be in a subfolder).

<linker>
    <assembly fullname="CsvHelper" preserve="all"/>
    <assembly fullname="System.Core">
        <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
    </assembly>
</linker>

The link.xml file tells IL2CPP to not strip these particular assemblies. Honestly you could probably improve the xml to only not strip certain parts of CsvHelper but I'm too lazy to figure out how/which parts.

Here's the relevant page from Unity docs: https://docs.unity3d.com/Manual/ManagedCodeStripping.html

And here's a different repo's wiki page that goes into further detail regarding the link.xml file: https://github.com/jilleJr/Newtonsoft.Json-for-Unity/wiki/Fix-AOT-using-link.xml

This should probably be pinned/noted somewhere for Unity and IL2CPP users...?

stef-t commented 9 months ago

After much digging, I found a solution.

It turns out that the link.xml isn't necessary, when you want to use CsvHelper 30.0.1 with Unity. The root cause stems from the problem that Unity's IL2CPP does not create AOT code for generics. Unity does not support calls like Type.MakeGeneric. See: https://forum.unity.com/threads/il2cpp-type-makegenerictype-work-around.311926/

This problem is fixed with Unity 2022.1 onwards. For earlier versions of Unity, there is a workaround. You must explicitly define any generic usages of classes in CsvHelper, which end up in calls to Type.MakeGeneric. This makes sure that AOT code is available at runtime. For example:

public struct Foo
{
  public int counter;
  public float treshold;
  public DateTime time;
}

[Preserve]
internal sealed class FooPreserves
{
  public static DefaultClassMap<Foo> s_DefaultClassMap;

  // for every type used by fields/properties in Foo
  public static MemberMap<Foo, int> s_MemberMapInt;
  public static MemberMap<Foo, float> s_MemberMapFloat;
  public static MemberMap<Foo, DateTime> s_MemberMapDateTime;
}

Note that I've only been looking at writing of csv data as this is my primary use-case.