将 C Struct 编组为 C# 委托的返回值

2024-02-06

我试图从绑定到本机函数的委托中按值返回一个小(8 字节)结构,但在针对 .NET Framework 2.0 时遇到以下错误(在针对 4.0+ 时,代码似乎可以正常工作) :

testclient.exe 中发生类型为“System.AccessViolationException”的未处理异常

附加信息:尝试读取或写入受保护的内存。这通常表明其他内存已损坏。

我怀疑我弄乱了托管类型注释,导致返回值未正确编组,但我看不出我做错了什么。下面是一个小型本机测试 DLL 和托管客户端的代码,它重现了该问题。

C/C++ Win32 (x86) 测试 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;
}

C# 测试客户端

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 的错误。

tl;dr

.NET Framework 2 生成不正确(可能不安全)的存根。

我是怎么发现这个的?

我进行了一些测试:

  1. 我使用了 4 字节长的结构,而不是 8 字节长的结构。有用!
  2. 我没有使用 x86,而是使用了 x64。有用!

弄清楚它在所有其他情况下都有效,我决定使用 Windbg 对它进行本机调试,以查看它崩溃的位置(因为 Visual Studio 不允许我“介入”本机)call与反汇编窗口)。

Guess what I found: enter image description here

.NET 框架生成了一个正在调用的存根memcpy,当它尝试复制到时失败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 函数被定义为返回一个struct大于 8 个字节,该函数实际上应返回一个指向struct在本地函数中stack并且调用者应该使用memcpy将其复制到自己的stack(这是 C 规范的一部分,由编译器实现 - 程序员只需“返回”一个结构体,而编译器则完成繁重的工作)。

然而,对于 8 个字节(或者struct or long long),大多数 C 编译器将其返回eax:edx。 .NET 的开发人员可能错过了这一点。错误可能是有人写的size >= 8反而size > 8...

编辑:更糟糕的是,它将结果写在给定的指针上!

before: 0x1111222244445555
after : 0x1234ABCD007BEF5C

它将指针更改为返回值!正如你所看到的,第一个dword调用后是 0x1234ABCD (如在本机 DLL 中),第二个dword是指向值的指针,即参数someVal那是给定的!

更有趣的是,因为如果你将一个指针传递给StatusBlockstruct - 它实际上适用于这种特定情况(因为第一个dword返回值中用作指针)

Solution

返回一个long变量并自己创建结构。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将 C Struct 编组为 C# 委托的返回值 的相关文章

随机推荐

  • 单元测试核心数据 - 异常退出,代码为 134

    我正在为我的核心数据应用程序设置单元测试 我在一个非常简单的测试中遇到了一个奇怪的问题 我收到的错误是 Developer Tools RunPlatformUnitTests include 451 0 Test rig Develope
  • asyncio create_task 永远运行

    我有以下代码 import asyncio loop asyncio get event loop async def while loop n 0 while True print f n await asyncio sleep 2 n
  • “角度未定义”的原因是什么

    我正在关注以下视频教程蛋头io http www egghead io 但在尝试效仿他创建工厂时的榜样 参见视频 我不断收到 角度未定义 参考错误 但我已经包含了角度脚本 这是我的 html 页面 div div div div
  • 如何使用 jackson 遍历生成的 json 模式并将自定义属性放入 json 模式

    type object properties name type string id type string i type integer p type object properties name type string id type
  • gnu screen:可以在最后调用的程序之后自动命名窗口吗?

    有没有办法自动让每个窗口将其名称更改为您从该窗口运行的最后一个程序的名称 这比手动重命名窗口更方便 是的 您可以使用 shelltitle 来执行此操作 假设您使用 bash 以下内容应该可以工作 将其添加到您的 screenrc shel
  • “英雄单位”是什么意思?

    英雄 一词是什么意思 为什么用它来命名网站 页面的 主要信息 具体来说 我想知道术语 英雄 或短语 英雄单位 是否是网页设计中使用的一些常见术语 但我却忽略了 英雄 一词是电影 电视道具设计师使用的 英雄道具 是为特写镜头而设计的道具 它有
  • 在 Mac 上未收到自定义记录区域的 CloudKit 推送通知

    我已设置自定义区域订阅以接收来自自定义记录区域的 静默 推送通知 我的 iOS 设备上一切正常 但我无法在 Mac 上接收通知 要注册通知 我正在注册通知类型applicationDidFinishLaunching NSApplicati
  • 我在我的博客页面上收到有关时区设置的警告[重复]

    这个问题在这里已经有答案了 正在显示PHP 日期选择器 http www triconsole com php calendar datepicker php在我的博客页面上使用简码 但低于警告 Warning date function
  • crypto.randomBytes 熵源耗尽

    我尝试使用以下命令生成大量 gt 1GB 伪随机数据crypto randomBytes 方法 但我无法为耗尽的熵源生成异常 以查看在出现这种可能的异常时我的应用程序的行为是什么 来自 Node JS 文档 注意 如果没有足够的数量 将抛出
  • Spring Data - MongoDB - JUnit 测试

    我有一个关于 Spring Data MongoDB 和 JUnit 测试的问题 RunWith SpringJUnit4ClassRunner class SpringApplicationConfiguration classes Us
  • moqing静态方法调用c#库类

    这似乎是一个很简单的问题 但我似乎找不到关键字来影响我的搜索 我试图通过模拟此方法调用中的所有对象来进行单元测试 我可以对我自己的所有创作执行此操作 除了这个 public void MyFunc MyVarClass myVar Imag
  • Rserve 服务器:如何终止阻塞实例(评估永远)?

    我需要执行Reval以多线程的方式 这是Rserve提供得相当好 但是 如果一个实例的评估时间太长 我需要能够关闭正在计算阻塞评估的实例 据我测试 给定的实例将拒绝关闭 直到评估完成 显然 它需要在再次监听之前获取结果 所以这是我的问题 有
  • 如何使用 API 从 Google 文档中提取标题

    目前正在尝试创建一个 python 脚本来检查 google 文档的各种 SEO 页面指标 谷歌文档 API 有一个好样本 https developers google com docs api samples extract text展
  • #如果忽略 DEBUG(VB.net 或 C#)

    我的代码中有几个到目前为止运行良好的代码 If DEBUG Then some code here End If 现在 我注意到 最近 If DEBUG then End If 中的代码 也在 释放模式 下执行 这很奇怪 以前没有发生过 可
  • 显示轨迹指示器

    从图像中您可以看到 在左侧发射的球在其后面发射 与计算的轨迹不符 我使用 SO 中的方程绘制球轨迹question https stackoverflow com questions 10401644 mousejointdef libgd
  • 由于 Windows Vista Home Premium 版本缺少 Windows 身份验证 IIS 组件...有修复吗?

    显然 Windows Vista Home Basic 和 Premium 都没有附带 IIS 的集成 Windows 身份验证 您需要商业版 所以有人知道如何为 IIS 安装 Windows 身份验证吗 我进入程序和功能 gt 打开或关闭
  • 如何使用脆皮形式格式化 django-filters?

    是否可以使用 django crispy forms 格式化 django filters 过滤器表单 我一直在尝试这样做 但是django filters FilterSet似乎不接受脆脆的表单格式 来自 DeviceFilter 类 它
  • jol GraphLayout 输出中的“(其他内容)”是什么?

    当使用 jol 的 GraphLayout 类打印从对象实例引用的对象图时 某些输出条目显示 其他内容 而不是类型和引用路径 例如 考虑以下代码 该代码打印 20 个随机 Integer 对象列表的图形 List
  • 如何与我的适配器类中的片段进行通信

    我创建了一个自定义适配器类 在该类中 当我单击列表视图布局时 我有一个代码必须向我的片段发送消息 谷歌搜索后 最好的方法可能是使用界面 其中大部分都是activity与fragment之间进行通信的例子 但就我而言 我对如何在适配器类与片段
  • 将 C Struct 编组为 C# 委托的返回值

    我试图从绑定到本机函数的委托中按值返回一个小 8 字节 结构 但在针对 NET Framework 2 0 时遇到以下错误 在针对 4 0 时 代码似乎可以正常工作 testclient exe 中发生类型为 System AccessVi