Можно ли передать структуру делегатов от управляемых к родным? - PullRequest
0 голосов
/ 27 апреля 2010

Я пишу обертку для библиотеки программирования игр "Allegro" и ее менее стабильной ветки 4.9. Теперь я хорошо поработал, если не считать обертывания структуры указателей на функции. По сути, я не могу изменить исходный код, несмотря на то, что у меня есть к нему доступ, потому что для этого мне потребуется каким-то образом его обработать. Мне нужно знать, как я могу каким-то образом передать структуру делегатов из управляемых в нативную, не вызывая AccessViolationException, что уже произошло.

Теперь по коду. Вот определение структуры Allegro:

typedef struct ALLEGRO_FILE_INTERFACE
{
   AL_METHOD(ALLEGRO_FILE*, fi_fopen, (const char *path, const char *mode));
   AL_METHOD(void,    fi_fclose, (ALLEGRO_FILE *handle));
   AL_METHOD(size_t,  fi_fread, (ALLEGRO_FILE *f, void *ptr, size_t size));
   AL_METHOD(size_t,  fi_fwrite, (ALLEGRO_FILE *f, const void *ptr, size_t size));
   AL_METHOD(bool,    fi_fflush, (ALLEGRO_FILE *f));
   AL_METHOD(int64_t, fi_ftell, (ALLEGRO_FILE *f));
   AL_METHOD(bool,    fi_fseek, (ALLEGRO_FILE *f, int64_t offset, int whence));
   AL_METHOD(bool,    fi_feof, (ALLEGRO_FILE *f));
   AL_METHOD(bool,    fi_ferror, (ALLEGRO_FILE *f));
   AL_METHOD(int,     fi_fungetc, (ALLEGRO_FILE *f, int c));
   AL_METHOD(off_t,   fi_fsize, (ALLEGRO_FILE *f));
} ALLEGRO_FILE_INTERFACE;

Моя простая попытка обернуть его:

public delegate IntPtr AllegroInternalOpenFileDelegate(string path, string mode);
public delegate void AllegroInternalCloseFileDelegate(IntPtr file);
public delegate int AllegroInternalReadFileDelegate(IntPtr file, IntPtr data, int size);
public delegate int AllegroInternalWriteFileDelegate(IntPtr file, IntPtr data, int size);   
public delegate bool AllegroInternalFlushFileDelegate(IntPtr file);
public delegate long AllegroInternalTellFileDelegate(IntPtr file);
public delegate bool AllegroInternalSeekFileDelegate(IntPtr file, long offset, int where);
public delegate bool AllegroInternalIsEndOfFileDelegate(IntPtr file);
public delegate bool AllegroInternalIsErrorFileDelegate(IntPtr file);
public delegate int AllegroInternalUngetCharFileDelegate(IntPtr file, int c);
public delegate long AllegroInternalFileSizeDelegate(IntPtr file);

[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct AllegroInternalFileInterface
{
    [MarshalAs(UnmanagedType.FunctionPtr)]
    public AllegroInternalOpenFileDelegate fi_fopen;

    [MarshalAs(UnmanagedType.FunctionPtr)]
    public AllegroInternalCloseFileDelegate fi_fclose;

    [MarshalAs(UnmanagedType.FunctionPtr)]
    public AllegroInternalReadFileDelegate fi_fread;

    [MarshalAs(UnmanagedType.FunctionPtr)]
    public AllegroInternalWriteFileDelegate fi_fwrite;

    [MarshalAs(UnmanagedType.FunctionPtr)]
    public AllegroInternalFlushFileDelegate fi_fflush;

    [MarshalAs(UnmanagedType.FunctionPtr)]
    public AllegroInternalTellFileDelegate fi_ftell;

    [MarshalAs(UnmanagedType.FunctionPtr)]
    public AllegroInternalSeekFileDelegate fi_fseek;

    [MarshalAs(UnmanagedType.FunctionPtr)]
    public AllegroInternalIsEndOfFileDelegate fi_feof;

    [MarshalAs(UnmanagedType.FunctionPtr)]
    public AllegroInternalIsErrorFileDelegate fi_ferror;

    [MarshalAs(UnmanagedType.FunctionPtr)]
    public AllegroInternalUngetCharFileDelegate fi_fungetc;

    [MarshalAs(UnmanagedType.FunctionPtr)]
    public AllegroInternalFileSizeDelegate fi_fsize;
}

У меня есть простая вспомогательная оболочка, которая превращает ALLEGRO_FILE_INTERFACE в ALLEGRO_FILE, вот так:

#define ALLEGRO_NO_MAGIC_MAIN
#include <allegro5/allegro5.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

__declspec(dllexport) ALLEGRO_FILE * al_aux_create_file(ALLEGRO_FILE_INTERFACE * fi)
{
    ALLEGRO_FILE * file;

    assert(fi && "`fi' null");

    file = (ALLEGRO_FILE *)malloc(sizeof(ALLEGRO_FILE));

    if (!file)
        return NULL;

    file->vtable = (ALLEGRO_FILE_INTERFACE *)malloc(sizeof(ALLEGRO_FILE_INTERFACE));

    if (!(file->vtable))
    {
        free(file);

        return NULL;
    }

    memcpy(file->vtable, fi, sizeof(ALLEGRO_FILE_INTERFACE));

    return file;
}

__declspec(dllexport) void al_aux_destroy_file(ALLEGRO_FILE * f)
{
    assert(f && "`f' null");
    assert(f->vtable && "`f->vtable' null");

    free(f->vtable);
    free(f);
}

Наконец, у меня есть класс, который принимает Stream и предоставляет надлежащие методы для взаимодействия с потоком. Просто чтобы убедиться, вот оно:

/// <summary>
/// A semi-opaque data type that allows one to load fonts, etc from a stream.
/// </summary>
public class AllegroFile : AllegroResource, IDisposable
{
    AllegroInternalFileInterface fileInterface;
    Stream fileStream;

    /// <summary>
    /// Gets the file interface.
    /// </summary>
    internal AllegroInternalFileInterface FileInterface
    {
        get { return fileInterface; }
    }

    /// <summary>
    /// Constructs an Allegro file from the stream provided.
    /// </summary>
    /// <param name="stream">The stream to use.</param>
    public AllegroFile(Stream stream)
    {
        fileStream = stream;

        fileInterface = new AllegroInternalFileInterface();
        fileInterface.fi_fopen = Open;
        fileInterface.fi_fclose = Close;
        fileInterface.fi_fread = Read;
        fileInterface.fi_fwrite = Write;
        fileInterface.fi_fflush = Flush;
        fileInterface.fi_ftell = GetPosition;
        fileInterface.fi_fseek = Seek;
        fileInterface.fi_feof = GetIsEndOfFile;
        fileInterface.fi_ferror = GetIsError;
        fileInterface.fi_fungetc = UngetCharacter;
        fileInterface.fi_fsize = GetLength;

        Resource = AllegroFunctions.al_aux_create_file(ref fileInterface);

        if (!IsValid)
            throw new AllegroException("Unable to create file");
    }

    /// <summary>
    /// Disposes of all resources.
    /// </summary>
    ~AllegroFile()
    {
        Dispose();
    }

    /// <summary>
    /// Disposes of all resources used.
    /// </summary>
    public void Dispose()
    {
        if (IsValid)
        {
            Resource = IntPtr.Zero; // Should call AllegroFunctions.al_aux_destroy_file

            fileStream.Dispose();
        }
    }

    IntPtr Open(string path, string mode)
    {
        return IntPtr.Zero;
    }

    void Close(IntPtr file)
    {
        fileStream.Close();
    }

    int Read(IntPtr file, IntPtr data, int size)
    {
        byte[] d = new byte[size];
        int read = fileStream.Read(d, 0, size);

        Marshal.Copy(d, 0, data, size);

        return read;
    }

    int Write(IntPtr file, IntPtr data, int size)
    {
        byte[] d = new byte[size];

        Marshal.Copy(data, d, 0, size);

        fileStream.Write(d, 0, size);

        return size;
    }

    bool Flush(IntPtr file)
    {
        fileStream.Flush();

        return true;
    }

    long GetPosition(IntPtr file)
    {
        return fileStream.Position;
    }

    bool Seek(IntPtr file, long offset, int whence)
    {
        SeekOrigin origin = SeekOrigin.Begin;

        if (whence == 1)
            origin = SeekOrigin.Current;
        else if (whence == 2)
            origin = SeekOrigin.End;

        fileStream.Seek(offset, origin);

        return true;
    }

    bool GetIsEndOfFile(IntPtr file)
    {
        return fileStream.Position == fileStream.Length;
    }

    bool GetIsError(IntPtr file)
    {
        return false;
    }

    int UngetCharacter(IntPtr file, int character)
    {
        return -1;
    }

    long GetLength(IntPtr file)
    {
        return fileStream.Length;
    }
}

Теперь, когда я делаю что-то вроде этого:

AllegroFile file = new AllegroFile(new FileStream("Test.bmp", FileMode.Create, FileAccess.ReadWrite));
bitmap.SaveToFile(file, ".bmp");

... я получаю AccessViolationException. Я думаю, что понимаю почему (сборщик мусора может перемещать struct s и class es в любое время), но я думаю, что заглушка метода, созданная платформой, примет это во внимание и направит вызовы к действительному классы. Однако, очевидно, что я ошибаюсь.

Так в принципе, есть ли способ, которым я могу успешно обернуть эту структуру?

(И я прошу прощения за весь код! Надеюсь, это не слишком много ...)

1 Ответ

0 голосов
/ 27 апреля 2010

Из того, что я понял, проблема была не с каким-либо кодом, перечисленным ранее. Проблема заключалась в соглашении о вызовах. По умолчанию stdcall, но это должно было быть cdecl. Удивительное шоу-пробка там. Следующее исправило проблему:

[UnmanagedFunctionPointer(AllegroFunctions.AllegroCallingConvention)]

... префикс для всех делегатов. Сделано и сделано.

...