#c# #.net #c#-4.0 #garbage-collection #unsafe-pointers
Вопрос:
Можно ли закрепить передачу int ссылкой на метод, который затем закрепит эту переменную в памяти, чтобы я мог получить к ней доступ с помощью указателя?
Я пытаюсь использовать GCHandle.Выделите, чтобы временно закрепить int в памяти, чтобы я мог безопасно хранить указатель на это целое число. Когда я пытаюсь установить значение int с помощью указателя, хранящегося в GCHandle, мое целое число не обновляется. Если я создам указатель на 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 = amp;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);
}
}
}
Адреса памяти handlept и 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;
}
Вызов() начинает создавать контекст того, как хранимая процедура будет настроена во время выполнения. setParameter и несколько других методов добавляют данные в контекст, который будет использоваться при выполнении процедуры StoredProcedure. EnumerateData настраивает машину состояний IEnumerable, которая выполняет хранимую процедуру с помощью SqlDataReader, который выдает результаты в каждой строке.
Некоторые хранимые процедуры используют Смещение / выборку для получения ограниченного числа результатов. В этих случаях мне также нужно вывести общее количество строк, используя выходной параметр Sql
public IEnumerable<Rows> CallMyProcedure(int pageIndex, int pageSize, out int totalCount)
{
var count = 0;
var rows = Call()
.SetParameter("PageIndex", pageIndex)
.SteParameter("PageSize", pageSize)
.OutputTotalCount(x=> count = x)
.EnumerateDataSet(commandTimeoutSeconds: 60)
.ToList();
totalCount = count;
return rows;
}
Действие, которое я передаю, будет выполнено после перечисления всех строк и закрытия SqlDataReader, так как это единственный способ получить выходной параметр из хранимой процедуры SQL. Таким образом, единственная причина, по которой это работает, заключается в том, что я звоню .ТоЛист(). Если я не позвоню .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, int pageSize, out int totalCount)
{
totalCount = 0; // Needed because we are leaving this method before setting
var rows = Call()
.SetParameter("PageIndex", pageIndex)
.SteParameter("PageSize", pageSize)
.OutputTotalCount(ref totalCount)
.EnumerateDataSet(commandTimeoutSeconds: 60)
return rows;
}
Новая перегрузка OutPutTotalCount здесь по существу добавляет экземпляр MyClass в контекст, который EnumerDataSet будет использовать для выполнения хранимой процедуры при вызове GetEnumerator. Процесс будет вызван после того, как все строки будут прочитаны. GCHandles также отслеживаются контекстом выполнения и являются свободными внутри try/finally.
Комментарии:
1. Я думаю, что целое число вставляется в вызов GCHandle. Alloc(), что означало бы, что вы правы, оно указывает на неправильное целое число.
2. Считаете ли вы, что он неправильно указывает на значение в коробке и на самом деле имеет закрепленное ожидаемое значение? Есть ли способ рассказать об этом с помощью VS2013 / Resharper?
3. Да, я думаю, что это правильно. Попробуйте сами боксировать int.
object oi = i; var gcHandle = GCHandle.Alloc(oi, GCHandleType.Pinned); int *pi = (int*) gcHandle.AddrOfPinnedObject();
Затем*pi = 1
это должно быть отражено в oi.4. Ваш GCHandle указывает на упакованную копию int. Но когда вы берете небезопасный указатель на int, этот указатель указывает на фактическое значение int в стеке. Таким образом, чтобы избежать небезопасных указателей, вам придется работать с int в штучной упаковке, который будет закреплен, как и ожидалось.
5. Знаете ли вы, что если
count
он выйдет за рамки, вы его потеряете? Опасно держать указатель на переменную в стеке, которая может жить дольше, чем область видимости. Я уверен, что он будет упакован в коробку, потому что он создаст копию в куче.