[英]Marshalling of C Struct as return value of C# delegate
我試圖通過綁定到本機函數的委托的值返回一個小的(8字節)結構,但是在針對.NET Framework 2.0時遇到以下錯誤(代碼似乎在定位4.0+時正常工作) :
testclient.exe中發生了未處理的“System.AccessViolationException”類型異常
附加信息:嘗試讀取或寫入受保護的內存。 這通常表明其他內存已損壞。
我懷疑我搞砸了托管類型注釋,以致返回值沒有被正確編組,但我看不出我做錯了什么。 下面是一個小型本機測試DLL和托管客戶端的代碼,它可以重現該問題。
//Natural alignment, blittable, sizeof(StatusBlock) == 8
struct StatusBlock{
std::uint32_t statusA;
std::uint32_t statusB;
};
/*
* When compiled this function stores the 64bit return value in the
* eax:edx register pair as expected.
*/
static StatusBlock __cdecl SomeFunction(std::uint32_t const someVal){
return StatusBlock{ someVal, 0x1234ABCD };
}
//Exported
extern "C" PVOID __stdcall GetFunctionPointer(){
return &SomeFunction;
}
class Program
{
//Blittable, Marshal.SizeOf(typeof(StatusBlock)) == 8
[StructLayout(LayoutKind.Sequential)]
private struct StatusBlock
{
public UInt32 statusA;
public UInt32 statusB;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate StatusBlock SomeFunction(UInt32 someVal);
[DllImport("testlib.dll",CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr GetFunctionPointer();
static void Main(string[] args)
{
var fnPtr = GetFunctionPointer();
System.Diagnostics.Debug.Assert(fnPtr != IntPtr.Zero);
var someFn = (SomeFunction)Marshal.GetDelegateForFunctionPointer(fnPtr, typeof(SomeFunction));
/*
* Crashes here with a System.AccessViolationException when targeting .NET Framework 2.0.
* Works as expected when targeting .NET Framework 4.0 +
*/
var statusBlock = someFn(22);
}
}
值得注意的是,如果委托的返回類型是Uint64
則應用程序在.NET 2.0和4.0情況下都按預期工作。 但是,我不應該這樣做; StatusBlock
應該正確編組。
在針對.NET 4.0時我是否幸運? 任何洞察我做錯的事情都會非常感激。
.NET Framework 2生成不正確(可能不安全)的存根。
我進行了一些測試:
確定它適用於所有其他情況,我決定使用windbg對其進行原生調試以查看它崩潰的位置(因為Visual Studio不允許我使用反匯編窗口“插入”本機call
)。
猜猜我發現了什么:
.NET框架生成了一個調用memcpy
的存根,當它嘗試復制到edi
時失敗,當時edi
的值為0x16(== 22),這是在C#代碼中發送的參數!
那么,讓我們看看如果我要發送一個有效的指針函數會發生什么:
unsafe
{
long* ptr = &something;
uint ptr_value = (uint)ptr;
Console.WriteLine("Pointer address: {0:X}", (long)ptr);
var statusBlock = someFn(ptr_value);
Console.WriteLine("A: {0}", statusBlock.statusA);
Console.WriteLine("B: {0:X}", statusBlock.statusB);
}
輸出:(它有效並且在給出有效指針時不會崩潰!)
Marshal.SizeOf(typeof(StatusBlock)) = 8
Running .NET Version 2
Pointer address: 49F15C
A: 0
B: 0
因此,我得出結論,這是.NET Framework 2中不可解決的問題。
當一個C函數被定義為返回一個大於8字節的struct
,該函數實際上應該返回一個指向本地函數stack
struct
的指針,調用者應該使用memcpy
將它復制到它自己的stack
(這是C的一部分)規范並由編譯器實現 - 程序員只需“返回”一個結構,編譯器就可以完成繁重的工作。
但是,對於8個字節( struct
或long long
),大多數C編譯器在eax:edx
返回它。 可能.NET的開發人員錯過了這一點。 錯誤可能是有人寫了size >= 8
而不是size > 8
...
編輯:更糟糕的是,它將結果寫在給定的指針上!
before: 0x1111222244445555
after : 0x1234ABCD007BEF5C
它將指針更改為返回值! 如您所見,調用后的第一個dword
是0x1234ABCD(如本機DLL中所示),第二個dword
是指向值的指針,即給定的參數someVal
!
它更有趣,因為如果你將指針傳遞給StatusBlock
結構 - 它實際上適用於這種特定情況(因為返回值中的第一個dword
用作指針)
返回一個long
變量並自己創建struct。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.