【问题标题】:P/Invoke AccessViolationException that makes no sense没有意义的 P/Invoke AccessViolationException
【发布时间】:2011-04-17 10:52:33
【问题描述】:

首先,很抱歉发布这样的问题,因为这个话题有很多其他人被问到,但我一直在阅读我能找到的所有问题 (+google),但没有一个真正给我任何提示至于我的情况。

我有一个第 3 方 .dll (libFLAC),其中包含两个名称相似的导出函数:

FLAC__StreamEncoderInitStatus FLAC__stream_encoder_init_file (FLAC__StreamEncoder *encoder, const char *filename, FLAC__StreamEncoderProgressCallback progress_callback, void *client_data)

FLAC__StreamEncoderInitStatus FLAC__stream_encoder_init_FILE (FLAC__StreamEncoder *encoder, FILE *file, FLAC__StreamEncoderProgressCallback progress_callback, void *client_data)

第一个的目的是用文件名初始化编码器。它调用 fopen() ,在某些情况下,这在 windows 上(带有 unicode 文件名)并不是一件好事。这就是开发人员提供第二个函数的原因,它可以在“w+b”模式下传递一个打开的 FILE*(我通过调用 MSVCRT.DLL _wfopen_s 打开它)

我已将我的 P/Invoke 声明定义如下:

[DllImport(Globals.LIBFLAC_DLL, EntryPoint = "FLAC__stream_encoder_init_file", CharSet = CharSet.Ansi)]
public static extern InitStatus InitFilenameAnsi(IntPtr Encoder, string Filename, ProgressDelegate ProgressCallback, ref Object ClientData);

[DllImport(Globals.LIBFLAC_DLL, EntryPoint = "FLAC__stream_encoder_init_FILE")]
public static extern InitStatus InitFileHandle(IntPtr Encoder, IntPtr FileHandle, ProgressDelegate ProgressCallback, ref Object ClientData);

(奇怪?)问题是对“文件”函数(小写)的调用效果很好,而使用 FILE* (IntPtr) 的调用不起作用(它会引发 AccessViolationException)。

我将完全相同的第 1、第 3 和第 4 个参数传递给两者,所以我很确定问题出在 IntPtr FileHandle 参数上(第 3 和第 4 个为空,并且根据文档可以为空。提供真实值不会也无济于事)。我也确信实际的文件句柄没问题:我在另一个项目中使用与 _wfopen_s() 完全相同的代码,并且运行良好。该文件也是在崩溃之前创建的,所以这也不是问题。

编辑:返回值只是一个公共枚举 InitStatus: int.

有那么一瞬间,我认为函数名称几乎相同可能有问题,所以我尝试按序号调用它们,但它也不起作用:

[DllImport(Globals.LIBFLAC_DLL, EntryPoint = "#259", CharSet = CharSet.Ansi)] //"FLAC__stream_encoder_init_file" OK

[DllImport(Globals.LIBFLAC_DLL, EntryPoint = "#258")] //"FLAC__stream_encoder_init_FILE" FAILS

我还认为我使用的 DLL 可能存在某种问题,因此我将其切换为由另一个 3rd 方编译的版本(大小增加了 150K),但我遇到了完全相同的问题。

我用来调用的代码:

[TestMethod]
public void ThisFailsMiserably()
{
    Object ClientData = null;
    IntPtr FileHandle = IntPtr.Zero;

    try
    {
        FILE.Open(out FileHandle, "test.flac", "w+b");
        Assert.AreNotEqual(IntPtr.Zero, FileHandle); //Works great! the file is created, and the debugger shows the file handle value.
        StreamEncoder.InitFileHandle(FlacStreamEncoder, FileHandle, null, ref ClientData); //AccessViolationException
        Assert.IsTrue(StreamEncoder.Finish(FlacStreamEncoder));
    }
    finally
    {
        FILE.Close(FileHandle);
    }
}

[TestMethod]
public void ThisWorksGreat()
{
    Object ClientData = null;
    Assert.AreEqual(StreamEncoder.InitStatus.OK, StreamEncoder.InitFilenameAnsi(FlacStreamEncoder, "test.flac", null, ref ClientData));
    Assert.IsTrue(StreamEncoder.Finish(FlacStreamEncoder));
}

PS:我不知道是否重要,但我在Win7 x64下使用VS2010。

提前感谢您的宝贵时间

【问题讨论】:

    标签: c# pinvoke access-violation


    【解决方案1】:

    “我不知道这是否重要,但我在 Win7 x64 下使用 VS2010。” - 这很重要!您使用的是 32 位 libFLAC DLL 吗?如果是这样,您需要将项目属性的构建选项卡下的“平台目标”设置为“x86”,以强制托管程序集以 32 位模式运行,因此它与您的 32 位 DLL 匹配p-invoking into。

    或者您可以获取 64 位 libFLAC DLL,然后您必须同时包含 32 位和 64 位版本,并根据 IntPtr.Size 的值调用适当的版本(以确定是否您在 32 位或 64 位模式下运行)。

    【讨论】:

    • 谢谢,我会尽快试试这个
    【解决方案2】:
        FILE.Open(out FileHandle, "test.flac", "w+b");
    

    这实际上是做什么的? flac 代码似乎需要 FILE*。那是指针,不是句柄。 CRT 与 Windows 句柄完全无关。如果 FileHandle 实际上是一个 Windows 句柄,那么你肯定会得到大的 Kaboom,句柄是混淆的指针值。

    如果不调用您编写的返回所需指针的函数,则无法从 CRT 获取 FILE*。该函数应该调用 fopen(或 _wfopen_s)并且应该使用与 flac 代码完全相同的 CRT 版本。最后你当然不超前了,还不如调用有效的函数。

    【讨论】:

    • 这只是调用 _wfopen_s : [DllImport("msvcrt.dll", EntryPoint = "_wfopen_s", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] public static extern int Open(out IntPtr FileHandle, string Filename, string Mode);
    • 这是为 Microsoft 程序保留的 CRT 实现。不能保证它对 FILE 结构的定义与 CRT 使用的完全一样。
    • 嗯,你是对的,这是有道理的。我也去看看。
    • 虽然这一次不是我的问题,但我下次一定会记住的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-25
    • 1970-01-01
    • 1970-01-01
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多