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; } } ```
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 +++++++++++++++++++++++--------- loader/wine.inf.in | 1 + 2 files changed, 24 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; }
diff --git a/loader/wine.inf.in b/loader/wine.inf.in index 7dc657cc9e0..20b12d4c11d 100644 --- a/loader/wine.inf.in +++ b/loader/wine.inf.in @@ -731,6 +731,7 @@ HKLM,SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x86,"Version",2,"14.42.344 [FakeDllsWin64] 10,twain_64,sane.ds 10,twain_64,gphoto2.ds +10,,twaindsm.dll,twain_32.dll
; Wow64-only fake dlls [FakeDllsWow64]
Esme Povirk (@madewokherd) commented about loader/wine.inf.in:
[FakeDllsWin64] 10,twain_64,sane.ds 10,twain_64,gphoto2.ds +10,,twaindsm.dll,twain_32.dll
Seems like according to the spec (Operating System Dependencies / Developing for Windows), twaindsm.dll should be in the system directory for the appropriate architecture. This would, if I understand correctly, put it in the Windows directory.
I think it would make sense to build it separately from twain_32 for this reason - the default installation behavior would then be what we want.
On Wed Nov 19 18:03:31 2025 +0000, Esme Povirk wrote:
Seems like according to the spec (Operating System Dependencies / Developing for Windows), twaindsm.dll should be in the system directory for the appropriate architecture. This would, if I understand correctly, put it in the Windows directory. I think it would make sense to build it separately from twain_32 for this reason - the default installation behavior would then be what we want.
You can just use 11 instead of 10 for that. I don't see the need to build it separately?
On Wed Nov 19 18:17:56 2025 +0000, Elizabeth Figura wrote:
You can just use 11 instead of 10 for that. I don't see the need to build it separately?
Why do we need it at all? It isn't part of Windows, my understanding is that apps are supposed to install it themselves.
On Wed Nov 19 18:30:09 2025 +0000, Alexandre Julliard wrote:
Why do we need it at all? It isn't part of Windows, my understanding is that apps are supposed to install it themselves.
Am Mittwoch, dem 19.11.2025 um 12:30 -0600 schrieb Alexandre Julliard (@julliard):
Alexandre Julliard commented on a discussion on loader/wine.inf.in: https://gitlab.winehq.org/wine/wine/-/merge_requests/9519#note_122976
> [FakeDllsWin64] > 10,twain_64,sane.ds > 10,twain_64,gphoto2.ds > +10,,twaindsm.dll,twain_32.dll
Why do we need it at all? It isn't part of Windows, my understanding is that apps are supposed to install it themselves.
Hello,
applications usually assume that twaindsm.dll comes with the Data Source. Since wine is installing the sane.ds and gphoto2.ds data sources, it is logical that application devopers assume there are no data sources installed if there is no twaindsm.dll.
When testing I found 2 applications that use twaindsm.dll besides my own. One is Irfanview 64-Bit and if twaindsm.dll (64-Bit) is missing, it assumes there are no 64-Bit Data sources.
The other is an old version of LibreOffice. New versions of 64-Bit Libreoffice meanwhile include a 32-Bit application to perform TWAIN with 32-Bit technology and they don't offer 64-Bit TWAIN any more.
The TWAIN spec says:
"Applications that wish to use access the Data Source Manager, must install it themselves." (Page 12-1). That sentence can be read so that it is not the responsibility of the operating system to provide the DSM. Or it can be read as it is not the responsibility of the Data Source. But the TWAIN spec also says on page 2-9:
"Installation of the Data Source Manager TWAIN Applications and Sources should install the latest version of the Data Source Manager."
So if Sources should install the latest version of the DSM and Wine includes Data Source sane.ds and gphoto2.ds, it should install the latest version of the DSM. However the TWAIN DSM is C++ code and wine includes no C++ code. So I found it reasonable to extend the dlls/twain_32 with the most important TWAIN 2 features to work with 64 Bit and ship the wine DSM as twaindsm.dll.
However neither the DS, nor the dlls/twain_32 are fully TWAIN 2.0 compliant yet. And they don't need to be to work for the three test cases I was able to come up with.
The TWAIN.org version of twaindsm.dll offers memory management callbacks, that themselfs only use the old GlobalAlloc/GlobalFree as the Wine version does, so is essentially a no-op. And it stores the last recently used DS in the registry, which seems to be missing in the Wine version.