On Friday, 14 November 2025 at 23:06:00 UTC, stpettersens wrote:
Hi there.

I am trying to build a Python extension (module) with D and the pyd (https://github.com/ariovistus/pyd) and dub and ldc2 compiler on Windows.

I have the following dub.sdl:

```
name "hello"
description "Python bindings module for human-datetime.d library."
authors "Sam Saint-Pettersen"
copyright "Copyright 2025 Sam Saint-Pettersen"
license "MIT"
dependency "pyd" version="~>0.14.5"
subConfiguration "pyd" "python313"
targetType "dynamicLibrary"
dflags "-g" "-w"
sourcePaths "source"
lflags "/LIBPATH:C:\\Dev\\Python313\\libs" platform="windows"
libs "python313" platform="windows"
```

source/hello.d (module code):

```d
module hello;

import pyd.pyd;
import std.stdio;

extern(C) void hello() {
    writefln("Hello, world!");
}

extern(C) void PydMain() {
    def!(hello);
    module_init();
}
```

Build batch file (build.bat):

```cmd
@cls
@set PYTHON_INCLUDE_DIR=C:\Dev\Python313\include
@set PYTHON_LIBRARY=C:\Dev\Python313\libs\python313.lib
@dub clean
@dub build --compiler=ldc2 --force
@dumpbin /exports hello.dll | findstr PyInit_hello
@copy druntime-ldc-shared.dll C:\Dev\Python313\Lib\site-packages
@copy phobos2-ldc-shared.dll C:\Dev\Python313\Lib\site-packages
@copy hello.dll C:\Dev\Python313\Lib\site-packages\hello.pyd
```

The problem I'm having is:

```
ImportError: dynamic module does not define module export function (PyInit_hello)
```

This is because the built **hello.pyd** does not seem to be exporting the entry function. As evident by empty output from `dumpbin /exports hello.dll | findstr PyInit_hello`.

Does anyone who has used **ariovistus/pyd** here know what I'm doing wrong?
I understand that maybe this is a niche question.

Thanks in advance.

~ Sam.

It sounds like you're encountering a common issue when building Python extensions with D and `pyd` on Windows: the `PyInit_hello` function, which Python expects to find, isn't being exported from your `hello.dll` (which you rename to `hello.pyd`).

Let's break down why this might be happening and how to fix it.

**Understanding the Problem**

When Python imports a dynamic module (like your `hello.pyd`), it looks for a specific initialization function. For a module named `hello`, Python 3 expects a function called `PyInit_hello`. The `ImportError` you're seeing confirms that Python can't find this function.

The `dumpbin` output further reinforces this: if `PyInit_hello` isn't listed in the exports, it's not being made available to external linkers or the Python interpreter.

**Why `PyInit_hello` might be missing or not exported:**

1. **Mismatched Naming Convention:** While `pyd` handles a lot of the boilerplate, sometimes there can be subtle differences in how the D compiler (LDC2 in your case) generates the symbol, or how `pyd` expects it to be declared. 2. **`extern(C)` for the wrong function:** You have `extern(C) void hello()`, but the crucial function for Python's initialization is `PydMain`, which `pyd` then uses to set up the actual `PyInit_hello`. The `PydMain` function itself needs to be visible to `pyd`'s internal mechanisms, and potentially exposed in a way that allows `pyd` to generate the `PyInit_hello` symbol. 3. **Linker Issues:** Even if the symbol is generated, the linker might not be exporting it correctly from the DLL. 4. **`pyd` version or configuration:** Though you're using a specific subConfiguration, there might be nuances with how `pyd` generates the `PyInit_` function for Windows with LDC2.

**Proposed Solutions and Things to Check:**

Here's a step-by-step approach to debug and resolve this:

    1.  **Ensure `PydMain` is Public and Externally Linkable:**
While `pyd` takes care of a lot, ensure your `PydMain` is set up correctly for `pyd` to do its magic. The `pyd` library typically generates the `PyInit_yourmodulename` function based on internal mechanisms. Your `PydMain` is the entry point for `pyd` itself.

        Let's refine your `source/hello.d`:

        ```d
        module hello;

        import pyd.pyd;
        import std.stdio;

        // This is your D function that will be exposed to Python
extern(C) void hello_world() { // Renamed to avoid potential conflict with module name
            writefln("Hello from D!");
        }

// PydMain is the entry point for pyd to initialize your Python module // pyd handles the generation of PyInit_hello behind the scenes based on this.
        // Ensure PydMain is public so it's accessible.
        public extern(C) void PydMain() {
            // Define your D functions to be accessible in Python
def!("hello", hello_world); // Python will call this as hello.hello()

// This is crucial for pyd to finalize the module setup and create PyInit_hello
            module_init();
        }
        ```
**Key Change:** I've explicitly made `PydMain` `public` and renamed `hello` to `hello_world` just to be absolutely clear. The name for Python when you `def!` it (`"hello"`) is what will appear in Python, e.g., `import hello; hello.hello()`.

    2.  **Verify `dub.sdl` Configuration:**
Your `dub.sdl` looks mostly correct, but let's double-check the `targetType` and `subConfiguration`.

        ```sdl
        name "hello"
description "Python bindings module for human-datetime.d library."
        authors "Sam Saint-Petrusen"
        copyright "Copyright 2025 Sam Saint-Petrusen"
        license "MIT"
        dependency "pyd" version="~>0.14.5"
subConfiguration "pyd" "python313" # This looks correct for Python 3.13
        targetType "dynamicLibrary"
        dflags "-g" "-w"
        sourcePaths "source"

# Ensure these are correct for YOUR Python 3.13 installation # The LFLAGS are critical for the linker to find the Python import library lflags "/LIBPATH:C:\\Dev\\Python313\\libs" platform="windows"
        libs "python313" platform="windows"

# Add an explicit export flag for Windows, although pyd usually handles this.
        # Sometimes, with LDC2, it's beneficial to be explicit.
# This might not be strictly necessary with pyd, but good for debugging. # You might try adding: lflags "/EXPORT:PyInit_hello" platform="windows" # However, pyd is supposed to generate this, so it might interfere.
        # Let's rely on pyd first.
        ```

    3.  **Examine `dub build` Output:**
When you run `dub build --compiler=ldc2 --force`, carefully inspect the output. Look for any warnings or errors related to symbol creation, linking, or any messages from `pyd` during its compilation stages.

    4.  **Confirm Python Installation and `dub`'s Access:**
* Are you absolutely sure `C:\Dev\Python313` is the correct path to your Python 3.13 installation? * Can `dub` and `ldc2` access these paths? Environment variables `PYTHON_INCLUDE_DIR` and `PYTHON_LIBRARY` are good, but the `lflags` in `dub.sdl` are paramount.

5. **Use `nm` (or equivalent for D) or `dumpbin` more broadly:** Instead of just `findstr PyInit_hello`, try running `dumpbin /exports hello.dll` without the filter. This will show you *all* exported functions. Look for anything resembling `PyInit_hello`, `_PyInit_hello`, or mangled names that might correspond.

Sometimes, compilers (especially on Windows) can add decorations to function names (e.g., `_PyInit_hello@0`). If you see a decorated name, you might need to adjust something, though `pyd` generally aims to abstract this.

6. **Temporary Debugging: Manual `PyInit_hello` (Less Ideal for `pyd`):** For the sake of *absolute confirmation* that the export mechanism works, you could, as a temporary debug step, try to *manually* define `PyInit_hello` in D, though this bypasses `pyd`'s primary role for that specific function.

        ```d
// **DO NOT USE THIS PERMANENTLY WITH PYD, IT'S FOR DEBUGGING ONLY**
        import std.stdio;
// You'd need to include the Python C API headers if doing this manually // import Python; // This isn't D, but illustrates the idea.

        extern(C) void hello_manual_export() {
            writefln("Hello from manually exported function!");
        }

// This is NOT how pyd works, but if you were exporting manually:
        // extern(C) PyObject* PyInit_hello() {
        //     // manual module initialization here
        //     // return PyModule_Create(&hellomodule);
        // }
        ```
This is mostly to illustrate that `pyd` is *supposed* to generate `PyInit_hello` *for you*. The problem isn't that you haven't written `PyInit_hello` yourself, but that `pyd` isn't causing it to be exported.

    7.  **Consider `pyd`'s internals or examples:**
Have you looked at the `pyd` repository for specific examples or issues related to Windows and LDC2? There might be a subtle configuration for LDC2 on Windows that `pyd` expects.

**Refined `build.bat` (Minor Improvement):**

```batch
@echo off
@set PYTHON_INCLUDE_DIR=C:\Dev\Python313\include
@set PYTHON_LIBRARY=C:\Dev\Python313\libs\python313.lib

echo Cleaning existing build...
@dub clean

    echo Building with LDC2...
    @dub build --compiler=ldc2 --force
    if %errorlevel% neq 0 (
        echo DUB build failed. Exiting.
        goto :eof
    )

    echo Checking for PyInit_hello export...
    @dumpbin /exports hello.dll | findstr PyInit_hello
    if %errorlevel% neq 0 (
        echo WARNING: PyInit_hello not found in exports!
    ) else (
        echo PyInit_hello found in exports.
    )

    echo Copying runtime libraries...
@copy druntime-ldc-shared.dll C:\Dev\Python313\Lib\site-packages @copy phobos2-ldc-shared.dll C:\Dev\Python313\Lib\site-packages
    if %errorlevel% neq 0 (
        echo Failed to copy D runtime libraries. Exiting.
        goto :eof
    )

    echo Copying hello.dll to hello.pyd...
    @copy hello.dll C:\Dev\Python313\Lib\site-packages\hello.pyd
    if %errorlevel% neq 0 (
        echo Failed to copy hello.pyd. Exiting.
        goto :eof
    )

echo Build and deployment complete.
```
This batch file adds some basic error checking, which can be helpful during debugging.

**The Most Likely Culprit:**

Given your setup, the most likely cause is still related to **how `pyd` interacts with LDC2 on Windows to generate and export the `PyInit_hello` symbol.** The `module_init()` call in `PydMain()` is where `pyd` should be generating this, but if the compiler or linker isn't instructed correctly (either by `pyd` or by your `dub.sdl`), it might not become an *exported* symbol.

**Final Recommendation:**

1. **Strictly use the `source/hello.d` from step 1 (with `public extern(C) void PydMain()` and `def!("hello", hello_world)`).**
2.  **Run the `build.bat` with the added error checks.**
3. **Run `dumpbin /exports hello.dll` *without* `findstr`** and paste the full output. This is crucial for seeing if *any* `PyInit_` symbol or other relevant symbols are being generated and exported.

If after trying the updated `source/hello.d` you still face issues, posting the complete `dumpbin /exports hello.dll` output will be very helpful.

Building Python extensions can be tricky across different compilers and platforms, but with `pyd`, it should be manageable. If you're looking for expert assistance with Python development, consider to [hire skilled Python developers](https://www.cmarix.com/hire-python-developers.html) from CMARIX Infotech who can help navigate complex integration challenges like this.

Good luck!

Reply via email to