Skip to content

Commit 4e035d7

Browse files
committed
Use ARm64EC libraries for X64 support on ARM64 systems
1 parent 55a4670 commit 4e035d7

File tree

8 files changed

+115
-57
lines changed

8 files changed

+115
-57
lines changed

nvdaHelper/archBuild_sconscript

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,21 +68,23 @@ def COMProxyDllBuilder(env, target, source, proxyClsid):
6868

6969
env.AddMethod(COMProxyDllBuilder, "COMProxyDll")
7070

71-
# We only support compiling with MSVC 14.2 (2019) or newer
72-
if not env.get("MSVC_VERSION") or tuple(map(int, env.get("MSVC_VERSION").split("."))) < (14, 2):
73-
raise RuntimeError("Visual C++ 14.2 (Visual Studio 2019) or newer not found")
71+
# We only support compiling with MSVC 14.3 (2022) or newer
72+
if not env.get("MSVC_VERSION") or tuple(map(int, env.get("MSVC_VERSION").split("."))) < (14, 3):
73+
raise RuntimeError("Visual C++ 14.3 (Visual Studio 2022) or newer not found")
7474

7575
PYTHON_PLATFORM = sysconfig.get_platform()
7676
TARGET_ARCH = env["TARGET_ARCH"]
77+
isArm64EC = env.get("isArm64EC", False)
7778

7879

7980
isNVDACoreArch = (
8081
(PYTHON_PLATFORM == "win32" and TARGET_ARCH == "x86")
8182
or (PYTHON_PLATFORM == "win-amd64" and TARGET_ARCH == "x86_64")
8283
or (PYTHON_PLATFORM == "win-arm64" and TARGET_ARCH == "arm64")
8384
)
84-
85-
85+
isNVDAHelperLocalArch = isNVDACoreArch or (
86+
PYTHON_PLATFORM == "win-amd64" and TARGET_ARCH == "arm64" and isArm64EC
87+
)
8688
debug = env["nvdaHelperDebugFlags"]
8789
release = env["release"]
8890
signExec = env["signExec"] if (bool(env["certFile"]) ^ bool(env["apiSigningToken"])) else None
@@ -124,7 +126,7 @@ env.Append(
124126
if TARGET_ARCH == "x86_64":
125127
env.Append(MIDLFLAGS="/x64")
126128
elif TARGET_ARCH == "arm64":
127-
env.Append(MIDLFLAGS="/arm64")
129+
env.Append(MIDLFLAGS="/x64" if isArm64EC else "/arm64")
128130
else:
129131
env.Append(MIDLFLAGS="/win32")
130132

@@ -160,6 +162,11 @@ env.Append(
160162
]
161163
)
162164

165+
if isArm64EC:
166+
env.Append(CCFLAGS="/arm64EC")
167+
env.Append(LINKFLAGS="/machine:arm64ec")
168+
env.Append(ARFLAGS="/machine:arm64ec")
169+
163170
if "debugCRT" in debug:
164171
env.Append(CCFLAGS=["/MTd"])
165172
else:
@@ -221,7 +228,7 @@ Export("detoursLib")
221228
apiHookObj = env.Object("apiHook", "common/apiHook.cpp")
222229
Export("apiHookObj")
223230

224-
if isNVDACoreArch:
231+
if isNVDAHelperLocalArch:
225232
localLib = env.SConscript("local/sconscript")
226233
Export("localLib")
227234
if signExec:
@@ -250,11 +257,14 @@ if signExec:
250257
env.AddPostAction(remoteLib[0], [signExec])
251258
env.Install(libInstallDir, remoteLib)
252259

253-
remoteLoaderProgram = env.SConscript("remoteLoader/sconscript")
254-
if signExec:
255-
env.AddPostAction(remoteLoaderProgram, [signExec])
256-
env.Install(libInstallDir, remoteLoaderProgram)
260+
if not isNVDAHelperLocalArch:
261+
remoteLoaderProgram = env.SConscript("remoteLoader/sconscript")
262+
if signExec:
263+
env.AddPostAction(remoteLoaderProgram, [signExec])
264+
env.Install(libInstallDir, remoteLoaderProgram)
257265

258266
if isNVDACoreArch:
259267
thirdPartyEnv.SConscript("espeak/sconscript")
260268
thirdPartyEnv.SConscript("liblouis/sconscript")
269+
thirdPartyEnv.SConscript("javaAccessBridge/sconscript")
270+
thirdPartyEnv.SConscript("uwp/sconscript")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# Copyright (C) 2019-2025 NV Access Limited, Leonard de Ruijter
3+
# This file may be used under the terms of the GNU General Public License, version 2 or later, as modified by the NVDA license.
4+
# For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt
5+
6+
Import(
7+
[
8+
"thirdPartyEnv",
9+
"sourceDir",
10+
]
11+
)
12+
13+
env: Environment = thirdPartyEnv
14+
TARGET_ARCH = env["TARGET_ARCH"]
15+
# Copy in the Java Access Bridge dependency.
16+
match TARGET_ARCH:
17+
case "x86":
18+
jabDllName = "windowsaccessbridge-32.dll"
19+
case "x86_64":
20+
jabDllName = "windowsaccessbridge-64.dll"
21+
case _:
22+
print(f"No Java Access Bridge support for NVDA core architecture {TARGET_ARCH!r}")
23+
jabDllName = None
24+
if jabDllName:
25+
env.Command(
26+
sourceDir.File("windowsaccessbridge.dll"), env.Dir("#include/javaAccessBridge32").File(jabDllName), Copy("$TARGET", "$SOURCE")
27+
)
28+

nvdaHelper/localWin10/sconscript

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@
1212
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
1313
###
1414

15-
import os
16-
import glob
17-
18-
from SCons.Tool.MSCommon.vc import find_vc_pdir
19-
2015
Import(
2116
"env",
2217
"sourceDir",
@@ -41,32 +36,4 @@ localWin10Lib = env.SharedLibrary(
4136
], # noqa: F821
4237
)
4338

44-
# UWP dlls can only be dynamically linked with the CRT,
45-
# but some systems might not have this version of the CRT.
46-
# Therefore, we must include it.
47-
# VS keeps changing the path to reflect the latest major.minor.build version which we canot easily find out.
48-
# Therefore Search these versioned directories from newest to oldest to collect all the files we need.
49-
msvc = env.get("MSVC_VERSION")
50-
vcRedistDirs = glob.glob(
51-
os.path.join(
52-
find_vc_pdir(msvc, env), rf"Redist\MSVC\{msvc[:2]}*\x86\Microsoft.VC{msvc.replace('.', '')}.CRT"
53-
)
54-
)
55-
if len(vcRedistDirs) == 0:
56-
raise RuntimeError(
57-
"Could not locate vc redistributables. Perhaps the Universal Windows Platform component in visual Studio is not installed"
58-
)
59-
vcRedistDirs.sort(reverse=True)
60-
for fn in ("msvcp140.dll", "vccorlib140.dll", "vcruntime140.dll"):
61-
for vcRedistDir in vcRedistDirs:
62-
path = os.path.join(vcRedistDir, fn)
63-
if os.path.isfile(path):
64-
env.Install(sourceDir, path)
65-
break
66-
else:
67-
raise RuntimeError(
68-
"Could not locate %s. Perhaps the Universal Windows Platform component in visual Studio is not installed"
69-
% fn
70-
)
71-
7239
Return(["localWin10Lib"])

nvdaHelper/uwp/sconscript

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# Copyright (C) 2017-2025 NV Access Limited, Leonard de Ruijter
3+
# This file may be used under the terms of the GNU General Public License, version 2 or later, as modified by the NVDA license.
4+
# For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt
5+
6+
import glob
7+
import os
8+
9+
from SCons.Tool.MSCommon.vc import find_vc_pdir
10+
11+
Import(
12+
[
13+
"thirdPartyEnv",
14+
"sourceDir",
15+
]
16+
)
17+
18+
env: Environment = thirdPartyEnv
19+
20+
# UWP dlls can only be dynamically linked with the CRT,
21+
# but some systems might not have this version of the CRT.
22+
# Therefore, we must include it.
23+
# VS keeps changing the path to reflect the latest major.minor.build version which we canot easily find out.
24+
# Therefore Search these versioned directories from newest to oldest to collect all the files we need.
25+
msvc = env.get("MSVC_VERSION")
26+
vcRedistDirs = glob.glob(
27+
os.path.join(
28+
find_vc_pdir(msvc, env), rf"Redist\MSVC\{msvc[:2]}*\x86\Microsoft.VC{msvc.replace('.', '')}.CRT"
29+
)
30+
)
31+
if len(vcRedistDirs) == 0:
32+
raise RuntimeError(
33+
"Could not locate vc redistributables. Perhaps the Universal Windows Platform component in visual Studio is not installed"
34+
)
35+
vcRedistDirs.sort(reverse=True)
36+
for fn in ("msvcp140.dll", "vccorlib140.dll", "vcruntime140.dll"):
37+
for vcRedistDir in vcRedistDirs:
38+
path = os.path.join(vcRedistDir, fn)
39+
if os.path.isfile(path):
40+
env.Install(sourceDir, path)
41+
break
42+
else:
43+
raise RuntimeError(
44+
"Could not locate %s. Perhaps the Universal Windows Platform component in visual Studio is not installed"
45+
% fn
46+
)

sconstruct

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ sourceLibDir64 = sourceDir.Dir("lib/x64")
196196
Export("sourceLibDir64")
197197
sourceLibDirArm64 = sourceDir.Dir("lib/arm64")
198198
Export("sourceLibDirArm64")
199+
sourceLibDirArm64EC = sourceDir.Dir("lib/arm64ec")
200+
Export("sourceLibDirArm64EC")
199201
buildDir = Dir("build")
200202
outFilePrefix = "nvda{type}_{version}".format(type="" if release else "_snapshot", version=version)
201203
Export("outFilePrefix")
@@ -247,10 +249,13 @@ archTools = ["default", "midl", "msrpc"]
247249
env32 = env.Clone(TARGET_ARCH="x86", tools=archTools)
248250
env64 = env.Clone(TARGET_ARCH="x86_64", tools=archTools)
249251
envArm64 = env.Clone(TARGET_ARCH="arm64", tools=archTools)
252+
envArm64EC = envArm64.Clone()
253+
envArm64EC["isArm64EC"] = True
250254
# Hack around odd bug where some tool [after] msvc states that static and shared objects are different
251255
env32["STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME"] = 1
252256
env64["STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME"] = 1
253257
envArm64["STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME"] = 1
258+
envArm64EC["STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME"] = 1
254259

255260
if platform.architecture()[0].startswith("64"):
256261
env = env64
@@ -276,17 +281,10 @@ resFile = env.RES(
276281
env32["projectResFile"] = resFile
277282
env64["projectResFile"] = resFile
278283
envArm64["projectResFile"] = resFile
284+
envArm64EC["projectResFile"] = resFile
279285

280286
# Fill sourceDir with anything provided for it by miscDeps
281287
env.recursiveCopy(sourceDir, Dir("miscdeps/source"))
282-
# Copy in some other dependencies.
283-
for targetDir, jabDll in (
284-
(sourceLibDir, "windowsaccessbridge-32.dll"),
285-
(sourceLibDir64, "windowsaccessbridge-64.dll"),
286-
):
287-
Command(
288-
targetDir.File("windowsaccessbridge.dll"), env.Dir("#include/javaAccessBridge32").File(jabDll), Copy("$TARGET", "$SOURCE")
289-
)
290288

291289
env.SConscript("source/comInterfaces_sconscript", exports=["env"])
292290

@@ -306,6 +304,11 @@ envArm64.SConscript(
306304
exports={"env": envArm64, "clientInstallDir": clientDir.Dir("arm64"), "libInstallDir": sourceLibDirArm64},
307305
variant_dir="build/arm64",
308306
)
307+
envArm64EC.SConscript(
308+
"nvdaHelper/archBuild_sconscript",
309+
exports={"env": envArm64EC, "clientInstallDir": clientDir.Dir("arm64ec"), "libInstallDir": sourceLibDirArm64EC},
310+
variant_dir="build/arm64ec",
311+
)
309312

310313
# Allow all NVDA's gettext po files to be compiled in source/locale
311314
for po in env.Glob(sourceDir.path + "/locale/*/lc_messages/*.po"):

source/JABHandler.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
import NVDAObjects.JAB
4040
import core
4141
import textUtils
42-
import NVDAHelper
4342
import config
4443
from utils.security import isRunningOnSecureDesktop
4544

@@ -1137,7 +1136,7 @@ def initialize():
11371136
global bridgeDll, isRunning
11381137
try:
11391138
bridgeDll = cdll.LoadLibrary(
1140-
os.path.join(NVDAHelper.coreArchLibPath, "windowsaccessbridge.dll"),
1139+
os.path.join("windowsaccessbridge.dll"),
11411140
)
11421141
except WindowsError:
11431142
raise NotImplementedError("dll not available")

source/NVDAHelper/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import buildVersion
3636
import globalVars
37+
import winVersion
3738

3839
versionedLibPath = os.path.join(globalVars.appDir, "lib")
3940

@@ -43,8 +44,13 @@
4344
# When running as a py2exe build, libraries are in a version-specific directory
4445
versionedLibPath = os.path.join(versionedLibPath, buildVersion.version)
4546

47+
arch = winVersion.getWinVer().processorArchitecture
4648
versionedLibX86Path = os.path.join(versionedLibPath, "x86")
47-
versionedLibAMD64Path = os.path.join(versionedLibPath, "x64")
49+
versionedLibAMD64Path = os.path.join(
50+
versionedLibPath,
51+
# On ARM64 Windows, we use arm64ec libraries for interop with x64 code.
52+
"arm64ec" if arch == "ARM64" else "x64",
53+
)
4854
versionedLibARM64Path = os.path.join(versionedLibPath, "arm64")
4955

5056
match sysconfig.get_platform():
@@ -86,7 +92,6 @@ def warnDeprecatedWithReplacement(deprecated: str, replacement: str):
8692

8793

8894
from . import localLib # noqa: E402
89-
import winVersion # noqa: E402
9095
import winKernel # noqa: E402
9196
import config # noqa: E402
9297
import winUser # noqa: E402
@@ -883,7 +888,6 @@ def initialize() -> None:
883888
log.error("Error installing IA2 support")
884889
# Manually start the in-process manager thread for this NVDA main thread now, as a slow system can cause this action to confuse WX
885890
_remoteLib.initInprocManagerThreadIfNeeded()
886-
arch = winVersion.getWinVer().processorArchitecture
887891
if arch != "x86" and coreArchLibPath != versionedLibX86Path:
888892
_remoteLoaderX86 = _RemoteLoader(versionedLibX86Path)
889893
elif arch in ("AMD64", "ARM64") and coreArchLibPath != versionedLibAMD64Path:

source/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ def _genManifestTemplate(shouldHaveUIAccess: bool) -> tuple[int, int, bytes]:
259259
("lib/%s/x86" % version, glob("lib/x86/*.dll") + glob("lib/x86/*.exe")),
260260
("lib/%s/x64" % version, glob("lib/x64/*.dll") + glob("lib/x64/*.exe")),
261261
("lib/%s/arm64" % version, glob("lib/arm64/*.dll") + glob("lib/arm64/*.exe")),
262+
("lib/%s/arm64ec" % version, glob("lib/arm64ec/*.dll") + glob("lib/arm64ec/*.exe")),
262263
("waves", glob("waves/*.wav")),
263264
("images", glob("images/*.ico")),
264265
("fonts", glob("fonts/*.ttf")),

0 commit comments

Comments
 (0)