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はvalueで返され、ネイティブ側でC++のものが乗っているため、何か問題が起きているのでは?
ネイティブ側の構造体は実際の情報を持っていますが、C APIについてはFLEncoderが定義されています を不透明なポインタとして定義しています。 . x86やx64で上記のメソッドを呼び出すとスムーズに動作しますが、ARMでは以下のように観測されます。 第1引数のアドレスが第2引数のアドレスになり、第2引数がnullになります(例えば、C#側でアドレスを記録すると、例えば0x054f59b8と0x0583f3bcになりますが、その後ネイティブ側では0x0583f3bcと0x000000になります)。 このような順序のずれた問題は何が原因なのでしょうか? 私は困っているので、誰かアイデアをお持ちですか...。
以下は、再現するために私が実行したコードです。
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 を介してネイティブのデバッグ シンボルを確実に提供し、ステップ実行できるようにする方法がまだわかっていません (ソースからすべてを構築することしかできないようです...)。したがって、ネイティブとマネージドの両方のコンポーネントを構築する必要があるため、デバッグは少し厄介になります。 もし誰かが試したいのであれば、これを簡単にする方法の提案を受け付けますが。 しかし、もし誰かが以前にこれを経験したことがあるか、なぜこれが起こるかについて何か考えがあれば、回答を追加してください。 もちろん、再現事例 (ソース ステッピングを提供しない簡単に構築できるもの、または提供する難しいもの) を希望する人がいれば、コメントを残してください。
EDIT
興味深い更新:もし私がC#で署名を偽造し、第2パラメータを削除したら、第1パラメータはOKになります。
編集2
2つ目の興味深い更新:C#のFLSliceResultのsizeの定義を次のように変更すると
UIntPtr
から
ulong
に変更した場合、引数は正しく入力されます。
size_t
は ARM では unsigned int であるべきだからです。
編集3
追加
[StructLayout(LayoutKind.Sequential, Size = 12)]
を追加することでも動作しますが、なぜでしょうか?このアーキテクチャのC / C++のsizeof(FLSliceResult)は、当然のように8を返します。 C# で同じサイズを設定するとクラッシュが発生しますが、12 に設定すると動作するようになります。
編集 4 C++のテストケースも書けるように、テストケースを最小化しました。 C# UWPでは失敗しますが、C++ UWPでは成功します。
EDIT 5 ここで は、比較のためにC++とC#の両方を分解した命令です(ただし、C#はどの程度取ればいいかわからないので、取り過ぎないようにしました)。
編集 6 さらに分析すると、C#で構造体が12バイトであると嘘をついたときの "good"実行時には、戻り値はレジスタr0に渡され、他の2つの引数はr1、r2経由で入力されることがわかります。 しかし、悪い方の実行では、これがずらされて、2つの引数がr0, r1経由で入ってきて、戻り値は別の場所(スタックポインタ?)にあることになる。
編集7 を参考にしました。 ARMアーキテクチャのプロシージャコール標準 . 4バイトより大きい、または呼び出し側と受信側の両方で静的にサイズを決定できないComposite Typeは、関数が呼び出されたときに追加引数として渡されたアドレスにメモリに格納されます。 4バイトより大きい、または呼び出し側と到達側の両方で静的にサイズを決定できない複合型は、関数が呼び出されたときに追加引数として渡されたアドレスにメモリに格納されます(§5.5, ルールA.4)。結果に使用されるメモリは、関数呼び出し中のどの時点でも変更可能である。" これは、extra argumentが最初の引数を意味するので、r0に渡すのが正しい動作であることを意味します(Cの呼び出し規約には引数の数を指定する方法がないため)。 CLR はこれを、以下のような別のルールと混同しているのではないでしょうか。 基本的 64ビットデータ型: "ダブルワードサイズの基本データ型(例えば、long long、double、64ビットコンテナ化ベクトル)は、r0とr1に返されます。 r0 と r1 で返されます。
編集8 CLRがここで間違ったことをしていることを示す多くの証拠があります。 バグレポート . そのレポに問題を投稿するすべての自動化されたボットの間で、誰かがそれに気づくことを願っています :-S.
どのように解決するのですか?
私が GH に提出した問題は、かなり長い間そのままになっています。 この動作は単なるバグであり、これ以上時間をかけて調べる必要はないと思います。
関連
-
[解決済み】"出力タイプがクラスライブラリのプロジェクトは直接起動できない"
-
[解決済み】ここで「要求URIに一致するHTTPリソースが見つかりませんでした」となるのはなぜですか?
-
[解決済み】5.7.57 SMTP - MAIL FROMエラー時に匿名メールを送信するためにクライアントが認証されない
-
[解決済み】aspNetCore 2.2.0 - AspNetCoreModuleV2 エラー
-
[解決済み】名前 'ViewBag' が現在のコンテキストに存在しない - Visual Studio 2015
-
[解決済み] Try-catchは私のコードをスピードアップさせるか?
-
[解決済み] なぜList<T>を継承しないのですか?
-
[解決済み] C#で同期メソッドから非同期メソッドを呼び出すには?
-
[解決済み] LINQ Orderby Descending Query(LINQ降順クエリ
-
[解決済み】スタックサイズがデフォルトの50倍のスレッドを作成した場合の危険性とは?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】文字列が有効な DateTime " format dd/MM/yyyy " として認識されなかった。
-
[解決済み】ASP.NET Core Dependency Injectionのエラーです。アクティブ化しようとしているときに、タイプのサービスを解決できません。
-
[解決済み】統合マネージドパイプラインモードで適用されないASP.NETの設定が検出された
-
解決済み] Critical error detected c0000374 - C++ dll returns pointer off allocated memory to C# [解決済み] Critical error detected c0000374 - C++ dll returns pointer off allocated memory to C#.
-
[解決済み] 'IEnumerable<SelectListItem>' 型の ViewData アイテムで、キーが国であるものは存在しない。
-
[解決済み】MetadataException: 指定されたメタデータ・リソースをロードできない
-
[解決済み】HRESULTからの例外:0x800A03ECエラー
-
[解決済み】5.7.57 SMTP - MAIL FROMエラー時に匿名メールを送信するためにクライアントが認証されない
-
[解決済み】aspNetCore 2.2.0 - AspNetCoreModuleV2 エラー
-
[解決済み】別のスレッドがこのオブジェクトを所有しているため、呼び出し側のスレッドはこのオブジェクトにアクセスできない