[c#] 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);

이 로직은 최신 개발자 빌드 에서 충돌을 트리거 할 수 있습니다.하지만 안타깝게도 Nuget을 통해 네이티브 디버그 기호를 안정적으로 제공 할 수있는 방법을 아직 알아 내지 못해 단계별로 진행할 수 있습니다. 관리되는 구성 요소를 구축해야합니다. 누군가가 시도하고 싶다면 이것을 더 쉽게 만드는 방법에 대한 제안에 열려 있습니다. 그러나 누군가가 전에 이것을 경험했거나 이것이 발생하는 이유에 대한 아이디어가 있다면 대답을 추가하십시오. 감사합니다! 물론 누군가가 복제 케이스 (소스 스테핑을 제공하지 않는 빌드하기 쉬운 케이스 또는 빌드하기 어려운 케이스)를 원하는 경우 댓글을 남기지 만 하나를 만드는 과정을 거치고 싶지 않습니다. 아무도 그것을 사용하지 않을 경우 (실제 ARM에서 Windows를 실행하는 것이 얼마나 인기가 있는지 모르겠습니다)

흥미로운 업데이트 편집 : C #에서 서명을 “위조”하고 두 번째 매개 변수를 제거하면 첫 번째 매개 변수가 확인됩니다.

편집 2 두 번째 흥미로운 업데이트 : 크기의 C # FLSliceResult 정의를에서 UIntPtr로 변경하면 ulong인수가 올바르게 입력됩니다 … size_tARM에서는 unsigned int 여야하기 때문에 의미가 없습니다 .

편집 3[StructLayout(LayoutKind.Sequential, Size = 12)] C #의 정의에 추가 하면이 작업도 수행되지만 왜? 이 아키텍처의 C / C ++에서 sizeof (FLSliceResult)는 8을 반환합니다. C #에서 동일한 크기를 설정하면 충돌이 발생하지만 12로 설정하면 작동합니다.

편집 4 C ++ 테스트 케이스도 작성할 수 있도록 테스트 케이스를 최소화했습니다. C # UWP에서는 실패하지만 C ++ UWP에서는 성공합니다.

EDIT 5 여기에 (C #을하지만 내가 너무 많이 복용의 편에 서 있죠 있도록 걸릴 얼마나 확실하지 않다) 비교를 위해 모두 C ++와 C #의 분해 지침입니다

편집 6 추가 분석은 내가 거짓말을하고 구조체가 C #에서 12 바이트라고 말할 때 “좋은”실행 중에 반환 값이 레지스터 r0에 전달되고 다른 두 개의 인수는 r1, r2를 통해 들어온다는 것을 보여줍니다. 그러나 나쁜 실행에서 이것은 두 개의 인수가 r0, r1을 통해 들어오고 반환 값이 다른 곳에 있도록 이동됩니다 (스택 포인터?)

편집 7 ARM 아키텍처에 대한 프로 시저 호출 표준을 참조했습니다 . 이 인용구를 찾았습니다. “4 바이트보다 크거나 호출자와 피 호출자 모두 크기를 정적으로 결정할 수없는 복합 유형은 함수가 호출 될 때 추가 인수로 전달 된 주소의 메모리에 저장됩니다 (§5.5, 규칙 A .4). 결과에 사용되는 메모리는 함수 호출 중 언제든지 수정할 수 있습니다. ” 이는 추가 인수가 첫 번째 인수를 의미하므로 r0에 전달하는 것이 올바른 동작임을 의미합니다 (C 호출 규칙에는 인수 수를 지정하는 방법이 없기 때문입니다). CLR이 이것을 기본 에 대한 다른 규칙과 혼동하고 있는지 궁금합니다. 64 비트 데이터 유형 : “더블 워드 크기의 기본 데이터 유형 (예 : long long, double 및 64 비트 컨테이너화 된 벡터)은 r0 및 r1에서 반환됩니다.”

편집 8 좋아 CLR이 여기에서 잘못된 일을하고 있음을 가리키는 많은 증거가 있으므로 버그 보고서를 제출했습니다 . 누군가가 해당 저장소에 문제를 게시하는 모든 자동화 된 봇 사이에서 알아 차리기를 바랍니다.



답변

GH에 제출 한 문제는 꽤 오랫동안 거기에있었습니다. 이 동작은 단순히 버그이며 더 이상 조사하는 데 시간을 할애 할 필요가 없다고 생각합니다.


답변