使用 GCHandle.Alloc 固定值类型

问题描述

是否可以通过引用一个方法来固定传递一个 int,然后将该变量固定在内存中,以便我可以使用指针访问它?

我正在尝试使用 GCHandle.Alloc 在内存中临时固定一个 int,以便我可以安全地存储指向该整数的指针。当我尝试通过存储在 GCHandle 中的指针设置 int 的值时,我的整数不会更新。如果我使用不安全代码创建一个指向 int 的指针,我可以成功地更改 int。似乎 int 是按值传递的,我设置的 int 与实际固定的 int 不同。这是我目前面临的问题的示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var count = 8001;
            var mc = MyClass.New(ref count);
            
            mc.Process();
        }
    }

    public unsafe sealed class MyClass
    {
        private GCHandle handle;
        private readonly int* countPtr;
        private MyClass(GCHandle handle,ref int totalCount)
        {
            this.handle = handle;
            fixed (int* p = &totalCount)
                countPtr = p;
        }

        public void Process()
        {
            var value = 9001;
            var handlePtr = (int*)handle.AddrOfPinnedobject();

            *handlePtr = (int)value; // count is unchanged
            *countPtr = (int)value; // IT'S OVER 9000!

            handle.Free();
        }

        public static MyClass New(ref int count)
        {
            var handle = GCHandle.Alloc(count,GCHandleType.Pinned);
            return new MyClass(handle,ref count);
        }
    }
}

handlePtr 和 countPtr 的内存地址是不同的,这让我相信我认为固定的不是固定的......而且我可能在我的应用程序中构建了一个定时炸弹。

用例:

我有一个使用以下 API 的自定义数据访问层

public IEnumerable<Rows> CallMyProcedure(int pageIndex,int pageSize,out int totalCount)
{
    var rows = Call()
        .SetParameter("PageIndex",pageIndex)
        .SteParameter("PageSize",pageSize)
        .EnumerateDataSet(commandTimeoutSeconds: 60);
    
    return rows;
}

Call() 开始构建关于如何在运行时设置存储过程的上下文。 SetParameter 和其他几个方法将数据添加到执行 StoredProcedure 时将使用的上下文中。 EnumerateData 设置一个 IEnumerable 状态机,该状态机使用 sqlDataReader 执行存储过程,该存储过程在每行上产生。

某些存储过程使用偏移/提取来检索有限数量的结果。在这些情况下,我还需要使用 sql 输出参数输出总行数

public IEnumerable<Rows> CallMyProcedure(int pageIndex,out int totalCount)
{
    var count = 0;
    var rows = Call()
        .SetParameter("PageIndex",pageSize)
        .OutputTotalCount(x=> count = x)
        .EnumerateDataSet(commandTimeoutSeconds: 60)
        .ToList();
    
    totalCount = count;
    return rows;
}

将在枚举所有行并关闭 sqlDataReader 后执行我传递的操作,因为这是从 sql 存储过程获取 OutPutParameter 的唯一方法。因此,这样做的唯一原因是因为我正在调用 .ToList()。如果我不调用 .ToList(),out totalCount 将被设置为 0,则该方法返回并在读取所有行后触发设置计数的操作。

我希望添加一个额外的 OutputTotalCount 重载,以便能够使用 do

public IEnumerable<Rows> GetAllData()
{
    var count = 0;
    return CallMyProcedure(1,int.MaxValue,out count);
}

public DataPage GetDataPage(int pageIndex,int pageSize)
{
    var count = 0;
    var list = CallMyProcedure(pageIndex,pageSize,out count).ToList()
    
    return new DataPage
    {
        Data = list,TotalCount = count
    }
}

public IEnumerable<Rows> CallMyProcedure(int pageIndex,out int totalCount)
{
    totalCount = 0; // Needed because we are leaving this method before setting
    var rows = Call()
        .SetParameter("PageIndex",pageSize)
        .OutputTotalCount(ref totalCount)
        .EnumerateDataSet(commandTimeoutSeconds: 60)
    
    return rows;
}

此处新的 OutPutTotalCount 重载本质上将 MyClass 的实例添加到 EnumerDataSet 将在调用 GetEnumerator 时用于执行存储过程的上下文。读取所有行后将调用进程。 GCHandles 也由执行上下文跟踪,并在 try/finally 中释放。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)