Закрепить обновляемую структуру перед переходом к неуправляемому коду?

Я использую какой-то старый API, и мне нужно передать указатель структуры на неуправляемый код, который работает асинхронно.

Другими словами, после того как я передаю указатель структуры неуправляемому коду, неуправляемый код копирует указатель и немедленно возвращается. Неуправляемый код может получить доступ к этой структуре в фоновом режиме в другом потоке. У меня нет контроля ни над неуправляемым кодом, который выполняется в другом потоке, ни над самим потоком.

Фиксированный оператор { } нельзя использовать для закрепления, так как он не предназначен для асинхронного неуправляемого закрепления.

GCHandle может закреплять только ссылки, поэтому для использования GCHandle структура должна быть упакована. Я попробовал, и это работает. Основная проблема заключается в том, что вы не можете обновить структуру из управляемого кода. Чтобы обновить структуру, в первую очередь нам нужно ее распаковать, затем обновить, затем снова упаковать, но... упс... снова упаковать?!? это означает, что предыдущий указатель в памяти по-прежнему указывает на старую неактуальную структуру, а новая структура имеет другой указатель, и это означает, что мне нужно передать новый указатель на неуправляемый код... неприменимо в моем кейс.

Как я могу закрепить структуру в памяти без фиксированного оператора { } и чтобы я мог обновить ее из управляемого кода без изменения ее указателя?

Спасибо.

Изменить:

Просто подумал... есть ли способ закрепить родительский объект, содержащий структуру, а затем получить указатель на struct, а не на объект-контейнер?


person DxCK    schedule 05.12.2009    source источник
comment
Я ожидал, что GCHandle является решением здесь. Если ничего не получится, вы можете выделить память в неуправляемом коде, чтобы сборщик мусора не перемещал ее.   -  person dtb    schedule 05.12.2009
comment
Хороший вопрос. Я думаю, что сделал это в CLI/C++, мне нужно просмотреть свои рабочие заметки по этому поводу. Однако не уверен, что существует эквивалентный механизм С#.   -  person Jason D    schedule 05.12.2009
comment
Может быть, сделать вызов в собственном фоновом потоке, использовать фиксированный {} и никогда не покидать фиксированный блок {} (по крайней мере, до тех пор, пока неуправляемый код может получить доступ к структуре)?   -  person Eric J.    schedule 05.12.2009
comment
Нет, вам просто нужно маршалировать данные самостоятельно, а не позволять GC играть с ними.   -  person Reed Copsey    schedule 05.12.2009
comment
@DxCK, не могли бы вы уточнить утверждение. Как я могу закрепить структуру в памяти без фиксированного оператора {} и чтобы я мог обновить ее без изменения ее указателя? ... Это управляемый код или неуправляемый код, который вы хотите иметь обновление памяти, пока она закреплена?   -  person Jason D    schedule 05.12.2009
comment
+1, именно тот вопрос, который я искал, после того, как понял, что а) копировал одну и ту же структуру несколько раз в набор собственных API и б) не мог видеть обновления, сделанные указанными API!   -  person anton.burger    schedule 05.10.2010


Ответы (6)


Является ли небезопасный код вариантом?

// allocate unmanaged memory
Foo* foo = (Foo*)Marshal.AllocHGlobal(sizeof(Foo));

// initialize struct
foo->bar = 0;

// invoke unmanaged function which remembers foo
UnsafeNativeMethods.Bar(foo);
Console.WriteLine(foo->bar);

// update struct
foo->bar = 10;

// invoke unmanaged function which uses remembered foo
UnsafeNativeMethods.Qux();
Console.WriteLine(foo->bar);

// free unmanaged memory
Marshal.FreeHGlobal((IntPtr)foo);

Это компилируется и не генерирует исключение, но у меня нет под рукой неуправляемой функции, чтобы проверить, работает ли она.

Из MSDN:

Когда AllocHGlobal вызывает LocalAlloc, он передает флаг LMEM_FIXED, который блокирует выделенную память. Кроме того, выделенная память не заполняется нулями.

person dtb    schedule 05.12.2009
comment
Спасибо, работает как шарм! на самом деле, я не использовал AllocHGlobal и FreeHGlobal, просто взял IntPtr, который дал мне GCHandle, и обновил структуру с помощью небезопасного кода. Простите меня за то, что я до сих пор не отметил это как ответ, потому что я хочу дождаться других возможных ответов, возможно, будут лучшие решения без небезопасного кода. - person DxCK; 05.12.2009

Использование закрепленной памяти в этом случае не является хорошей идеей, учитывая, что память для структуры должна быть действительной в течение длительного времени. GCHandle.Alloc() упакует структуру и сохранит ее в куче. Когда он будет закреплен, он будет долговременным бременем для сборщика мусора, поскольку ему нужно постоянно находить способ обойти камень на дороге.

Простое решение — выделить память для структуры в неуправляемой памяти. Используйте Marshal.SizeOf() для получения размера структуры и Marshal.AllocCoTaskMem() для выделения памяти. Это даст вам указатель, который нужно передать неуправляемому коду. Инициализируйте память с помощью Marshal.StructureToPtr(). И читать обновления структуры, написанные неуправляемым кодом, с помощью PtrToStructure().

Если вы делаете это часто, вы будете постоянно копировать структуру. Это может быть дорого, в зависимости от размера конструкции. Чтобы избежать этого, используйте небезопасный указатель для прямого доступа к неуправляемой памяти. Некоторый базовый синтаксис:

using System;
using System.Runtime.InteropServices;

class Program {
  unsafe static void Main(string[] args) {
    int len = Marshal.SizeOf(typeof(Test));
    IntPtr mem = Marshal.AllocCoTaskMem(len);
    Test* ptr = (Test*)mem;
    ptr->member1 = 42;
    // call method
    //..
    int value = ptr->member1;
    Marshal.FreeCoTaskMem(mem);
  }
  public struct Test {
    public int member1;
  }
}
person Hans Passant    schedule 05.12.2009
comment
В чем разница между использованием всех этих методов: Marshal.AllocCoTaskMem и Marshal.FreeCoTaskMem VS Marshal.AllocHGlobal и Marshal.FreeHGlobal, sizeof VS Marshal.SizeOf и какие методы мне следует использовать в моем случае и почему? Спасибо. - person DxCK; 09.12.2009
comment
Я не могу поместить ответ в поле для комментариев. Почему бы не создать новую тему с этим вопросом? - person Hans Passant; 09.12.2009
comment
StackOverflow .com/questions/1887288/ - person DxCK; 11.12.2009

Вместо закрепления вам нужно использовать Marshal. StructureToPtr и Marshal.PtrToStructure, чтобы маршалировать структуру в память, которую можно использовать в машинном коде.

person Reed Copsey    schedule 05.12.2009
comment
Кажется, он хочет обновить структуру из С# после того, как она была передана в неуправляемый код... - person dtb; 05.12.2009
comment
StructureToPtr копирует содержимое структуры в предварительно выделенный блок памяти... Это не совсем то, о чем просил OP. Он хочет, чтобы собственный код мог напрямую манипулировать памятью элементов. Я предполагаю, что это из соображений производительности... Возможно, я ошибаюсь. - person Jason D; 05.12.2009
comment
Вы не можете. Вы должны скопировать его в блок памяти, выделенный маршалом, обновить его в C++, а затем скопировать обратно в стек. В противном случае нет прямого способа сделать это. Если вы не хотите этого делать, лучше всего использовать C++/CLI. - person Reed Copsey; 05.12.2009
comment
+1 У Рида есть это прямо здесь. См. мой ответ для примера или ознакомьтесь с разделом примеров любой ссылки, которую Рид имеет выше. - person SwDevMan81; 05.12.2009

Пример структуры:

[StructLayout(LayoutKind.Sequential)]
public struct OVERLAPPED_STRUCT
{
   public IntPtr InternalLow;
   public IntPtr InternalHigh;
   public Int32 OffsetLow;
   public Int32 OffsetHigh;
   public IntPtr EventHandle;
}

Как прикрепить его к структуре и использовать:

OVERLAPPED_STRUCT over_lapped = new OVERLAPPED_STRUCT();
// edit struct in managed code
over_lapped.OffsetLow = 100;
IntPtr pinned_overlap_struct = Marshal.AllocHGlobal(Marshal.SizeOf(over_lapped));
Marshal.StructureToPtr(over_lapped, pinned_overlap_struct, true);

// Pass pinned_overlap_struct to your unmanaged code
// pinned_overlap_struct changes ...

// Get resulting new struct
OVERLAPPED_STRUCT nat_ov = (OVERLAPPED_STRUCT)Marshal.PtrToStructure(pinned_overlap_struct, typeof(OVERLAPPED_STRUCT));
// See what new value is
int offset_low = nat_ov.OffsetLow;
// Clean up
Marshal.FreeHGlobal(pinned_overlap_struct);
person SwDevMan81    schedule 05.12.2009

Как насчет того, чтобы структура включала интерфейс ActOnMe() и метод, например:

delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
interface IActOnMe<TT> {ActOnMe<T>(ActByRef<TT,T> proc, ref T param);}
struct SuperThing : IActOnMe<SuperThing>
{
  int this;
  int that;
  ...
  void ActOnMe<T>(ActByRef<SuperThing,T>, ref T param)
  {
    proc(ref this, ref param);
  }
}

Поскольку делегат принимает универсальный параметр по ссылке, в большинстве случаев можно избежать накладных расходов на создание замыканий путем передачи делегата статическому методу вместе со ссылкой на структуру для переноса данных в этот метод или из него. Кроме того, приведение уже упакованного экземпляра SuperThing к IActOnMe<SuperThing> и вызов ActOnMe<T> для него предоставит поля этого упакованного экземпляра для обновления, в отличие от создания другой их копии, как это произошло бы с приведением типа к структура.

person supercat    schedule 06.03.2012

Чтобы ответить на ваше редактирование:

Просто подумал... есть ли способ закрепить родительский объект, содержащий структуру, а затем получить указатель на struct, а не на объект-контейнер?

Я так думаю. Во всяком случае, вы должны иметь возможность использовать управляемый массив структур (возможно, массив из одной).

Вот пример кода:

    [StructLayout(LayoutKind.Sequential)]
    struct SomeStructure
    {
        public int first;
        public int second;
        public SomeStructure(int first, int second) { this.first=first; this.second=second; }
    }
    
    /// <summary>
    /// For this question on Stack Overflow:
    /// https://stackoverflow.com/questions/1850488/pinning-an-updateble-struct-before-passing-to-unmanaged-code
    /// </summary>
    private static void TestModifiableStructure()
    {
        SomeStructure[] objArray = new SomeStructure[1];
        objArray[0] = new SomeStructure(10, 10);
        
        GCHandle hPinned = GCHandle.Alloc(objArray, GCHandleType.Pinned);

        //Modify the pinned structure, just to show we can
        objArray[0].second = 42;

        Console.WriteLine("Before unmanaged incrementing: {0}", objArray[0].second);
        PlaceholderForUnmanagedFunction(hPinned.AddrOfPinnedObject());
        Console.WriteLine("Before unmanaged incrementing: {0}", objArray[0].second);
        
        //Cleanup
        hPinned.Free();
    }
    
    //Simulates an unmanaged function that accesses ptr->second
    private static void PlaceholderForUnmanagedFunction(IntPtr ptr)
    {
        int secondInteger = Marshal.ReadInt32(ptr, 4);
        secondInteger++;
        Marshal.WriteInt32(ptr, 4, secondInteger);
    }

И его вывод:

Before unmanaged incrementing: 42
Before unmanaged incrementing: 43
person Medinoc    schedule 14.08.2020
comment
Подождите, этому вопросу было десять лет? Как я вообще это видел? - person Medinoc; 17.08.2020