使用WinAPI函数时垃圾回收器崩溃

3
在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  

2
Runtime.initialize 和 scope exit runtime.terminate 都应该在try之外,将它们放在函数的最顶部,然后尝试您的代码。现在发生的是新窗口会抛出一个异常(就像大卫说的那样,您需要先注册一个窗口类,而不是传递null),然后在try结束时,运行时终止。因此,在catch中,这些东西没有设置,toString调用无法完成其工作。因此,请将这两个Runtime行移到try之上,您应该会得到一个漂亮的异常消息框。 - Adam D. Ruppe
2个回答

5

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

最简单的解决方法是定义一个普通的main函数,而不是WinMain。WinMains在D中是有效的,但如果您自己定义,则会跳过druntime初始化函数。(运行时,在src/druntime/src/rt/main2.d中,如果您感兴趣)定义一个C main函数来处理设置任务,然后调用您的D main函数。顺便说一下,C main由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做的那样。你也可以在这里获取更完整的win32绑定 http://www.dsource.org/projects/bindings/browser/trunk/win32(我认为这是最新的链接)。不过,你也可以使用一个D main函数来为你完成这种操作。WinMain可以提供更多的控制,但在D中并不是必需的。

现在我只是在“leave”的反汇编中遇到了访问冲突。 - Jeroen

5
CreateWindowExW需要使用16位字符串而不是8位字符串。我不确定如何在D语言中实现这一点。我认为char在Windows上的C ++中是8位的。你可以使用CreateWindowExA,但最好传递16位的UTF-16文本。
null用作lpClassName参数必须是错误的。窗口需要一个窗口类。

2
是的,类型应该是 wchar 而不是 char。如果你在字符串字面值末尾加上 w 前缀,例如 "myclassname"w,它将作为 wchar* 使用。将非字面值转换为 wchar 的方法是使用 std.utf.toUTFz!wstring(your_data); - Adam D. Ruppe
@AdamD.Ruppe 谢谢!在 D 语言的上下文中,我不了解这些东西。 - David Heffernan
2
我刚试了一下,显然wstrings不能隐式转换为指针,所以要么也对它们进行toUTFz处理,要么使用ptr属性, "myclassname"w.ptr。(我认为这可能是编译器的疏忽,因为utf-8字符串字面量可以隐式转换为char*。D字符串不一定以零结尾,因此隐式转换通常是错误的,但由于字面量是一个特殊情况,在那里是可以的,所有字符串字面量都会获得一个零终止符,以便轻松进行C互操作性。)编辑:显然需要使用toUTFz!(wchar *)而不是wstring。奇怪的是,我以为wstring可以工作... - Adam D. Ruppe

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