使用C#/.NET调用GDAL时出现“无磁盘”错误。

14
我正在使用Tamas Szekeres构建的GDAL,包括C#绑定,在使用C#和.net 4.0的桌面GIS应用程序中。
我将整个GDAL分发包含在可执行文件的子目录中,并具有以下文件夹结构:
\Plugins\GDAL
\Plugins\GDAL\gdal
\Plugins\GDAL\gdal-data
\Plugins\GDAL\proj

我们正在使用EPSG:4326,该软件是使用32位目标构建的,因为GDAL C# API使用p/invoke到32位库(可以尝试64位,因为Tamas提供了这些,但还没有找到时间去做)。
当我运行我的应用程序时,我会得到以下错误。

enter image description here

这个错误通常发生在软件尝试访问一个已经断开连接的设备,例如可移动驱动器。由于会弹出系统对话框,因此无法“捕获”此异常。
使用任何按钮关闭对话框后,软件将按设计继续执行。
该错误发生在第一次调用以下方法时。
OSGeo.OSR.CoordinateTransformation.TransformPoint(double[] inout);

奇怪的问题:

  • 这个错误只在一台电脑上出现(至少目前是这样)
  • 我在其他几台32位和64位计算机上运行了此软件,没有出现问题
  • 编译GDAL代理库后,第一次运行时不会出现错误,但在每次后续运行时都会出现错误
  • 无论是发布版还是调试版,都会发生错误
  • 无论是否附加调试器,都会发生错误
  • 无论是否打开Gdal.UseExceptions或Osr.UseExceptions(),都会发生错误
  • 禁用可移动驱动器可以使错误消失。但我认为这不是真正的解决方案,因为我无法要求客户这样做。

我尝试了以下方法:

  • 捕捉错误
  • 更改GDAL目录和环境设置
  • 更换电脑和操作系统:这个方法有效
  • 使用SysInternals ProcMon跟踪打开的文件,但没有成功,它们似乎都是存在的文件
  • 硬盘失败时,我重新构建了相关计算机,但没有成功。
  • 使用CCleaner“清理”注册表
  • 在执行时,GDAL目录中的文件未发生变化

假设

  • 错误发生在非托管代码中
  • 在GDAL初始化期间,某个路径指向了计算机上不再连接的驱动器
  • 我还假设这仅限于计算机配置错误

配置

  • Windows 7 Pro(专业版)
  • Intel Core i7 920 @ 2,67GHz
  • 12.0 GB RAM(内存)
  • 64-bit OS(操作系统)
  • Drive C: 120 GB SSD with OS, development (Visual Studio 10), etc(包含操作系统、开发工具等的 120GB 固态硬盘)
  • Drive D: 1 TB WD 10,000k with data, not being accessed for data.(不包含数据的 1TB 机械硬盘)

问题

我需要一个指引来捕获这个错误,或者一个工具或技术来帮助我找出是什么导致了这个错误。我不想发布软件而存在某些系统会出现这种行为的可能性。


更新1:根据下面的第一个答案,我查看了GDAL目录中的文件数量,文件数量保持稳定。更新2:我采纳了自己的建议,并使用设备管理器禁用了系统上的所有可移动设备。这“解决”了问题!但这不是真正的解决方案,所以我将保留这个问题的开放状态。如何跟踪哪个软件正在访问哪个可移动设备? - Blue Toque
也许方法调用引起了异常,因此Visual Studio尝试加载库的原始源文件,这些文件可能是从一个驱动器编译而来,在您的计算机上指向一个空驱动器?不过您应该能够通过ProcMon看到。 - CodeCaster
嘿!在这最后的8个小时里需要帮忙吗?如果有任何需要,请告诉我们,因为这似乎有点麻烦。 - Jeremy Thompson
大家好;我觉得我不能授予赏金,因为提供的解决方案都没有找到错误。这些是很好的想法;其中一个关于编写代码来点击按钮的想法很有创意,但绝对是一种可怕的可怕的黑客行为。我将让系统使用此处概述的算法授予一半的赏金:http://meta.stackexchange.com/questions/16065/how-does-the-bounty-system-work - Blue Toque
5个回答

2
我对这个库没有经验,但也许新的视角可以给你一个灵感...
首先,问题描述得非常清晰!显然这个问题已经让你束手无策了...
你提到重新构建后错误不再出现:这个库是否会在运行后生成某种状态文件,存储在二进制目录中?如果是这样,可能它正在将错误的路径信息保存到那个“配置”文件中,试图加速下一次启动。
也许可以扫描这个目录,在“全新构建”和“第一次运行”之间查找变化?
至少你可能会发现一个文件,在关闭时可以清理以避免这个警告...
希望有所帮助

如果您喜欢,可以为我的悬赏点赞以补偿我。我将检查目录以查找在启动期间写出的某种文件。考虑到这一点,可能会有一些写入注册表的内容,这是更难拦截的。有人可以推荐一个拦截注册表写入的工具吗?我猜SysInternals有一个。 - Blue Toque
绝对的SysInternals进程监视器 http://technet.microsoft.com/en-us/sysinternals/bb896645 - Adam

2
也许您可以尝试这样做:
运行diskmgmt.msc
如果我的假设是正确的,Disk 2是可移动磁盘,请更改Disk 2的驱动器号(右键单击)
运行您的应用程序
如果这样可以消除错误,则说明应用程序中有内容在引用旧的驱动器号
这可能涉及到P / invoked库
也许可以参考:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46501。它谈到了gcc如何将驱动器号编译到二进制文件中。

我昨晚添加了一条评论,说明我尝试过这个方法(点赞)。我已经禁用了所有外部驱动器。但我不认为这是一个解决方案,因为我不能告诉客户永久禁用驱动器。由于该库在其他计算机上没有问题,我不确定这是否是驱动器字母的问题。 - Blue Toque
只有在驱动器具有可移动存储并且存储已被移除的情况下才会发生。如果它是一个“低”驱动器字母,比如“E”,那么在许多系统上不太可能发生,因为大多数人会使用较低的字母来存储多个硬盘/分区,这不会导致问题。但我承认这是一个很小的可能性。 - Daniel van Os

1
+1 很好的问题,但是“捕捉”是不可能的

这是一种可怕的解决方案,将在5年后出现在DailyWTF上。但现在它被存储在这里http://www.pinvoke.net/default.aspx/user32.senddlgitemmessage

using Microsoft.VisualBasic;  //this reference is for the Constants.vbNo;  

public partial class Form1 : Form
{
[DllImport("user32.dll")]
static extern IntPtr SendDlgItemMessage(IntPtr hDlg, int nIDDlgItem, uint Msg, UIntPtr wParam, IntPtr lParam);

[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetActiveWindow(IntPtr hWnd);

// For Windows Mobile, replace user32.dll with coredll.dll
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

// Find window by Caption only. Note you must pass IntPtr.Zero as the first parameter.
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);

[DllImport("user32.dll", SetLastError = true)]
static extern uint GetDlgItemText(IntPtr hDlg, int nIDDlgItem,[Out] StringBuilder lpString, int nMaxCount);

public void ClickSaveBoxNoButton()
{
    //In this example, we've opened a Notepad instance, entered some text, and clicked the 'X' to close Notepad.
    //Of course we received the 'Do you want to save...' message, and we left it sitting there. Now on to the code...
    //
    //Note: this example also uses API calls to FindWindow, GetDlgItemText, and SetActiveWindow.
    //    You'll have to find those separately.

    //Find the dialog box (no need to find a "parent" first)
    //classname is #32770 (dialog box), dialog box title is Notepad
    IntPtr theDialogBoxHandle; // = null;
    string theDialogBoxClassName = "#32770";
    string theDialogBoxTitle = "Notepad";
    int theDialogItemId = Convert.ToInt32("0xFFFF", 16);
    StringBuilder theDialogTextHolder = new StringBuilder(1000);
    //hardcoding capacity - represents maximum text length
    string theDialogText = string.Empty;
    string textToLookFor = "Do you want to save changes to Untitled?";
    bool isChangeMessage = false;
    IntPtr theNoButtonHandle; // = null;
    int theNoButtonItemId = (int)Constants.vbNo;
    //actual Item ID = 7
    uint theClickMessage = Convert.ToUInt32("0x00F5", 16);
    //= BM_CLICK value
    uint wParam = 0;
    uint lParam = 0;

    //Get a dialog box described by the specified info
    theDialogBoxHandle = FindWindow(theDialogBoxClassName, theDialogBoxTitle);
    //a matching dialog box was found, so continue
    if (theDialogBoxHandle != IntPtr.Zero)
    {

        //then get the text
        GetDlgItemText(theDialogBoxHandle, theDialogItemId, theDialogTextHolder, theDialogTextHolder.Capacity);
        theDialogText = theDialogTextHolder.ToString();

    }

    //Make sure it's the right dialog box, based on the text we got.
    isChangeMessage = Regex.IsMatch(theDialogText, textToLookFor);


    if ((isChangeMessage))
    {
        //Set the dialog box as the active window
        SetActiveWindow(theDialogBoxHandle);

        //And, click the No button
        SendDlgItemMessage(theDialogBoxHandle, theNoButtonItemId, theClickMessage, (System.UIntPtr)wParam, (System.IntPtr)lParam);

    }

}

0

嗨,在StackOverflow答案中,通常包括链接内容的摘要或特别回答问题的亮点。SE网站的目标是成为未来多年的知识和答案资源。对于仅包含链接的答案,操作员必须查找其他资源以找到他/她可能不确定的答案。最重要的是,如果您的链接出现问题,您的答案对于未来访问此页面的任何人都是无用的。考虑对您的答案进行[编辑]以添加更多细节。祝好运! - Jeremy Thompson
感谢您提供的GDAL错误处理程序提示;我忘了提到我已经尝试过了。 - Blue Toque

0

事实证明,没有确切的方法来回答这个问题。最终,我通过发现系统上注册了一些不存在的硬件来“解决”这个问题。仍然有一个谜团困扰着我,为什么在几年后只有GDAL才能引发这个错误。

我将无法捕获此异常归因于与p/invoke相关的特殊性以及在系统的非常低层次上抛出的硬件错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接