diff --git a/doc/next/3-tools.md b/doc/next/3-tools.md
index 730e5e0..cd4baba 100644
--- a/doc/next/3-tools.md
+++ b/doc/next/3-tools.md
@@ -2,6 +2,10 @@
### Go command {#go-command}
+When building for Windows with `-buildmode=c-shared` and no `-o` flag,
+`go build` now names the output file with a `.dll` extension.
+Previously the file had no extension. <!-- go.dev/issue/38244 -->
+
### Cgo {#cgo}
### Vet {#vet}
diff --git a/src/cmd/go/internal/work/action.go b/src/cmd/go/internal/work/action.go
index 5e27ff6..51cabfd 100644
--- a/src/cmd/go/internal/work/action.go
+++ b/src/cmd/go/internal/work/action.go
@@ -941,9 +941,9 @@
// in an otherwise empty subdirectory named exe to avoid
// naming conflicts. The only possible conflict is if we were
// to create a top-level package named exe.
- name := "a.out"
+ name := "a.out" + cfg.ExeSuffix
if p.Internal.ExeName != "" {
- name = p.Internal.ExeName
+ name = p.Internal.ExeName + cfg.ExeSuffix
} else if (cfg.Goos == "darwin" || cfg.Goos == "windows") && cfg.BuildBuildmode == "c-shared" && p.Target != "" {
// On OS X, the linker output name gets recorded in the
// shared library's LC_ID_DYLIB load command.
@@ -952,9 +952,10 @@
// we'll install it as; otherwise the library is only loadable as "a.out".
// On Windows, DLL file name is recorded in PE file
// export section, so do like on OS X.
+ // The target name is used as is, without adding cfg.ExeSuffix.
_, name = filepath.Split(p.Target)
}
- a.Target = a.Objdir + filepath.Join("exe", name) + cfg.ExeSuffix
+ a.Target = a.Objdir + filepath.Join("exe", name)
a.built = a.Target
b.addTransitiveLinkDeps(s, a, exportAction, "")
diff --git a/src/cmd/go/internal/work/init.go b/src/cmd/go/internal/work/init.go
index 9226dd3..cee8ad2 100644
--- a/src/cmd/go/internal/work/init.go
+++ b/src/cmd/go/internal/work/init.go
@@ -253,8 +253,7 @@
case "linux", "android", "freebsd":
codegenArg = "-shared"
case "windows":
- // Do not add usual .exe suffix to the .dll file.
- cfg.ExeSuffix = ""
+ cfg.ExeSuffix = ".dll"
}
}
ldBuildmode = "c-shared"
diff --git a/src/cmd/go/testdata/script/build_cshared_windows_dll.txt b/src/cmd/go/testdata/script/build_cshared_windows_dll.txt
new file mode 100644
index 0000000..0816f38
--- /dev/null
+++ b/src/cmd/go/testdata/script/build_cshared_windows_dll.txt
@@ -0,0 +1,51 @@
+# Issue #38244: when building for Windows with -buildmode=c-shared
+# and no -o flag, the output file should have a .dll extension.
+
+[compiler:gccgo] skip 'gccgo does not cross-compile to windows here'
+
+# Cross-compile with -n so that no C toolchain for Windows is needed.
+env GOOS=windows
+env GOARCH=amd64
+env CGO_ENABLED=1
+
+go build -n -buildmode=c-shared
+stderr ' -o mylib\.dll '
+stderr '^mv \$WORK[/\\]b001[/\\]exe[/\\]mylib\.dll mylib\.dll$'
+
+# An explicit -o name is used as is.
+go build -n -buildmode=c-shared -o other.dll
+stderr ' -o other\.dll '
+stderr '^mv \$WORK[/\\]b001[/\\]exe[/\\]other\.dll other\.dll$'
+
+go build -n -buildmode=c-shared -o noext
+stderr ' -o noext '
+stderr '^mv \$WORK[/\\]b001[/\\]exe[/\\]noext noext$'
+
+# Build for real on Windows hosts that support c-shared.
+[!GOOS:windows] stop
+[short] stop
+[!cgo] stop
+[!buildmode:c-shared] stop
+
+env GOOS=
+env GOARCH=
+env CGO_ENABLED=
+
+go build -buildmode=c-shared
+exists mylib.dll
+exists mylib.h
+! exists mylib
+
+-- go.mod --
+module example.com/mylib
+
+go 1.27
+-- main.go --
+package main
+
+import "C"
+
+//export Hello
+func Hello() {}
+
+func main() {}