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; } } ```
-- v6: dlls/twaindsm: Implement DG_CONTROL/DAT_ENTRYPOINT/MSG_GET dlls/twaindsm, dlls/twain_32: Put DSM code into twaindsm.dll and add twain_32.dll wrapper
From: Bernd Herd codeberg@herdsoft.com
Renamed dlls/twain_32 to dlls/twaindsm thus creating twaindsm.dll as platform independent name for the Twain Data Source Manager as it is used on 64-Bit windows.
Create new dlls/twain_32 as a wrapper for 32-Bit Windows only that forwards all calls to the main implementation in twaindsm.dll.
Send the DG_CONTROL/DAT_ENTRYPOINT/MSG_SET before DG_CONTROL/DAT_IDENTITY/MSG_OPENDS so Twain 2.x aware data sources like sane.ds don't need to know the filename of the Twain DSM ro find the DSM entry point.
On 32-Bit Systems LoadLibrary for the twain_32.dll wrapper when opening the DS and it is not 2.x compliant. This makes sure it will find the DSM_Entry of twain_32.dll if it uses GetModuleHandle("twain_32.dll") to find the DLL. --- MAINTAINERS | 1 + configure | 6 ++- configure.ac | 5 +- dlls/twain_32/Makefile.in | 6 +-- dlls/twain_32/twain_32.c | 50 +++++++++++++++++++ dlls/twain_32/twain_32.spec | 2 +- dlls/twaindsm/Makefile.in | 8 +++ dlls/{twain_32 => twaindsm}/dsm_ctrl.c | 47 +++++++++++++---- dlls/{twain_32 => twaindsm}/resource.h | 0 dlls/{twain_32 => twaindsm}/tests/Makefile.in | 2 +- dlls/{twain_32 => twaindsm}/tests/dsm.c | 4 +- dlls/{twain_32 => twaindsm}/twain.rc | 0 dlls/{twain_32 => twaindsm}/twain32_main.c | 0 dlls/{twain_32 => twaindsm}/twain_i.h | 0 dlls/twaindsm/twaindsm.spec | 1 + 15 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 dlls/twain_32/twain_32.c create mode 100644 dlls/twaindsm/Makefile.in rename dlls/{twain_32 => twaindsm}/dsm_ctrl.c (94%) rename dlls/{twain_32 => twaindsm}/resource.h (100%) rename dlls/{twain_32 => twaindsm}/tests/Makefile.in (64%) rename dlls/{twain_32 => twaindsm}/tests/dsm.c (99%) rename dlls/{twain_32 => twaindsm}/twain.rc (100%) rename dlls/{twain_32 => twaindsm}/twain32_main.c (100%) rename dlls/{twain_32 => twaindsm}/twain_i.h (100%) create mode 100644 dlls/twaindsm/twaindsm.spec
diff --git a/MAINTAINERS b/MAINTAINERS index c34abcecbc3..6167ff4464e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -325,6 +325,7 @@ F: dlls/uxtheme/ TWAIN P: Esme Povirk esme@codeweavers.com @madewokherd F: dlls/twain_32/ +F: dlls/twaindsm/ F: dlls/sane.ds/ F: dlls/gphoto2.ds/
diff --git a/configure b/configure index e3fd3140529..0942db1bf59 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 @@ -22278,6 +22279,8 @@ enable_msiexec=${enable_msiexec:-yes} enable_netsh=${enable_netsh:-yes} enable_regsvr32=${enable_regsvr32:-yes} enable_rundll32=${enable_rundll32:-yes} +enable_twaindsm=${enable_twaindsm:-yes} +enable_twain_32=${enable_twain_32:-i386}
enable_win16=${enable_win16:-i386} enable_w32skrnl=${enable_w32skrnl:-$enable_win16} @@ -23062,7 +23065,8 @@ wine_fn_config_makefile dlls/toolhelp.dll16 enable_win16 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/twaindsm/tests enable_tests 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..cf6d2953579 100644 --- a/configure.ac +++ b/configure.ac @@ -2420,6 +2420,8 @@ enable_msiexec=${enable_msiexec:-yes} enable_netsh=${enable_netsh:-yes} enable_regsvr32=${enable_regsvr32:-yes} enable_rundll32=${enable_rundll32:-yes} +enable_twaindsm=${enable_twaindsm:-yes} +enable_twain_32=${enable_twain_32:-i386}
dnl Disable Win16-related modules enable_win16=${enable_win16:-i386} @@ -3207,7 +3209,8 @@ WINE_CONFIG_MAKEFILE(dlls/toolhelp.dll16) 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/twaindsm/tests) WINE_CONFIG_MAKEFILE(dlls/twinapi.appcore) WINE_CONFIG_MAKEFILE(dlls/twinapi.appcore/tests) WINE_CONFIG_MAKEFILE(dlls/typelib.dll16) diff --git a/dlls/twain_32/Makefile.in b/dlls/twain_32/Makefile.in index b4ac1e27729..94225d4cc64 100644 --- a/dlls/twain_32/Makefile.in +++ b/dlls/twain_32/Makefile.in @@ -1,7 +1,5 @@ MODULE = twain_32.dll -IMPORTS = user32 +IMPORTS = user32 twaindsm
SOURCES = \ - dsm_ctrl.c \ - twain.rc \ - twain32_main.c + twain_32.c diff --git a/dlls/twain_32/twain_32.c b/dlls/twain_32/twain_32.c new file mode 100644 index 00000000000..eba57a4578b --- /dev/null +++ b/dlls/twain_32/twain_32.c @@ -0,0 +1,50 @@ +/* + * TWAIN32 functions + * + * Copyright 2025 Bernd Herd codeberg@herdsoft.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * This implements twain_32.dll as a simple wrapper for 32-Bit Intel Windows + * to forward all requests to the platform independent + * main inplementation in twaindsm.dll + */ + +#include <stdarg.h> + +#include "windef.h" +#include "winbase.h" +#include "twain.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(twain); + +/* Main entry point for the TWAIN_32.dll library */ +TW_UINT16 WINAPI +TWAIN_32_DSM_Entry ( + pTW_IDENTITY pOrigin, + pTW_IDENTITY pDest, + TW_UINT32 DG, + TW_UINT16 DAT, + TW_UINT16 MSG, + TW_MEMREF pData) +{ + TRACE("twain_32 (DG=%ld DAT=%d MSG=%d)\n", DG, DAT, MSG); + + /* Just forward the call to the twaindsm.dll */ + return DSM_Entry(pOrigin, pDest, DG, DAT, MSG, pData); +} diff --git a/dlls/twain_32/twain_32.spec b/dlls/twain_32/twain_32.spec index fe79c9f19dc..cc2fc585157 100644 --- a/dlls/twain_32/twain_32.spec +++ b/dlls/twain_32/twain_32.spec @@ -1 +1 @@ -@ stdcall DSM_Entry(ptr ptr long long long ptr) +@ stdcall DSM_Entry(ptr ptr long long long ptr) TWAIN_32_DSM_Entry diff --git a/dlls/twaindsm/Makefile.in b/dlls/twaindsm/Makefile.in new file mode 100644 index 00000000000..ad8053803da --- /dev/null +++ b/dlls/twaindsm/Makefile.in @@ -0,0 +1,8 @@ +MODULE = twaindsm.dll +IMPORTLIB = twaindsm +IMPORTS = user32 + +SOURCES = \ + dsm_ctrl.c \ + twain.rc \ + twain32_main.c diff --git a/dlls/twain_32/dsm_ctrl.c b/dlls/twaindsm/dsm_ctrl.c similarity index 94% rename from dlls/twain_32/dsm_ctrl.c rename to dlls/twaindsm/dsm_ctrl.c index 89e788f979d..c77f4f32b6b 100644 --- a/dlls/twain_32/dsm_ctrl.c +++ b/dlls/twaindsm/dsm_ctrl.c @@ -47,6 +47,11 @@ struct all_devices { static int nrdevices = 0; static struct all_devices *devices = NULL;
+#ifndef WIN64 +/* Instance-Handle of the twain_32.dll */ +HINSTANCE hinstTwain_32 = NULL; +#endif // !WIN64 + static void twain_add_onedriver(const WCHAR *dsname) { HMODULE hmod; @@ -276,6 +281,12 @@ TW_UINT16 TWAIN_CloseDS (pTW_IDENTITY pOrigin, TW_MEMREF pData) /* This causes crashes due to still open Windows, so leave out for now. * FreeLibrary (currentDS->hmod); */ +#ifndef WIN64 + if (!(pIdentity->SupportedGroups & DF_DS2)) { + FreeLibrary(hinstTwain_32); + } +#endif // !WIN64 + if (prevDS) prevDS->next = currentDS->next; else @@ -381,8 +392,34 @@ 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. */ + newSource->dsEntry (pOrigin, DG_CONTROL, DAT_ENTRYPOINT, MSG_SET, (TW_ENTRYPOINT *) &_entrypoints); + } +#ifndef WIN64 + else { + /* DS is Version 1.x. Make sure twain_32.dll is loaded in case DS uses GetModuleHandle("twain_32") */ + hinstTwain_32 = LoadLibraryW(L"twain_32.dll"); + } +#endif // !WIN64 + /* 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); @@ -398,16 +435,6 @@ TW_UINT16 TWAIN_OpenDS (pTW_IDENTITY pOrigin, TW_MEMREF pData) newSource->event_window = NULL; 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/dlls/twain_32/resource.h b/dlls/twaindsm/resource.h similarity index 100% rename from dlls/twain_32/resource.h rename to dlls/twaindsm/resource.h diff --git a/dlls/twain_32/tests/Makefile.in b/dlls/twaindsm/tests/Makefile.in similarity index 64% rename from dlls/twain_32/tests/Makefile.in rename to dlls/twaindsm/tests/Makefile.in index 13052327d7d..11cc60fef8b 100644 --- a/dlls/twain_32/tests/Makefile.in +++ b/dlls/twaindsm/tests/Makefile.in @@ -1,4 +1,4 @@ -TESTDLL = twain_32.dll +TESTDLL = twaindsm.dll IMPORTS = user32 gdi32
SOURCES = \ diff --git a/dlls/twain_32/tests/dsm.c b/dlls/twaindsm/tests/dsm.c similarity index 99% rename from dlls/twain_32/tests/dsm.c rename to dlls/twaindsm/tests/dsm.c index f12110c664b..85893ed4827 100644 --- a/dlls/twain_32/tests/dsm.c +++ b/dlls/twaindsm/tests/dsm.c @@ -845,10 +845,10 @@ START_TEST(dsm) return; }
- htwain = LoadLibraryA("twain_32.dll"); + htwain = LoadLibraryA("twaindsm.dll"); if (! htwain) { - win_skip("twain_32.dll not available, skipping tests\n"); + win_skip("twaindsm.dll not available, skipping tests\n"); return; } pDSM_Entry = (void*)GetProcAddress(htwain, "DSM_Entry"); diff --git a/dlls/twain_32/twain.rc b/dlls/twaindsm/twain.rc similarity index 100% rename from dlls/twain_32/twain.rc rename to dlls/twaindsm/twain.rc diff --git a/dlls/twain_32/twain32_main.c b/dlls/twaindsm/twain32_main.c similarity index 100% rename from dlls/twain_32/twain32_main.c rename to dlls/twaindsm/twain32_main.c diff --git a/dlls/twain_32/twain_i.h b/dlls/twaindsm/twain_i.h similarity index 100% rename from dlls/twain_32/twain_i.h rename to dlls/twaindsm/twain_i.h 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)
From: Bernd Herd codeberg@herdsoft.com
Implement DG_CONTROL/DAT_ENTRYPOINT/MSG_GET as a first step towards implementing Twain 2.0 extensions to the protocol --- dlls/twaindsm/dsm_ctrl.c | 34 ++++++++++++++++++++++++++++++++++ dlls/twaindsm/twain32_main.c | 15 +++++++++++++++ dlls/twaindsm/twain_i.h | 1 + include/twain.h | 2 ++ 4 files changed, 52 insertions(+)
diff --git a/dlls/twaindsm/dsm_ctrl.c b/dlls/twaindsm/dsm_ctrl.c index c77f4f32b6b..8addc688188 100644 --- a/dlls/twaindsm/dsm_ctrl.c +++ b/dlls/twaindsm/dsm_ctrl.c @@ -582,6 +582,12 @@ TW_UINT16 TWAIN_OpenDSM (pTW_IDENTITY pOrigin, TW_MEMREF pData) TRACE("DG_CONTROL/DAT_PARENT/MSG_OPENDSM\n"); if (!DSM_initialized) { event_message = RegisterWindowMessageA("WINE TWAIN_32 EVENT"); + + if (pOrigin->SupportedGroups & DF_APP2) + { + pOrigin->SupportedGroups |= DF_DSM2; + } + DSM_currentDevice = 0; DSM_initialized = TRUE; DSM_twCC = TWCC_SUCCESS; @@ -606,3 +612,31 @@ TW_UINT16 TWAIN_GetDSMStatus (pTW_IDENTITY pOrigin, TW_MEMREF pData) DSM_twCC = TWCC_SUCCESS; /* clear the condition code */ return TWRC_SUCCESS; } + + +/* DG_CONTROL/DAT_ENTRYPOINT/MSG_GET */ +TW_UINT16 TWAIN_GetEntrypoint(TW_ENTRYPOINT *pEntrypoint) +{ + TW_UINT16 twRC; + + if (!pEntrypoint) + { + ERR("pEntrypoint is null\n"); + twRC = TWRC_FAILURE; + DSM_twCC = TWCC_BADVALUE; + } + else if (pEntrypoint->Size < sizeof(TW_ENTRYPOINT)) + { + ERR("pEntrypoint->Size=%ld too small\n", pEntrypoint->Size); + twRC = TWRC_FAILURE; + DSM_twCC = TWCC_BADVALUE; + } + else + { + memcpy(pEntrypoint, &_entrypoints, sizeof(_entrypoints)); + twRC = TWRC_SUCCESS; + DSM_twCC = TWCC_SUCCESS; + } + + return twRC; +} diff --git a/dlls/twaindsm/twain32_main.c b/dlls/twaindsm/twain32_main.c index a8b11a9bbfe..8edbad40327 100644 --- a/dlls/twaindsm/twain32_main.c +++ b/dlls/twaindsm/twain32_main.c @@ -126,6 +126,21 @@ static TW_UINT16 TWAIN_SourceManagerHandler ( } break;
+ case DAT_ENTRYPOINT: + switch (MSG) + { + case MSG_GET: + twRC = TWAIN_GetEntrypoint((TW_ENTRYPOINT *) pData); + break; + + default: + /* Unrecognized operation triplet */ + twRC = TWRC_FAILURE; + DSM_twCC = TWCC_BADPROTOCOL; + WARN("unrecognized operation triplet\n"); + } + break; + case DAT_STATUS: if (MSG == MSG_GET) { twRC = TWAIN_GetDSMStatus (pOrigin, pData); diff --git a/dlls/twaindsm/twain_i.h b/dlls/twaindsm/twain_i.h index 7ff608d2e95..a4aa37a5dbd 100644 --- a/dlls/twaindsm/twain_i.h +++ b/dlls/twaindsm/twain_i.h @@ -63,5 +63,6 @@ extern TW_UINT16 TWAIN_OpenDSM(pTW_IDENTITY pOrigin, TW_MEMREF pData); extern TW_UINT16 TWAIN_GetDSMStatus(pTW_IDENTITY pOrigin, TW_MEMREF pData); extern TW_UINT16 TWAIN_ControlNull(pTW_IDENTITY pOrigin, pTW_IDENTITY pDest, activeDS *pSource, TW_UINT16 MSG, TW_MEMREF pData); extern TW_UINT16 TWAIN_ProcessEvent(pTW_IDENTITY pOrigin, activeDS *pSource, TW_MEMREF pData); +extern TW_UINT16 TWAIN_GetEntrypoint(TW_ENTRYPOINT *pEntrypoint);
#endif diff --git a/include/twain.h b/include/twain.h index ec17a4fe519..0dc65fcd8d0 100644 --- a/include/twain.h +++ b/include/twain.h @@ -1845,6 +1845,8 @@ typedef TW_UINT16 (*DSENTRYPROC)(pTW_IDENTITY,
/* Definitions from TWAIN 2.x used by our builtin data sources */ #define DAT_ENTRYPOINT 0x0403 +#define DF_DSM2 0x10000000L +#define DF_APP2 0x20000000L #define DF_DS2 0x40000000
#ifdef __cplusplus