fix: Correctly unbox generic parameters wrapped in Il2CppObjectBase when underlying type is ValueType (e.g. Nullable<T>(T value) constructor) #246
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Description
This PR addresses a code generation issue regarding how generic parameters (
T) are marshaled when passed to native methods.The Problem
When a method takes a generic parameter
T(e.g.,Method(T value)), andTis instantiated as anIl2CppObjectBasewrapper representing a native ValueType (Struct), the previous codegen logic treated it purely as a reference type.It passed the pointer to the boxed object (header + data) to the native function. However, if the native method signature expects the raw value type (common in generic methods like
Nullable<T>.ctoror generic collection manipulation), this resulted in the native code reading the object header as data, causing data corruption or crashes.The Fix
I updated the code generation template to include a runtime check for generic parameters.
The new logic detects this specific scenario:
Il2CppObjectBase?il2cpp_class_is_valuetype).Tis compatible withIl2CppSystem.ValueType.il2cpp_object_unbox) to get the raw data pointer before passing it to the native method.Related Issue
Fixes #240
(Although the issue specifically reports
Nullable<T>, this fix applies generally to any method acceptingTwhereTis a struct wrapper).Test Plan
I verified the fix using
Il2CppSystem.Nullable<T>as a reproduction case, as it relies heavily on correctly receiving the unboxed value ofT.Test Environment:
AwesomeStruct) defined in the game assembly.Nullable<AwesomeStruct>and verifying data integrity.Code for Testing
Results
Before Fix (Broken):
The native constructor read the object header as the struct data, resulting in garbage values.
After Fix (Working):
The native constructor receives the unboxed data correctly.
(Note: I also verified the Dictionary/Int64 case mentioned in PR #69 to ensure no regression, and it works correctly.)
Implementation Note
Regarding the unbox check condition:
I am currently using
typeof(Il2CppSystem.ValueType).IsAssignableFrom(typeof(T))combined withil2cpp_class_is_valuetypeon the runtime instance.There is an alternative approach using
il2cpp_class_is_valuetype(Il2CppClassPointerStore<T>.NativeClassPtr). I opted forIsAssignableFromas it seemed safer for the generated code flow, but I am open to feedback if checking theNativeClassPtrdirectly is preferred for this generic constraint check.