Я пишу обертку для библиотеки программирования игр "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 в любое время), но я думаю, что заглушка метода, созданная платформой, примет это во внимание и направит вызовы к действительному классы. Однако, очевидно, что я ошибаюсь.
Так в принципе, есть ли способ, которым я могу успешно обернуть эту структуру?
(И я прошу прощения за весь код! Надеюсь, это не слишком много ...)