【问题标题】:Garbage Collector Crashing when using WinAPI Functions使用 WinAPI 函数时垃圾收集器崩溃
【发布时间】:2013-12-16 15:01:30
【问题描述】:

在 D 中,每次启动应用程序时,我的垃圾收集器都会崩溃。

Windows 模块:

pragma(lib, "user32.lib");

import std.string;

extern(Windows) {
    void* CreateWindowExW(uint extendedStyle , 
                          const char* classname,
                          const char* title,
                          uint style,
                          int x, int y,
                          int width, int height,
                          void* parentHandle,
                          void* menuHandle,
                          void* moduleInstance, 
                          void* lParam);
}

class Window {
    private void* handle;
    private string title;

    this(string title, const int x, const int y, const int width, const int height) {
        this.title = title;
        handle = CreateWindowExW(0, null, toStringz(this.title), 0, x, y, width, height, null, null, null, null);

        if(handle == null)
            throw new Exception("Error while creating Window (WinAPI)");
    }
}

主模块:

import std.stdio;

version(Windows) {
    import windows;
    extern (Windows) {
    int WinMain(void* hInstance, void* hPrevInstance, char* lpCmdLine, int nCmdShow) {
        import core.runtime;

        Runtime.initialize();
        scope(exit) Runtime.terminate();
        auto window = new Window("Hello", 0, 0, 0, 0);
        writeln("test");
        return 0;
    }
    }
}

这给了我位置 0 的访问冲突。当我查看反汇编时,它崩溃了

0040986F  mov         ecx,dword ptr [eax]  

此程序集位于_gc_malloc 内。


编辑:这是新代码:

Windows 模块:

pragma(lib, "user32.lib");

import std.utf;

extern(Windows) {
    void* CreateWindowExW(uint extendedStyle , 
                          const wchar* classname,
                          const wchar* title,
                          uint style,
                          int x, int y,
                          int width, int height,
                          void* parentHandle,
                          void* menuHandle,
                          void* moduleInstance, 
                          void* lParam);
}

class Window {
    private void* handle;
    private wstring title;

    this(wstring title, const int x, const int y, const int width, const int height) {
        this.title = title;
        handle = CreateWindowExW(0, null, toUTFz!(wchar*)(this.title), 0, x, y, width, height, null, null, null, null);

        if(handle == null)
            throw new Exception("Error while creating Window (WinAPI)");
    }
}

WinMain:

    int WinMain(void* hInstance, void* hPrevInstance, char* lpCmdLine, int nCmdShow) {
        import core.runtime;

        try {
            Runtime.initialize();
            scope(exit) Runtime.terminate();
            auto window = new Window("Hello", 0, 0, 0, 0);
            writeln("test");
        } catch(Exception ex) {
            writeln(ex.toString);
        }
        return 0;
    }

当我运行第二个代码时,我还会在随机(对我而言)地址上遇到访问冲突。

拆解(在__d_createTrace内):

0040C665  cmp         dword ptr [ecx+1Ch],0  

【问题讨论】:

  • Runtime.initialize 和范围 exit runtime.terminate 都应该在 outside 尝试中,将它们放在函数的最顶部,然后尝试您的代码。现在发生的事情是新窗口抛出异常(就像大卫所说,你需要先注册一个窗口类而不是传递 null),然后在尝试结束时,运行时终止。所以在 catch 内部,这些东西没有设置好,toString 调用无法完成它的工作。所以移动上面的那两条运行时行,你应该会得到一个很好的异常消息框。

标签: winapi memory-management garbage-collection d


【解决方案1】:

David Heffernan 的帖子提供了很好的信息,但不会解决这里的主要问题,即 D 运行时未初始化。你的代码应该抛出一个异常,你创建窗口的参数是错误的,但它应该是访问冲突。

解决这个问题的最简单方法是定义一个常规的main 函数而不是WinMain。 WinMains 在 D 中有效,但如果您定义自己的,它会跳过运行时初始化函数。 (运行时,src/druntime/src/rt/main2.d,如果您有兴趣)定义一个 C 主函数来执行设置任务,然后调用您的 D 主函数。 C main btw 反过来又由 C 运行时从 WinMain 调用。

如果您需要实例或命令行的参数,您可以使用 Windows API 函数 GetModuleHandle(null) 和 GetCommandLineW()。

或者,您可以在 WinMain 函数中自己初始化运行时:

extern (Windows) {
   int WinMain(void* hInstance, void* hPrevInstance, char* lpCmdLine, int   nCmdShow) {
    import core.runtime; // the Runtime functions are in here
    Runtime.initialize(); // initialize it!
        auto window = new Window("Hello", 0, 0, 0, 0);
        return 0;
    }
}

您应该做的另一件事是终止它并捕获异常。默认情况下,Windows 上未捕获的异常将触发系统调试器,因此这并不是一件坏事,但通常在 D 程序中,您期望得到更好的消息。所以做这样的事情:

int WinMain(void* hInstance, void* hPrevInstance, char* lpCmdLine, int nCmdShow) {
    import core.runtime;
    Runtime.initialize();
    scope(exit) Runtime.terminate();
    try
        auto window = new Window("Hello", 0, 0, 100, 100);
    catch(Throwable t)
        MessageBoxW(null, toUTFz!(wchar*)(t.toString()), null, 0);
    return 0;
}

所以我在那里对其进行了初始化,在函数返回时终止,并且还捕获了异常并将其放入消息框中以便于阅读。我在自己的 MessageBoxW 中放置了一个原型,就像您为 CreateWindow 所做的那样。您还可以在http://www.dsource.org/projects/bindings/browser/trunk/win32 处获取更完整的 win32 绑定(我认为无论如何这都是最新的链接。)

不过,你也可以只使用 D 主函数来为你做这种事情。 WinMain 提供更多控制权,但 D 不需要。

【讨论】:

  • 我现在只是在“离开”的反汇编中遇到访问冲突。
【解决方案2】:

CreateWindowExW 采用 16 位字符串而不是 8 位字符串。我不确定如何在 D 中实现这一点。我假设char 是 8 位的,就像它在 Windows 上的 C++ 中一样?我想你可以使用CreateWindowExA,但最好传递 16 位 UTF-16 文本。

lpClassName参数传递null一定是错误的。一个窗口确实需要一个窗口类。

【讨论】:

  • 是的,类型应该是wchar 而不是char。如果您在字符串文字的末尾加上 w 前缀,例如"myclassname"w,它将作为 wchar* 工作。使用 std.utf.toUTFz!wstring(your_data); 将非文字转换为 wchar
  • @AdamD.Ruppe 谢谢!在 D 的上下文中,我不知道任何这些东西!
  • 我刚试过,显然 wstrings 不会隐式转换为指针,所以要么对它们执行 toUTFz,要么使用 ptr 属性,"myclassname"w.ptr。 (我认为这可能是编译器的疏忽,因为 utf-8 字符串文字 do 隐式转换为 char*。D 字符串不一定以零结尾,因此隐式转换通常不好,但由于文字是一种特殊情况,在那里可以,所有字符串文字都有一个零终止符,以便于 C 互操作性。)编辑:显然它也需要toUTFz!(wchar*) 而不是 wstring。奇怪,我认为 wstring 工作......
猜你喜欢
  • 1970-01-01
  • 2015-06-15
  • 1970-01-01
  • 2010-11-20
  • 2018-12-30
  • 1970-01-01
  • 2011-03-10
  • 2015-11-26
  • 2012-03-21
相关资源
最近更新 更多