2

Судя по документации, не совсем ясно что должен вообще реализовывать интерфейс ICustomMarshaler.

Хотелось бы описать маршалер для преобразования массива си строк в управляемый массив строк и обратно.

Есть ли какие-либо примеры или подсказки как это сделать правильно?

Дополнительные классы преобразования описал, вот только неясность осталась с данным интерфейсом.

public class CString : IDisposable
{
    private bool _isDisposed;

    public CString(string str)
    {
        byte[] tmpArr = Encoding.UTF8.GetBytes(str);
        Handle = Marshal.AllocHGlobal(tmpArr.Length + 1);
        Marshal.Copy(tmpArr, 0, Handle, tmpArr.Length);
        Marshal.WriteByte(Handle, tmpArr.Length, (byte) '\0');
        GC.KeepAlive(this);
    }

    public CString(IntPtr unmanagedStrPtr)
    {
        Handle = unmanagedStrPtr;
    }

    public IntPtr Handle { get; }

    public char this[int idx] => (char) Marshal.ReadByte(Handle, idx);

    public void Dispose()
    {
        if (_isDisposed) return;

        Marshal.FreeHGlobal(Handle);
        _isDisposed = true;
    }

    public static implicit operator CString(string str)
    {
        return new CString(str);
    }

    public static implicit operator CString(IntPtr strPtr)
    {
        return new CString(strPtr);
    }

    public static implicit operator string(CString str)
    {
        int size = 0;
        while (Marshal.ReadByte(str.Handle, size) != '\0') size++;

        byte[] arrBytes = new byte[size];
        Marshal.Copy(str.Handle, arrBytes, 0, size);
        return Encoding.UTF8.GetString(arrBytes);
    }

    ~CString()
    {
        Dispose();
        GC.SuppressFinalize(this);
    }
}
public class CStringArray : IDisposable
{
    public IntPtr Handle { get; }
    private CString[] _arrStrings;
    private IntPtr[] _arrPtr;
    private bool _isDisposed;

    public int Length => _arrPtr.Length;

    public CStringArray(string[] arrayStrings)
    {
        _arrStrings = new CString[arrayStrings.Length];
        _arrPtr = new IntPtr[arrayStrings.Length];
        for (int i = 0; i < arrayStrings.Length; i++)
        {
            _arrStrings[i] = arrayStrings[i];
        }

        Handle = Marshal.AllocHGlobal(IntPtr.Size * arrayStrings.Length);

        for (int i = 0; i < _arrPtr.Length; i++)
        {
            _arrPtr[i] = _arrStrings[i].Handle;
        }

        Marshal.Copy(_arrPtr, 0, Handle, _arrPtr.Length);

        GC.KeepAlive(this);
    }

    public CStringArray(IntPtr unmanagedPtr, int length)
    {
        _arrPtr = new IntPtr[length];
        _arrStrings = new CString[length];
        for (int i = 0; i < length; i++)
        {
            _arrPtr[i] = Marshal.ReadIntPtr(unmanagedPtr, IntPtr.Size * i);
            _arrStrings[i] = new CString(_arrPtr[i]);
        }
    }

    public CString this[int idx] => _arrStrings[idx];

    public static implicit operator string[](CStringArray array)
    {
        string[] arr = new string[array.Length];
        for (int i = 0; i < array.Length; i++)
        {
            arr[i] = array[i];
        }
        return arr;
    }

    public static implicit operator CStringArray(string[] strings)
    {
        return new CStringArray(strings);
    }

    public void Dispose()
    {
        if (_isDisposed)
        {
            return;
        }

        for (int i = 0; i < _arrStrings.Length; i++)
        {
            _arrStrings[i].Dispose();
        }

        _arrStrings = null;
        _arrPtr = null;
        _isDisposed = true;
    }

    ~CStringArray()
    {
        Dispose();
        GC.SuppressFinalize(this);
    }
}

На данный момент описал его так:

public class CStringMarshaler : ICustomMarshaler
{
    static CStringMarshaler StaticInstance;

    public void CleanUpManagedData(object ManagedObj)
    {
        if (ManagedObj is CString data)
        {
            data.Dispose();
        }
        else
        {
            throw new BadArgumentException("ManagedObj is not CString");
        }
    }

    public void CleanUpNativeData(IntPtr pNativeData)
    {
        Marshal.FreeHGlobal(pNativeData);
    }

    public int GetNativeDataSize()
    {

        return -1;
    }

    public IntPtr MarshalManagedToNative(object ManagedObj)
    {
        return new CString((string)ManagedObj).Handle;
    }

    public object MarshalNativeToManaged(IntPtr pNativeData)
    {
        return new CString(pNativeData);
    }

    public static ICustomMarshaler GetInstance(string cookie)
    {
        if (StaticInstance == null)
        {
            return StaticInstance = new CStringMarshaler();
        }
        return StaticInstance;
    }
}

Это работает, но не уверен в правильности реализации.

  • А зачем так сложно? Стандартный маршаллер вроде умеет сишные строки. – VladD Oct 07 '18 at 19:38
  • Хотелось бы обходиться без вечных вызовов преобразований тому прочего. Указать один раз тип на делегате, и пользоваться им зная, что нет необходимости постоянно преобразовывать данные из управляемой строки в массив байт UTF8, далее выделять память под строку, записывать массив данных туда и только потом отправлять данные как указатель на выделенную память из кучи, а потом еще вызывать Marshal.FreeHGlobal дабы не было утечек.. –  Oct 07 '18 at 20:14
  • А стандартный маршаллер вроде бы не умеет из UTF8 строк отдавать нормальные данные туда и обратно. –  Oct 07 '18 at 20:15
  • Зачем Marshal.FreeGlobal? Просто [MarshalAs] не работает? – VladD Oct 07 '18 at 20:17
  • Сейчас попробую передать имя файла с символами которые не содержатся в ASCI таблице. –  Oct 07 '18 at 20:18
  • Не забудьте UnmanagedType.LPUTF8Str – VladD Oct 07 '18 at 20:22
  • Ох, это же .NET 4.7+, жалко, вот теперь точно уверен в необходимости кастомного маршалинга. –  Oct 07 '18 at 20:30
  • Эх! Ну хотя бы вот так тогда: https://stackoverflow.com/a/27198771/276994 – VladD Oct 07 '18 at 20:44
  • Ваш финализатор, кстати, неверен. Вы не имеете права обращаться к управляемым полям в финализаторе! https://ru.stackoverflow.com/q/486696/10105 – VladD Oct 08 '18 at 10:31
  • В чем именно это выражается? Обнулением ссылок или вызовом GC.SupressFinilize(this)? –  Oct 08 '18 at 18:01
  • Прошу прощения, не заметил, он у вас и правда только освобождает неуправляемый объект. – VladD Oct 09 '18 at 00:02

0 Answers0