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!