Могу ли я маршалировать System.Object как недоступный указатель на C, а затем обратно на C #? - PullRequest
3 голосов
/ 18 ноября 2011

В CI есть функция, которая регистрирует обратный вызов и объект состояния, а затем передает объект состояния в обратный вызов каждый раз, когда он вызывается, так же, как EventHandler<EventArgs> работает в C #.У меня есть класс в C #, который регистрирует управляемый обратный вызов с помощью функции C и в настоящее время передает IntPtr.Zero, и я просто не использую его, потому что я не нашел хорошего и чистого способа передачи ссылки на управляемый объект в C и получения его обратнов C #.

Я не хочу, чтобы управляемый объект был доступен в C, я просто хочу передать его в C и передать его (дословно) в управляемый обратный вызов каждый раз, когда он вызывается из C.

В общем, ищем "правильный" способ сделать это.

Вот мой делегат для вызова:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.SysInt)]
internal delegate IntPtr LuaAllocator(
    [In, MarshalAs(UnmanagedType.AsAny)] object args,
    [In, MarshalAs(UnmanagedType.SysInt)] IntPtr ptr,
    [In, MarshalAs(UnmanagedType.SysInt)] IntPtr originalSize,
    [In, MarshalAs(UnmanagedType.SysInt)] IntPtr newSize);

Вот текущий DllImport Iя делаю:

    [DllImport(DllName, EntryPoint = "lua_newstate", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
    [return: MarshalAs(UnmanagedType.SysInt)]
    public static extern IntPtr NewState(
        [In, MarshalAs(UnmanagedType.FunctionPtr)] LuaAllocator allocator,
        [In, MarshalAs(UnmanagedType.AsAny)] object args);

И ошибка, которую я получаю прямо сейчас:

MarshalDirectiveException не обработан: Cannot marshal 'параметр # 1': AsAny не может быть использован навозвращаемые типы, параметры ByRef, ArrayWithOffset или параметры, передаваемые из неуправляемого в управляемое.

Эта ошибка исчезает, если я оставляю первый аргумент делегата как IntPtr и использую MarshalAs.SysInt, но тогда я не могуЯ не могу восстановить управляемый объект, но я получаю значение, отличное от IntPtr.Zero, что любопытно и может оказаться полезным.

Ответы [ 3 ]

2 голосов
/ 18 ноября 2011

Я нашел решение. Комментарий HandleRef указал мне на GCHandle, с которым я уже знаком, но никогда не делал этого. Сделав не закрепленный GCHandle из объекта, который вы хотите маршалировать, вы можете вызвать статический метод GCHandle.ToIntPtr(GCHandle), чтобы получить IntPtr, представляющий этот конкретный дескриптор. Внутри обратного вызова я позвонил GCHandle.FromIntPtr(IntPtr), чтобы повторно получить дескриптор, а затем получил System.Object из свойства GCHandle.Target.

Вот как вы можете настроить обратный вызов и объект состояния:

// Omitted
string test = "This is a test string.";
GCHandle handle = GCHandle.Alloc(test, GCHandleType.Normal);
LuaCore.NewState(allocateCallback, GCHandle.ToIntPtr(handle));
// Omitted

А вот как вы используете это в обратном вызове:

private IntPtr Allocate(IntPtr sender, IntPtr ptr, IntPtr originalSize, IntPtr newSize)
{
    GCHandle handle = GCHandle.FromIntPtr(sender);
    string test = handle.Target as string;

    // Omitted
}
2 голосов
/ 18 ноября 2011

Я думаю, что вы ищете System.Runtime.InteropServices.GCHandle.Вы можете выделить GCHandle и преобразовать его в System.IntPtr.Когда вы получите его обратно, вы можете использовать GCHandle.FromIntPtr, чтобы получить обратно GCHandle, и оттуда получить правильную ссылку на оригинал object.

Предупреждение

Возможны проблемы, связанные с использованием этого метода, связанного со сборкой мусора и переменными, которые вы размещаете.Как обычно для Platform Invoke, вам нужно убедиться, что некоторые вещи, которые вы выделяете, не собраны.Параметр GCHandle.Alloc должен быть где-то кэширован с сильной ссылкой, и, конечно же, вам нужно будет запретить сбор делегата, который вы передаете любой нативной функции, которую вы вызываете.Также обратите внимание, что GCHandle должен быть освобожден, когда вы закончили с ним, чтобы предотвратить утечки памяти.

Ниже приведен пример, демонстрирующий процедуру.

using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.IO;
public delegate bool InteropCallback(int handle, System.IntPtr payload);

public class InteropApp
{
    [DllImport("user32.dll")]
    public static extern bool EnumWindows(InteropCallback cb, System.IntPtr payload);

    public static void Main()
    {
        var tw = System.Console.Out;
        var twhandle = GCHandle.Alloc(tw);

        InteropCallback cb = CallbackFunc;

        EnumWindows(cb, GCHandle.ToIntPtr(twhandle));
        twhandle.Free();
    }

    private static bool CallbackFunc(int handle, System.IntPtr payload)
    {
        var gch = GCHandle.FromIntPtr(payload);
        var tw = (TextWriter)gch.Target;
        tw.WriteLine(handle);
        return true;
    }
}
1 голос
/ 18 ноября 2011

Я думаю, что вы хотите использовать HandleRef вместо IntPtr, если я правильно понимаю.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...