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

发布于 2024-08-30 00:34:38 字数 7739 浏览 2 评论 0原文

我正在为游戏编程库“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 和 class ),但我认为框架创建的方法存根将采取考虑到这一点并将调用路由到有效的类。然而,显然我错了。

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

(我对所有代码感到抱歉!希望不要太多......)

I am writing a wrapper for the game programming library "Allegro" and its less stable 4.9 branch. Now, I have done good insofar, except for when it comes to wrapping a structure of function pointers. Basically, I can't change the original code, despite having access to it, because that would require me to fork it in some manner. I need to know how I can somehow pass a structure of delegates from managed to native without causing an AccessViolationException that has occurred so far.

Now, for the code. Here is the Allegro definition of the structure:

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;

My simple attempt at wrapping it:

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;
}

I have a simple auxiliary wrapper that turns an ALLEGRO_FILE_INTERFACE into an ALLEGRO_FILE, like so:

#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);
}

Lastly, I have a class that accepts a Stream and provides the proper methods to interact with the stream. Just to make sure, here it is:

/// <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;
    }
}

Now, when I do something like this:

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

...I get an AccessViolationException. I think I understand why (the garbage collector can relocate structs and classes whenever), but I'd think that the method stub that is created by the framework would take this into consideration and route the calls to the valid classes. However, it seems obviously so that I'm wrong.

So basically, is there any way I can successfully wrap that structure?

(And I'm sorry for all the code! Hope it's not too much...)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

我不吻晚风 2024-09-06 00:34:38

根据我收集的信息,问题不在于前面列出的任何代码。问题出在调用约定上。默认值为 stdcall,但它应该是 cdecl。那里有令人惊叹的表演。以下解决了该问题:

[UnmanagedFunctionPointer(AllegroFunctions.AllegroCallingConvention)]

...为所有代表添加前缀。完成了,完成了。

From what I have gathered, the problem was not with any code listed previously. The problem lay with the calling convention. The default is stdcall, but it should have been cdecl. Amazing show stopper there. The following fixed the problem:

[UnmanagedFunctionPointer(AllegroFunctions.AllegroCallingConvention)]

...prefixed to all delegates. Done and done.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文