mcpiroman / UnityNativeTool

Allows to unload native plugins in Unity3d editor
MIT License
183 stars 18 forks source link

Native calls from static constructor fail #25

Closed zhmt closed 4 years ago

zhmt commented 4 years ago

Hi , mcpiroman, I am trying to use swig with UnityNativeTool together. but I got some error that I can't resolve. Need your help again.

The code I am using is copied from master branch.

This my dll ( Placing dll in x86_64 doesn't help. ): QQ图片20200611083619

Errors: QQ图片20200611083425

This is status of running DllManipulator: QQ图片20200611082817

Here are detailed exceptions: QQ图片20200611083537

QQ图片20200611083600

zhmt commented 4 years ago

I am using unity2019.4.0f1, with URP.

zhmt commented 4 years ago

I have tried logging all mocked methods, It seems that all my native methods and some methods from user32 are mocked, even the " SWIGRegisterExceptionCallbacks_ezgapi "(where the excetion root): QQ图片20200611084627

zhmt commented 4 years ago

The other exception root: QQ图片20200611084850

zhmt commented 4 years ago

I found how to reproduce this error: Call native method in static constructor, like this:

public class HiTest 
{
    [System.Runtime.InteropServices.DllImport("libezg")]
    public static extern int test_add(int a,int b);

    static HiTest()
    {
        Debug.Log(test_add(2, 3));
    }
}

It will complain:

DllNotFoundException: libezg
HiTest..cctor () (at Assets/CppGlue/HiTest.cs:12)
Rethrow as TypeInitializationException: The type initializer for 'HiTest' threw an exception.
RoleMyAnimator.Start () (at Assets/My/Script/RoleMyAnimator.cs:18)

Everything will be ok , if we remove the static constructor.

Does this mean: we cant mock methods before static constructor? If mocking occurs before static constructor, everything should be ok.

zhmt commented 4 years ago

If I change loading mode to lazy, It will not try to mock user32.dll.

zhmt commented 4 years ago

If all codes in static constructors are removed into plain static methods, and called in a MonoBehavior script with priority -9000, everything runs smoothly.

Is it possible to support this: mocking our method before static constructors being called?

If it is supported , it will be perfect. SWIG saves us from writing glue codes, UnityNativeTool saves us from restarting unity frequently. And it not a big deal if we can't resolve this problem, we can still use them both with one extra step.

mcpiroman commented 4 years ago

Hi, regarding the issue with loading user32.dll, that's because All native functions option is selected. You can use attributes (w or w/ disabling this option) to control which functions you want to mock. I also once considered to implement a filter of DLL names, with common once like kernel32 and libc disabled by default. Maybe not a bad idea.

Static ctors AFAIK shouldn't run before unity scripts, because they run when containing class is first accessed. Maybe thay changed it somehow. I'll look at this some time later.

Now, I'm not sure what SWIG is, probably i've heard of it but not in this acronym. What's most relevant here is whether it's a native or managed library.

zhmt commented 4 years ago

I have tried filtering my dll names, it works.

SWIG is just a tool to generate code like "[System.Runtime.InteropServices.DllImport("libezg")]" automatically. It is not the root cause of errors. Calling native method in static constructor is the root cause .

zhmt commented 4 years ago

I have moved all native method calling out of static constructor. It works for now.

zhmt commented 4 years ago

Thanks for your response in morning. :)

mcpiroman commented 4 years ago

I investigated it further and it's more interesting than it might seem. First of all, as I expected, static constructors aren't executed on they own until the containing class is called from code, which may only happen from script. If you put some logs you'll see that my code is run before yours.

What happens here is that this tool at the attempt to mock a native method triggers the static ctor (because Mono treats that as an access to the class) and as it happens before it finishes mocking, it thus fails. So apparently it won't work if you have class having native function and a static ctor making native call, which I'll add to readme.

Moreover, this also means your code wouldn't even run, if not by mentioned accident, so you'd have to move it out of static ctors anyway.

zhmt commented 4 years ago

It makes sense. I know more about java than c#, static constructors will be excuted before we access the class info in java, it looks like c# does this similarly.

Thanks for your explanation, and your great work.

rogerbarton commented 4 years ago

You can use the [NativeDllLoadedTrigger] as a replacement for the static ctor when using the tool. Without this tool it is of course fine to use the static ctor.

This it what I do to initialize my lib before any method is called. I only use the tool in the editor.

#if !UNITY_EDITOR
static MyClass() { Initialize(); }
#endif

#if UNITY_EDITOR
[NativeDllLoadedTrigger] // Trigger this each time the dll is loaded, so we reinitialize if we reload it
#endif
public static void Initialize()
{
   InitializeNative(...);
}

public const string DllName =
#if UNITY_EDITOR
      "mylib";
#else
      "__mylib";
#endif

[DllImport(DllName)]
private static extern void InitializeNative(...);

So in a build we use the static ctor and otherwise the callback attribute. Note that if you mock multiple libs then you need to modify it slightly to check which lib was loaded (see attribute code).

Again bare in mind when static ctors are really called.

zhmt commented 4 years ago

Thanks rogerbarton, it is another choice. I will try later.