P/Invoke AccessViolationException 没有意义
首先,很抱歉在这个主题上被问到很多其他问题时发布这样的问题,但我一直在阅读我能找到的所有问题(+谷歌),并且没有一个真正给我任何关于什么的提示我的情况正在发生。
我有一个第三方 .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)。
我向这两个参数传递了完全相同的第一个、第三个和第四个参数,所以我很确定问题是 IntPtr FileHandle 参数(第三个和第四个参数为空,并且根据文档可以为空。提供实际值没有帮助任何一个)。我也确信实际的文件句柄是好的:我在另一个项目中使用与 _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 可能有一些有点问题,所以我把它换成了另一个第三方编译的版本(大小大了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。
感谢您提前抽出时间
First of all, sorry to post a question like this when so many other have been asked on this topic, but I've been reading all the questions I could find (+google), and none has really given me any hints as to what is happening in my case.
I have a 3rd party .dll (libFLAC) with two exported functions with similar name:
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)
The purpose of the first one is to initialize an encoder with a filename. It calls fopen() which on windows (with unicode filenames) is not a good thing in some cases. That's why the developer provides the second function, which can be passed an open FILE* in "w+b" mode (which i'm opening with a call to MSVCRT.DLL _wfopen_s)
I have defined my P/Invoke declarations as follows:
[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);
The (strange?) problem is that the call to the "file" function (lowercase) works great, while the one that takes a FILE* (IntPtr) doesn't work (It throws the AccessViolationException).
I am passing exactly the same 1st, 3rd and 4th parameters to both, so I'm pretty sure the problem is the IntPtr FileHandle parameter (3rd and 4th are null and can be null as per the docs. Providing real values doesn't help either). I am also sure the actual file handle is ok: I am using the exact same code to _wfopen_s() in another project and it's working perfectly. The file is also created before the crash, so it's not a problem with that either.
EDIT: The return value is just a public enum InitStatus: int.
For a moment, i thought that there could be a problem with the functions having almost the same name, so I tried calling them by ordinal, but it didn't work either:
[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
I also thought that maybe the DLL I was using could have some sort of problem, so I switched it for a version compiled by another 3rd party (150K bigger in size), but I get exactly the same problem.
The code I'm using to call:
[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: I don't know if it matters, but I'm using VS2010 under Win7 x64.
Thank you for your time in advance
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
“我不知道这是否重要,但我在 Win7 x64 下使用 VS2010。” - 这很重要!您使用的是 32 位 libFLAC DLL 吗?如果是这样,您需要将项目属性的“构建”选项卡下的“平台目标”设置为“x86”,以强制托管程序集在 32 位模式下运行,以便它与您正在使用的 32 位 DLL 匹配p-调用到。
或者,您可以获得 64 位 libFLAC DLL,然后必须包含 32 位和 64 位版本,并根据
IntPtr.Size
的值调用适当的版本(以确定您是在 32 位模式还是 64 位模式下运行)。"I don't know if it matters, but I'm using VS2010 under Win7 x64." - it matters a lot! Are you using a 32-bit libFLAC DLL? If so, you need to set your "Platform target" under the build tab of the project properties to "x86" in order to force the managed assembly to run in 32-bit mode, so it matches the 32-bit DLL you're p-invoking into.
Or you could obtain a 64-bit libFLAC DLL, and then you'll have to include both the 32-bit and 64-bit versions, and call the appropriate one depending on the value of
IntPtr.Size
(to determine if you're running in 32- or 64-bit mode).这实际上有什么作用? flac 代码似乎需要 FILE*。那是一个指针,而不是一个句柄。 CRT 完全不知道 Windows 句柄。如果 FileHandle 实际上是 Windows 句柄,那么您一定会得到大 Kaboom,句柄是混淆的指针值。
如果不调用您编写的返回所需指针的函数,则无法从 CRT 获取 FILE*。该函数应该调用 fopen (或 _wfopen_s),并且应该使用与 flac 代码完全相同的 CRT 版本。当然,最终你没有领先,不妨调用有效的函数。
What does this actually do? The flac code seems to require a FILE*. That's a pointer, not a handle. The CRT is completely agnostic of Windows handles. If FileHandle is actually a Windows handle then you're guaranteed to get the big Kaboom, handles are obfuscated pointer values.
You cannot get a FILE* from the CRT without pinvoking a function that you write that returns the required pointer. That function should call fopen (or _wfopen_s) and ought to use the exact same version of the CRT as the flac code. In the end you're not ahead of course, might as well call the function that works.