是否可以将委托结构从托管传递给本机?

时间:2010-04-26 21:31:12

标签: c# delegates marshalling

我正在为游戏编程库“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。我想我理解为什么(垃圾收集器可以随时重新定位structclass es),​​但我认为框架创建的方法存根会考虑到这一点并路由调用有效的类。但是,显然我错了。

基本上,有什么方法可以成功地包装那个结构吗?

(我很抱歉所有的代码!希望它不是太多......)

1 个答案:

答案 0 :(得分:0)

根据我收集的内容,问题不在于之前列出的任何代码。问题在于调用约定。默认值为stdcall,但应为cdecl。惊人的节目塞在那里。以下解决了问题:

[UnmanagedFunctionPointer(AllegroFunctions.AllegroCallingConvention)]

...以所有代表为前缀。做完了。