64-Bit Windows applications typically expect the Twain Data Source manager to be named twaindsm.dll.
Send the DG_CONTROL/DAT_ENTRYPOINT/MSG_SET before DG_CONTROL/DAT_IDENTITY/MSG_OPENDS so sane.ds does not need to know the module name to find the DSM entry point.
An explanation for the request to offer the name twaindsm.dll for 64-Bit windows is offered here by the TWAIN organization:
https://github.com/twain/twain-dsm/blob/master/TWAIN_DSM/src/readme.doc
They cannot update the twain_32.dll installed and maintained by Microsoft, so they choose the new name twaindsm.dll instead. This name is also used at least by Irfanview and oder versions oder LibreOffice (Newer Libreoffice version start a 32-Bit process for 32-Bit TWAIN usage).
This Merge request also changes the twain_32.dll so that it send it's entry points earlier, before opening the data source. This is the same behaviour as the twaindsm.dll from the TWAIN organization from [https://github.com/twain/twain-dsm/%5D(https://github.com/twain/twain-dsm/bl...)
Otherwise sane.ds would in MSG_OPENDS try to find the DSM entry point based on the module-name "twain_32" in sane_main.c:60:
``` if (SANE_dsmentry == NULL) { HMODULE moddsm = GetModuleHandleW(L"twain_32");
if (moddsm) SANE_dsmentry = (void*)GetProcAddress(moddsm, "DSM_Entry");
if (!SANE_dsmentry) { ERR("can't find DSM entry point\n"); return TWRC_FAILURE; } } ```
-- v4: dlls/twain_32, dlls/twaindsm: Build Twain DSM as twaindsm.dll on 64-Bit build
From: Bernd Herd codeberg@herdsoft.com
64-Bit windows applications expect the Twain Data Source manager to be named twaindsm.dll.
Send the DG_CONTROL/DAT_ENTRYPOINT/MSG_SET before DG_CONTROL/DAT_IDENTITY/MSG_OPENDS so sane.ds does not need to know the module name to find the DSM entry point --- dlls/twain_32/dsm_ctrl.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-)
diff --git a/dlls/twain_32/dsm_ctrl.c b/dlls/twain_32/dsm_ctrl.c index 89e788f979d..436654bfcc7 100644 --- a/dlls/twain_32/dsm_ctrl.c +++ b/dlls/twain_32/dsm_ctrl.c @@ -381,8 +381,31 @@ TW_UINT16 TWAIN_OpenDS (pTW_IDENTITY pOrigin, TW_MEMREF pData) } newSource->hmod = hmod; newSource->dsEntry = (DSENTRYPROC)GetProcAddress(hmod, "DS_Entry"); + if (!newSource->dsEntry) { + ERR("Failed to find DS_Entry() in TWAIN DS %s\n", debugstr_w(devices[i].modname)); + DSM_twCC = TWCC_OPERATIONERROR; + HeapFree(GetProcessHeap(), 0, newSource); + return TWRC_FAILURE; + } /* Assign id for the opened data source */ pIdentity->Id = DSM_sourceId ++; + /* Get the Identity of the new DS, so we know the SupportedGroups */ + if (TWRC_SUCCESS != newSource->dsEntry (NULL, DG_CONTROL, DAT_IDENTITY, MSG_GET, pIdentity)) { + DSM_twCC = TWCC_OPERATIONERROR; + HeapFree(GetProcessHeap(), 0, newSource); + DSM_sourceId--; + return TWRC_FAILURE; + } + /* Tell the source our entry points */ + if (pIdentity->SupportedGroups & DF_DS2) { + /* This makes sure that the DS knows the current address of our DSM_Entry + * function so there is no risk that it is using a stale copy. + * The other entry points are also set for formal reasons, + * but are currently not used. + */ + newSource->dsEntry (pOrigin, DG_CONTROL, DAT_ENTRYPOINT, MSG_SET, (TW_ENTRYPOINT *) &_entrypoints); + } + /* Open the data source */ if (TWRC_SUCCESS != newSource->dsEntry (pOrigin, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, pIdentity)) { DSM_twCC = TWCC_OPERATIONERROR; HeapFree(GetProcessHeap(), 0, newSource); @@ -399,15 +422,6 @@ TW_UINT16 TWAIN_OpenDS (pTW_IDENTITY pOrigin, TW_MEMREF pData) activeSources = newSource; DSM_twCC = TWCC_SUCCESS;
- /* Tell the source our entry points */ - if (pIdentity->SupportedGroups & DF_DS2) { - /* This makes sure that the DS knows the current address of our DSM_Entry - * function so there is no risk that it is using a stale copy. - * The other entry points are also set for formal reasons, - * but are currently not used. - */ - newSource->dsEntry (pOrigin, DG_CONTROL, DAT_ENTRYPOINT, MSG_SET, (TW_ENTRYPOINT *) &_entrypoints); - } return TWRC_SUCCESS; }
From: Bernd Herd codeberg@herdsoft.com
Adds dlls/twaindsm to build from the same source as twain_32 but named twaindsm.dll for 64-Bit build.
Change configure.ac to build only twaindsm.dll on 64-Bit and only twain_32.dll on 32-Bit, but can be overwritten with configure options --- configure | 5 +++++ configure.ac | 5 +++++ dlls/twaindsm/Makefile.in | 10 ++++++++++ dlls/twaindsm/twaindsm.spec | 1 + 4 files changed, 21 insertions(+) create mode 100644 dlls/twaindsm/Makefile.in create mode 100644 dlls/twaindsm/twaindsm.spec
diff --git a/configure b/configure index e3fd3140529..0f929bb0bd8 100755 --- a/configure +++ b/configure @@ -1457,6 +1457,7 @@ enable_tdi_sys enable_threadpoolwinrt enable_traffic enable_twain_32 +enable_twaindsm enable_twinapi_appcore enable_tzres enable_ucrtbase @@ -22279,6 +22280,9 @@ enable_netsh=${enable_netsh:-yes} enable_regsvr32=${enable_regsvr32:-yes} enable_rundll32=${enable_rundll32:-yes}
+enable_twain_32=${enable_twain_32:-i386} +enable_twaindsm=${enable_twaindsm:-x86_64,arm64ec,arm,aarch64} + enable_win16=${enable_win16:-i386} enable_w32skrnl=${enable_w32skrnl:-$enable_win16} enable_wow32=${enable_wow32:-$enable_win16} @@ -23063,6 +23067,7 @@ wine_fn_config_makefile dlls/traffic enable_traffic wine_fn_config_makefile dlls/twain.dll16 enable_win16 wine_fn_config_makefile dlls/twain_32 enable_twain_32 wine_fn_config_makefile dlls/twain_32/tests enable_tests +wine_fn_config_makefile dlls/twaindsm enable_twaindsm wine_fn_config_makefile dlls/twinapi.appcore enable_twinapi_appcore wine_fn_config_makefile dlls/twinapi.appcore/tests enable_tests wine_fn_config_makefile dlls/typelib.dll16 enable_win16 diff --git a/configure.ac b/configure.ac index a9db2abcce4..42f46ab6a2d 100644 --- a/configure.ac +++ b/configure.ac @@ -2421,6 +2421,10 @@ enable_netsh=${enable_netsh:-yes} enable_regsvr32=${enable_regsvr32:-yes} enable_rundll32=${enable_rundll32:-yes}
+dnl twain_32.dll is for i386 only, others use twaindsm.dll +enable_twain_32=${enable_twain_32:-i386} +enable_twaindsm=${enable_twaindsm:-x86_64,arm64ec,arm,aarch64} + dnl Disable Win16-related modules enable_win16=${enable_win16:-i386} enable_w32skrnl=${enable_w32skrnl:-$enable_win16} @@ -3208,6 +3212,7 @@ WINE_CONFIG_MAKEFILE(dlls/traffic) WINE_CONFIG_MAKEFILE(dlls/twain.dll16) WINE_CONFIG_MAKEFILE(dlls/twain_32) WINE_CONFIG_MAKEFILE(dlls/twain_32/tests) +WINE_CONFIG_MAKEFILE(dlls/twaindsm) WINE_CONFIG_MAKEFILE(dlls/twinapi.appcore) WINE_CONFIG_MAKEFILE(dlls/twinapi.appcore/tests) WINE_CONFIG_MAKEFILE(dlls/typelib.dll16) diff --git a/dlls/twaindsm/Makefile.in b/dlls/twaindsm/Makefile.in new file mode 100644 index 00000000000..653c0419b39 --- /dev/null +++ b/dlls/twaindsm/Makefile.in @@ -0,0 +1,10 @@ +MODULE = twaindsm.dll +IMPORTS = user32 + +PARENTSRC = ../twain_32 + + +SOURCES = \ + dsm_ctrl.c \ + twain.rc \ + twain32_main.c diff --git a/dlls/twaindsm/twaindsm.spec b/dlls/twaindsm/twaindsm.spec new file mode 100644 index 00000000000..fe79c9f19dc --- /dev/null +++ b/dlls/twaindsm/twaindsm.spec @@ -0,0 +1 @@ +@ stdcall DSM_Entry(ptr ptr long long long ptr)
On Mon Nov 24 15:43:33 2025 +0000, Bernd Herd wrote:
Am Freitag, dem 21.11.2025 um 14:12 -0600 schrieb Esme Povirk (@madewokherd):
Merge Request https://gitlab.winehq.org/wine/wine/-/merge_requests/9519%C2%A0wurde von Esme Povirk überprüft
-- Esme Povirk started a new discussion on configure.ac: https://gitlab.winehq.org/wine/wine/-/merge_requests/9519#note_123300
> + enable_twaindsm="no"; > + fi > +fi
This way of doing it won't work correctly when we're building multiple architectures in a single tree. I think this should be similar to enable_dpnsvr below.
I'll likely take a look into this over the weekend... But I'm insecure here.
I still think that twaindsm should be provided on all architectures,
That surprises me. If you have an old twain 1.x compatible data source, that does not support the TWAIN 2.x extensions with the callbacks, that old data source needs to find the DSM using the "twain_32" name. As does sane.ds if being called from an old version 1.x DSM. So such an old Data Source cannot be used with twaindsm.dll, it must be used by twain_32.dll, else it won't work. Even if you just make a copy of twain_32.dll as twaindsm.dll and use that, an old Twain 1.x data source will likely fail. It will also fail with the twaindsm.dll provided by twain.org. https://github.com/twain/twain-dsm/blob/master/TWAIN_DSM/src/readme.doc Describes that problem in chapter 3.3.3: "TWAIN Drivers that want to use twaindsm32.dll need to add the flag SF_DSM2_DS to the TW_IDENTITY SupportedGroups member. This is to trigger the application that they can use twaindsm32.dll. When a TWAIN Driver is opened it can check the TW_IDENITY SupportedGroups flag. If the DSM has set SF_DSM2_DSM the TWAIN Driver knows that it has been opened by twaindsm32.dll. The twaindsm32.dll after successfully opening a DS can immediately send a DG_CONTROL/ DAT_CALLBACK/ MSG_REGISTER_CALLBACK triplet message to the DS. This message will contain the TW_CALLBACK structure with handle to the DSM_Entry function. If the DS does not receive this message it will default to the old method of LoadLibrary() and GetProcAddress() and assume it is being called by the TWAIN_32.DLL. TWAIN Application that want to support both twaindsm32.dll and TWAIN_32.DLL should first review all the drivers by using TWAIN_32.dll using getFirst() getNext on all the drivers. By checking the TW_IDENTIY SupportedGroups flag against SF_DSM2_DS the app knows that the TWAIN Driver can use the twaindsm32.dll. The TWAIN Application can safely use the twaindsm32.dll to open this TWIAN Driver by name. The application does not set the SF_DSM2_DSM or SF_DSM2_DS flag to indicate they want to use the twaindsm32.dll the DSM will do this." That sounds lake an awfully complex process. The only good reason to add this complexity for 32-Bit is if an application profits from the TWAIN 2.x extensions and thus the twaindsm.dll. And I don't think wine's dlls/twain_32 supports all TWAIN 2.0 features yet. Compared to 64-Bit: "Since Microsoft is not installing the DSM in 64-bit versions of Windows, this is the only DSM that TWAIN Applications and TWAIN Drivers need to look for." So we can expect all 64-Bit data sources to know how to handle this. If an application really wants to use the TWAIN 2.0 extensions in twaindsm.dll for 32-Bit, it can still install the TWAIN organization Version on 32-Bit... I read that some users successfully use TCP/IP-Based TWAIN Data Sources Drivers designed for Windows unter Wine. Twain has a driver database and it seems 64-Bit TWAIN for Windows is usually only offered by higher-class devices.
though at the moment it's unclear to me where it should look for data sources on arm64.
I have no idea of the ARM64 architecture yet. The twain spec says: "The name of the TWAIN directory is "twain_32" for 32-bit Sources and "twain_64" for 64-bit Sources (on 64-bit systems only)." (PDF-Page 645). Shouldn't that be sufficient? Or do we need to distinguish betwen ARM64 and AMD64 binaries on the same machine? Does ARM64 Architecture mean that I can compile up to 30 year old WIN32 source codes with few changes and run it on a raspberry pi? Could be cool. :-)
I see. I did not know that making twaindsm work on 32-bit would be that complex. In that case, it does make sense to wait until there's a clear benefit to providing it.
ARM64: Come to think of it, in theory, we could install ARM64X binaries in twain_64. ARM64X combines ARM64EC (so it works with emulated x86_64 code, but is itself native) and native ARM64. I do not know whether any applications would take advantage of the ARM64 side in practice.
On Mon Nov 24 15:43:33 2025 +0000, Esme Povirk wrote:
I see. I did not know that making twaindsm work on 32-bit would be that complex. In that case, it does make sense to wait until there's a clear benefit to providing it. ARM64: Come to think of it, in theory, we could install ARM64X binaries in twain_64. ARM64X combines ARM64EC (so it works with emulated x86_64 code, but is itself native) and native ARM64. I do not know whether any applications would take advantage of the ARM64 side in practice.
I'm not sure whether Wine currently can build ARM64X binaries, or how installing them works if it does.
On Mon Nov 24 15:47:43 2025 +0000, Alexandre Julliard wrote:
Maybe we should move everything to twaindsm.dll, and have twain_32.dll simply forward to twaindsm.
Would this remove the extra complexity of supporting it on 32-bit? I can see that it would remove the need for hooking.
On Mon Nov 24 15:47:43 2025 +0000, Esme Povirk wrote:
Would this remove the extra complexity of supporting it on 32-bit? I can see that it would remove the need for hooking.
One concern I would have: if native twaindsm.dll is installed (and potentially tries to hook itself/the forwarders), would this cause problems? Can/should we use builtin even when native is installed?
On Mon Nov 24 15:46:37 2025 +0000, Esme Povirk wrote:
I'm not sure whether Wine currently can build ARM64X binaries, or how installing them works if it does.
Yes, it works fine, and installing both x86_64 and arm64x files in twain_64 should work.
On Mon Nov 24 15:54:59 2025 +0000, Esme Povirk wrote:
One concern I would have: if native twaindsm.dll is installed (and potentially tries to hook itself/the forwarders), would this cause problems? Can/should we use builtin even when native is installed?
AFAICT it's only hooking GetProcAddress so that should work fine. Builtin will be used by default anyway, unless the user explicitly configures it to native.
So it sounds like what we're settling on is: * Instead of sharing source code, `twain_32` should forward all calls to `twaindsm`. The existing `twain_32` code should be moved into `dlls/twaindsm`. This will remove the need for any hooking to support data sources that expect to call `twain_32`. * `twaindsm` should be built for all architectures. `twain_32` should be built only for i386, using `enable_twain_32=${enable_twain_32:-i386}` in configure.ac. * On ARM64, `sane.ds` and `gphoto.ds` should be installed into the `twain_64` folder as ARM64X, which will support both x86_64 and native ARM64 processes. I'm not sure if we need to do anything special to make this happen.