your programing

P / Invoke 인수가 전달 될 때 순서가 잘못되는 원인은 무엇입니까?

lovepro 2020. 10. 8. 08:24
반응형

P / Invoke 인수가 전달 될 때 순서가 잘못되는 원인은 무엇입니까?


이것은 x86 또는 x64가 아닌 ARM에서 특히 발생하는 문제입니다. 사용자가이 문제를보고했으며 Windows IoT를 통해 Raspberry Pi 2에서 UWP를 사용하여 재현 할 수있었습니다. 불일치하는 호출 규칙으로 이전에 이런 종류의 문제를 보았지만 P / Invoke 선언에 Cdecl을 지정하고 동일한 결과로 네이티브 측에 __cdecl을 명시 적으로 추가하려고했습니다. 다음은 몇 가지 정보입니다.

P / Invoke 선언 ( 참조 ) :

[DllImport(Constants.DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern FLSliceResult FLEncoder_Finish(FLEncoder* encoder, FLError* outError);

C # 구조체 ( 참조 ) :

internal unsafe partial struct FLSliceResult
{
    public void* buf;
    private UIntPtr _size;

    public ulong size
    {
        get {
            return _size.ToUInt64();
        }
        set {
            _size = (UIntPtr)value;
        }
    }
}

internal enum FLError
{
    NoError = 0,
    MemoryError,
    OutOfRange,
    InvalidData,
    EncodeError,
    JSONError,
    UnknownValue,
    InternalError,
    NotFound,
    SharedKeysStateError,
}

internal unsafe struct FLEncoder
{
}

C 헤더의 함수 ( 참조 )

FLSliceResult FLEncoder_Finish(FLEncoder, FLError*);

FLSliceResult는 값으로 반환되고 네이티브 측에 C ++ 관련 내용이 있기 때문에 문제가 발생할 수 있습니다.

네이티브 측의 구조체에는 실제 정보가 있지만 C API의 경우 FLEncoder는 불투명 포인터로 정의 됩니다 . x86 및 x64에서 위의 메서드를 호출하면 원활하게 작동하지만 ARM에서는 다음을 관찰합니다. 첫 번째 인수의 주소는 SECOND 인수의 주소이고 두 번째 인수는 null입니다 (예 : C # 측에 주소를 기록하면 0x054f59b8 및 0x0583f3bc와 같은 주소가 표시되지만 네이티브 측에서는 인수가 나타납니다). 0x0583f3bc 및 0x00000000). 이런 종류의 고장 문제를 일으키는 원인은 무엇입니까? 내가 어리둥절해서 아이디어가있는 사람이 있나요 ...

재현하기 위해 실행하는 코드는 다음과 같습니다.

unsafe {
    var enc = Native.FLEncoder_New();
    Native.FLEncoder_BeginDict(enc, 1);
    Native.FLEncoder_WriteKey(enc, "answer");
    Native.FLEncoder_WriteInt(enc, 42);
    Native.FLEncoder_EndDict(enc);
    FLError err;
    NativeRaw.FLEncoder_Finish(enc, &err);
    Native.FLEncoder_Free(enc);
}

다음과 같이 C ++ 앱을 실행하면 정상적으로 작동합니다.

auto enc = FLEncoder_New();
FLEncoder_BeginDict(enc, 1);
FLEncoder_WriteKey(enc, FLSTR("answer"));
FLEncoder_WriteInt(enc, 42);
FLEncoder_EndDict(enc);
FLError err;
auto result = FLEncoder_Finish(enc, &err);
FLEncoder_Free(enc);

이 로직은 최신 개발자 빌드 에서 충돌을 트리거 할 수 있습니다. but unfortunately I have not yet figured out how to reliably be able to provide native debug symbols via Nuget such that it can be stepped through (only building everything from source seems to do that...) so debugging is a bit awkward because both native and managed components need to be built. I am open to suggestions on how to make this easier though if someone wants to try. But if anyone has experienced this before or has any ideas about why this happens, please add an answer, thanks! Of course, if anyone wants a reproduction case (either an easy to build one that doesn't provide source stepping or a hard to build one that does) then leave a comment but I don't want to go through the process of making one if no one is going to use it (I'm not sure how popular running Windows stuff on actual ARM is)

EDIT Interesting update: If I "fake" the signature in C# and remove the 2nd parameter, then the first one comes through OK.

EDIT 2 Second interesting update: If I change the C# FLSliceResult definition of size from UIntPtr to ulong then the arguments come in correctly...which doesn't make sense since size_t on ARM should be unsigned int.

EDIT 3 Adding [StructLayout(LayoutKind.Sequential, Size = 12)] to the definition in C# also makes this work, but WHY? sizeof(FLSliceResult) in C / C++ for this architecture returns 8 as it should. Setting the same size in C# causes a crash, but setting it to 12 makes it work.

EDIT 4 I minimalized the test case so that I could write a C++ test case as well. In C# UWP it fails, but in C++ UWP it succeeds.

EDIT 5 Here are the disassembled instructions for both C++ and C# for comparison (though C# I'm not sure how much to take so I erred on the side of taking too much)

EDIT 6 Further analysis shows that during the "good" run when I lie and say that the struct is 12 bytes on C#, the return value gets passed to register r0, with the other two args coming in via r1, r2. However, in the bad run, this is shifted over so that the two args are coming in via r0, r1 and the return value is somewhere else (stack pointer?)

EDIT 7 I consulted the Procedure Call Standard for the ARM Architecture. I found this quote: "A Composite Type larger than 4 bytes, or whose size cannot be determined statically by both caller and callee, is stored in memory at an address passed as an extra argument when the function was called (§5.5, rule A.4). The memory to be used for the result may be modified at any point during the function call." This implies that passing into r0 is the correct behavior as extra argument implies the first one (since C calling convention doesn't have a way to specify the number of arguments). I wonder if the CLR is confusing this with another rule about fundamental 64-bit data types: "A double-word sized Fundamental Data Type (e.g., long long, double and 64-bit containerized vectors) is returned in r0 and r1."

EDIT 8 Ok there is a lot of evidence pointing to the CLR doing the wrong thing here, so I filed a bug report. I hope someone notices it between all the automated bots posting issues on that repo :-S.


The issue I filed on GH has been sitting there for quite some time. I believe that this behavior is simply a bug and no more time needs to be spent looking into it.

참고URL : https://stackoverflow.com/questions/44641195/what-could-cause-p-invoke-arguments-to-be-out-of-order-when-passed

반응형