Закрепление обновляемой структуры перед передачей в неуправляемый код? - PullRequest
10 голосов
/ 05 декабря 2009

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

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

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

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

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

Спасибо.

Edit:

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

Ответы [ 5 ]

6 голосов
/ 05 декабря 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;
  }
}
3 голосов
/ 05 декабря 2009

Возможно ли использование небезопасного кода?

// 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, что приводит к блокировке выделенной памяти на месте. Кроме того, выделенная память не заполнена нулями.

1 голос
/ 05 декабря 2009

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

0 голосов
/ 06 марта 2012

Как насчет того, чтобы структура включала в себя 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> для него предоставит поля этого экземпляра в штучной упаковке для обновления, а не для создания другой их копии, как если бы происходят с типом, передаваемым в структуру.

0 голосов
/ 05 декабря 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);
...