[PATCH v8 0/4] MR9519: dlls/twain_32: Offer TWAIN DSM as twaindsm.dll for 64 Bit and send DAT_ENTRYPOINT earlier.
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/](https://github.com/twain/twain-dsm/blob/master/TWAIN_DSM/src/readme.doc) 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; } } ``` -- v8: dlls/twaindsm: Recursively search in C:\Windows\twain_xx for installed data sources https://gitlab.winehq.org/wine/wine/-/merge_requests/9519
From: Bernd Herd <codeberg(a)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(a)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 7aebd970364..b9570edfe40 100755 --- a/configure +++ b/configure @@ -1461,6 +1461,7 @@ enable_tdi_sys enable_threadpoolwinrt enable_traffic enable_twain_32 +enable_twaindsm enable_twinapi_appcore enable_tzres enable_ucrtbase @@ -22283,6 +22284,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} @@ -23071,7 +23074,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 06a3a820e4c..05e2ba85b91 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} @@ -3211,7 +3213,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(a)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) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9519
From: Bernd Herd <codeberg(a)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 -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9519
From: Bernd Herd <codeberg(a)herdsoft.com> Since TWAIN version 2.0, applications are encouraged to use callbacks instead of window messages for communication. This commit implements the TRIPLET to set the callbacks and the call to those callbacks. If an application does not use callbacks, the old PostMessage method is being used as before. --- dlls/twaindsm/dsm_ctrl.c | 54 ++++++++++++++++++++++++++++++++++-- dlls/twaindsm/twain32_main.c | 19 ++++++++++++- dlls/twaindsm/twain_i.h | 2 ++ include/twain.h | 23 +++++++++++++++ 4 files changed, 95 insertions(+), 3 deletions(-) diff --git a/dlls/twaindsm/dsm_ctrl.c b/dlls/twaindsm/dsm_ctrl.c index 8addc688188..d0a6a13c9a6 100644 --- a/dlls/twaindsm/dsm_ctrl.c +++ b/dlls/twaindsm/dsm_ctrl.c @@ -77,6 +77,7 @@ twain_add_onedriver(const WCHAR *dsname) { do { int i; + memset(&sourceId, 0, sizeof(sourceId)); sourceId.Id = DSM_sourceId; sourceId.ProtocolMajor = TWON_PROTOCOLMAJOR; sourceId.ProtocolMinor = TWON_PROTOCOLMINOR; @@ -188,12 +189,43 @@ TW_UINT16 TWAIN_ControlNull (pTW_IDENTITY pOrigin, pTW_IDENTITY pDest, activeDS if (MSG != MSG_CLOSEDSREQ && MSG != MSG_DEVICEEVENT && - MSG != MSG_XFERREADY) + MSG != MSG_XFERREADY && + MSG != MSG_DEVICEEVENT) { DSM_twCC = TWCC_BADPROTOCOL; return TWRC_FAILURE; } + if (pSource && + pSource->registered_callback.CallBackProc) + { + static BOOL bProcessingCallback = FALSE; + TRACE("DG_CONTROL/DAT_NULL using callback\n"); + if (!bProcessingCallback) + { + TW_UINT16 twRC; + bProcessingCallback=TRUE; + twRC = ((DSMENTRYPROC) pSource->registered_callback.CallBackProc) + (pOrigin, + pDest, + DG_CONTROL, DAT_NULL, MSG, + (TW_MEMREF) pSource->registered_callback.RefCon); + bProcessingCallback=FALSE; + if (twRC != TWCC_SUCCESS) + { + DSM_twCC = TWCC_BADPROTOCOL; + } + return twRC; + } + else + { + ERR("Nested callback\n"); + DSM_twCC = TWCC_BADPROTOCOL; + return TWRC_FAILURE; + } + + } + message = HeapAlloc(GetProcessHeap(), 0, sizeof(*message)); if (!message) { @@ -377,7 +409,7 @@ TW_UINT16 TWAIN_OpenDS (pTW_IDENTITY pOrigin, TW_MEMREF pData) } /* else use the first device */ /* the source is found in the device list */ - newSource = HeapAlloc (GetProcessHeap(), 0, sizeof (activeDS)); + newSource = HeapAlloc (GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof (activeDS)); if (!newSource) { DSM_twCC = TWCC_LOWMEMORY; FIXME("Out of memory.\n"); @@ -640,3 +672,21 @@ TW_UINT16 TWAIN_GetEntrypoint(TW_ENTRYPOINT *pEntrypoint) return twRC; } + + + +/** @brief DG_CONTROL/DAT_CALLBACK/MSG_REGISTER_CALLBACK and DG_CONTROL/DAT_CALLBACK2/MSG_REGISTER_CALLBACK + * @param pSource The data source to associate the callback to + * @param CallBackProc Address of a callback procedure defined by the application program + * @param RefCon Reference Constant as defined by the application program + * @return Twain result code, TWRC_SUCCESS on success + */ +TW_UINT16 TWAIN_RegisterCallback(activeDS *pSource, TW_MEMREF *CallBackProc, UINT_PTR RefCon) +{ + TRACE("DG_CONTROL/DAT_CALLBACKx/MSG_REGISTER_CALLBACK\n"); + pSource->registered_callback.CallBackProc=CallBackProc; + pSource->registered_callback.RefCon=RefCon; + + DSM_twCC = TWCC_SUCCESS; + return TWRC_SUCCESS; +} diff --git a/dlls/twaindsm/twain32_main.c b/dlls/twaindsm/twain32_main.c index 8edbad40327..6f727c04747 100644 --- a/dlls/twaindsm/twain32_main.c +++ b/dlls/twaindsm/twain32_main.c @@ -187,7 +187,6 @@ DSM_Entry (pTW_IDENTITY pOrigin, return TWAIN_ControlNull (pOrigin, pDest, pSource, MSG, pData); } - if (pDest) { activeDS *pSource = TWAIN_LookupSource (pDest); @@ -213,6 +212,24 @@ DSM_Entry (pTW_IDENTITY pOrigin, pSource->ui_window = ((TW_USERINTERFACE*)pData)->hParent; } + if (DG == DG_CONTROL && + MSG == MSG_REGISTER_CALLBACK && + (DAT == DAT_CALLBACK || DAT != DAT_CALLBACK2) && + pData != NULL) + { + if (DAT == DAT_CALLBACK) + { + TW_CALLBACK *pCallback = (TW_CALLBACK *) pData; + twRC = TWAIN_RegisterCallback(pSource, pCallback->CallBackProc, pCallback->RefCon); + } + else + { + TW_CALLBACK2 *pCallback = (TW_CALLBACK2 *) pData; + twRC = TWAIN_RegisterCallback(pSource, pCallback->CallBackProc, pCallback->RefCon); + } + return twRC; + } + DSM_twCC = TWCC_SUCCESS; TRACE("Forwarding %ld/%d/%d/%p to DS.\n", DG, DAT, MSG, pData); twRC = pSource->dsEntry(pOrigin, DG, DAT, MSG, pData); diff --git a/dlls/twaindsm/twain_i.h b/dlls/twaindsm/twain_i.h index a4aa37a5dbd..6858aac0908 100644 --- a/dlls/twaindsm/twain_i.h +++ b/dlls/twaindsm/twain_i.h @@ -43,6 +43,7 @@ typedef struct tagActiveDS struct list pending_messages; HWND ui_window; HWND event_window; + TW_CALLBACK2 registered_callback; } activeDS; extern TW_UINT16 DSM_twCC; /* current condition code of Source Manager */ @@ -64,5 +65,6 @@ 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); +extern TW_UINT16 TWAIN_RegisterCallback(activeDS *pSource, TW_MEMREF *CallBackProc, UINT_PTR RefCon); #endif diff --git a/include/twain.h b/include/twain.h index 0dc65fcd8d0..bac51357012 100644 --- a/include/twain.h +++ b/include/twain.h @@ -81,6 +81,7 @@ #ifdef _MSWIN_ typedef HANDLE TW_HANDLE; typedef LPVOID TW_MEMREF; + typedef UINT_PTR TW_UINTPTR; typedef BYTE * HPBYTE; typedef void * HPVOID; #endif /* _MSWIN_ */ @@ -496,6 +497,22 @@ typedef struct { TW_INT16 VRefNum; } TW_SETUPAUDIOFILEXFER, FAR * pTW_SETUPAUDIOFILEXFER; + +/* Used with DG_CONTROL / DAT_CALLBACK / MSG_REGISTER_CALLBACK */ +typedef struct { + TW_MEMREF CallBackProc; + TW_UINT32 RefCon; + TW_INT16 Message; +} TW_CALLBACK, * pTW_CALLBACK; + +/* Used with DG_CONTROL / DAT_CALLBACK2 / MSG_REGISTER_CALLBACK */ +typedef struct { + TW_MEMREF CallBackProc; + TW_UINTPTR RefCon; + TW_INT16 Message; +} TW_CALLBACK2, * pTW_CALLBACK2; + + /**************************************************************************** * Generic Constants * ****************************************************************************/ @@ -1351,6 +1368,8 @@ typedef struct { #define DAT_DEVICEEVENT 0x000d /* TW_DEVICEEVENT */ #define DAT_FILESYSTEM 0x000e /* TW_FILESYSTEM */ #define DAT_PASSTHRU 0x000f /* TW_PASSTHRU */ +#define DAT_CALLBACK 0x0010 /* TW_CALLBACK */ +#define DAT_CALLBACK2 0x0012 /* TW_CALLBACK2 */ /* Data Argument Types for the DG_IMAGE Data Group. */ #define DAT_IMAGEINFO 0x0101 /* TW_IMAGEINFO */ @@ -1436,6 +1455,10 @@ typedef struct { /* Messages used with a pointer to a DAT_PASSTHRU structure */ #define MSG_PASSTHRU 0x0901 +/* Added 2.x */ +#define MSG_REGISTER_CALLBACK 0x0902 /* Used with DAT_CALLBACK/DAT_CALLBACK2*/ + + /**************************************************************************** * Capabilities * ****************************************************************************/ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9519
From: Bernd Herd <codeberg(a)herdsoft.com> Data sources installed with a setup program are storing their data in subdirectiries of C:\Windows\twain_32 or C:\Windows\twain_64. This commit adds recursive search for all .ds files in these directories to add them to the list of available data sources --- dlls/twaindsm/dsm_ctrl.c | 78 ++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/dlls/twaindsm/dsm_ctrl.c b/dlls/twaindsm/dsm_ctrl.c index d0a6a13c9a6..fc026c3528f 100644 --- a/dlls/twaindsm/dsm_ctrl.c +++ b/dlls/twaindsm/dsm_ctrl.c @@ -53,16 +53,13 @@ HINSTANCE hinstTwain_32 = NULL; #endif // !WIN64 static void -twain_add_onedriver(const WCHAR *dsname) { +twain_add_onedriver(const WCHAR *path) { HMODULE hmod; DSENTRYPROC dsEntry; TW_IDENTITY fakeOrigin; TW_IDENTITY sourceId; struct all_devices *new_devices; TW_UINT16 ret; - WCHAR path[MAX_PATH]; - - swprintf( path, MAX_PATH, L"c:\\windows\\twain_%u\\%s", sizeof(void *) * 8, dsname ); hmod = LoadLibraryW(path); if (!hmod) { ERR("Failed to load TWAIN Source %s\n", debugstr_w(path)); @@ -71,6 +68,7 @@ twain_add_onedriver(const WCHAR *dsname) { dsEntry = (DSENTRYPROC)GetProcAddress(hmod, "DS_Entry"); if (!dsEntry) { ERR("Failed to find DS_Entry() in TWAIN DS %s\n", debugstr_w(path)); + FreeLibrary(hmod); return; } /* Loop to do multiple detects, mostly for sane.ds and gphoto2.ds */ @@ -108,20 +106,72 @@ twain_add_onedriver(const WCHAR *dsname) { FreeLibrary (hmod); } + +/** + * Recursively search all *.ds Data Source files in the given directory + * and add them to the new_devices list. + * + * We search for file ending with ".ds" for the Data Source drivers + * and fill all subdirectories. + * + * @param dirname Name of the start directory. + */ +static void +twain_autodetect_recurse(const WCHAR *dirname) +{ + HANDLE hfind; + WCHAR szFindPattern[MAX_PATH]; + WCHAR *p; + WIN32_FIND_DATAW ff; + + swprintf(szFindPattern, ARRAY_SIZE(szFindPattern), L"%s\\*", dirname); + + hfind = FindFirstFileW(szFindPattern, &ff); + if (hfind != INVALID_HANDLE_VALUE) + { + WCHAR szFullName[MAX_PATH]; + do + { + swprintf(szFullName, ARRAY_SIZE(szFullName), L"%s\\%s", dirname, ff.cFileName); + if ((ff.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + lstrcmpW(ff.cFileName, L".") && + lstrcmpW(ff.cFileName, L"..")) + { + /* Found a subdirectory. Recursivly search in it. */ + twain_autodetect_recurse(szFullName); + } + else if (NULL != (p=wcsrchr(ff.cFileName, '.')) && + !lstrcmpiW(p, L".ds")) + { + twain_add_onedriver(szFullName); + } + } + while (FindNextFileW(hfind, &ff)); + FindClose(hfind); + } +} + + static BOOL detectionrun = FALSE; +/** @brief Detect all installed data sources by recursive directory scan. + * + * TWAIN Data Sources install in a subdirectory of c:\windows\twain_<bitdepth> as + * *.ds files. Search all of them and add them to the list. + */ static void -twain_autodetect(void) { +twain_autodetect(void) +{ + WCHAR szWindowsDir[MAX_PATH]; + WCHAR szTwainDir[MAX_PATH]; + if (detectionrun) return; - detectionrun = TRUE; - - twain_add_onedriver(L"sane.ds"); - twain_add_onedriver(L"gphoto2.ds"); -#if 0 - twain_add_onedriver(L"Largan\\sp503a.ds"); - twain_add_onedriver(L"vivicam10\\vivicam10.ds"); - twain_add_onedriver(L"ws30slim\\sp500a.ds"); -#endif + detectionrun = TRUE; + + GetWindowsDirectoryW(szWindowsDir, MAX_PATH); + swprintf( szTwainDir, MAX_PATH, L"%s\\twain_%u", szWindowsDir, sizeof(void *) * 8 ); + + twain_autodetect_recurse(szTwainDir); } /** -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9519
Esme Povirk (@madewokherd) commented about dlls/twaindsm/tests/dsm.c:
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");
I hadn't thought about tests. Would it make sense to run the tests twice: once with twain_32 (on i386 only) and once with twaindsm? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9519#note_124235
This merge request was approved by Esme Povirk. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9519
Esme Povirk (@madewokherd) commented about MAINTAINERS:
TWAIN P: Esme Povirk <esme(a)codeweavers.com> @madewokherd F: dlls/twain_32/ +F: dlls/twaindsm/ F: dlls/sane.ds/ F: dlls/gphoto2.ds/ FWIW, I think at this point you could add yourself as a "knowledgeable person" if you want. You certainly are much more knowledgeable of TWAIN than I am. I'm only there because I happened to work on one of those components once, for a reason I no longer remember.
But it's understandable if you don't want to be asked to review TWAIN MRs. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9519#note_124236
On Sat Nov 29 18:15:08 2025 +0000, Esme Povirk wrote:
I hadn't thought about tests. Would it make sense to run the tests twice: once with twain_32 (on i386 only) and once with twaindsm? There's no twaindsm.dll by default on Windows, so the tests won't do anything. We should keep them in twain_32.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9519#note_124385
On Tue Dec 2 09:42:59 2025 +0000, Alexandre Julliard wrote:
There's no twaindsm.dll by default on Windows, so the tests won't do anything. We should keep them in twain_32. Am Dienstag, dem 02.12.2025 um 03:43 -0600 schrieb Alexandre Julliard (@julliard):
Alexandre Julliard commented on a discussion on dlls/twaindsm/tests/dsm.c: https://gitlab.winehq.org/wine/wine/-/merge_requests/9519#note_124385
> 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");
There's no twaindsm.dll by default on Windows, so the tests won't do anything. We should keep them in twain_32.
I assume you want to keep the comparison what the code does on native Windows? But when associating the tests with twain_32.dll, which only exists on 32-Bit Windows, the tests would only test something in 32-Bit Windows and would not do anything in 64-Bit Windows and ARM platforms. Should we posibly add test code both for twaindsm and for twain_32, initially the same code just with different dll names? As Esme suggested? Or just do something like #ifdef WIN64 htwain = LoadLibraryA("twaindsm.dll"); #else htwain = LoadLibraryA("twain_32.dll"); #endif And try using the PARENTSRC feature? Bernd Herd :-) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9519#note_124434
But when associating the tests with twain_32.dll, which only exists on 32-Bit Windows, the tests would only test something in 32-Bit Windows and would not do anything in 64-Bit Windows and ARM platforms.
I think that's good enough. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9519#note_124435
participants (4)
-
Alexandre Julliard (@julliard) -
Bernd Herd -
Bernd Herd (@herdsoft) -
Esme Povirk (@madewokherd)