# Fault secnario
If an application loads twain_32.dll for every scan and unloads it when done with scanning, it very likely crashes when the user chooses to scan a second time within the same process. Unloading twain_32.dll is not uncommon behaviour.
# Internal error cause
When sane.ds gets loaded the first time, it retrieves the address of the DSM_Entry function from twain_32.dll and stores it in a variable SANE_dsmentry in the sane.ds global data. When the DS gets closed, the sane.ds does not get unloaded due to an old workaround in dlls/twain_32/dsm_ctrl.c:275:
``` twRC = currentDS->dsEntry (pOrigin, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, pData); /* This causes crashes due to still open Windows, so leave out for now. * FreeLibrary (currentDS->hmod); */ ```
When the application program closes the DSM (twain_32.dll) and reloads it, there is a hight probability that it will be loaded in a different memory location. So the exported DSM_Entry function is located at a different address. But the code in dlls/sane.ds/sane_main.c:60 does not update the pointer if SANE_dsmentry is not NULL:
``` if (SANE_dsmentry == NULL) { HMODULE moddsm = GetModuleHandleW(L"twain_32"); if (moddsm) SANE_dsmentry = (void*)GetProcAddress(moddsm, "DSM_Entry"); ```
So SANE_dsmentry still points to where the twain_32.dll was loaded the first time it loaded the sane.ds. Which is now an invalid address.
When later the DS wants to notify the application of an event (DS Closed, Image transfered..) it calls SANE_Notify in sane_main.c:357:
``` void SANE_Notify (TW_UINT16 message) { SANE_dsmentry (&activeDS.identity, &activeDS.appIdentity, DG_CONTROL, DAT_NULL, message, NULL); } ```
And here the crash occurs due to the invalid pointer in variable SANE_dsmentry.
# Suggested Solution
There are many ways how this problem could be solved. The solution suggested here is based on an extension of the TWAIN protocol in Version 2: The DSM transfers the entry points to it's callback functions in a message:
DG_CONTROL / DAT_ENTRYPOINT / MSG_SET
That message was already implemented in sane.ds and gphoto2.gs. And both also set the flag DF_DS2 in the identitiy information SupportedGroups, so the DSM knows it is allowed to send that message. So this approach is not a workaround, but an additional feature that solves the problem in the way intended according to the TWAIN specification. This also means that the patch should solve the problem in closed source TWAIN drivers a user might install.
However [sane.d](http://sane.de)s and gphoto2.ds do not fill the TW_IDENTITY information in the response to the DAT_IDENTITIY / MSG_OPENDS message. This is different from the behaviour observed on windows. For that reason the DF_DS2 flag in SupportedGroups is also not set, and thus a change in sane.ds is neccessary to make the DAT_ENTRYPOINT solution work.
This is also not just a workaround but a change of the behaviour so it becomes closer to what is observed on windows.
# Test scenario software
I've written a small 163 lines C application program to reproduce the erroneous behaviour. It is called unloaddsm.c and I'll attach it to this merge request. It is meant to be cross-compiled with mingw-w64. It loads the DSM, opens the DS (withour showing the UI), closes the DS, unloads DSM, re-loads DSM, re-opens the DS and then displays the UI (User Interface) when the user then clicks "cancel" or presses escape, this causes the call of SANE_Notify that leads to the crash.
It also shows the TW_IDENTITY fields that sane.ds did not fill. I ran the same program with a 32-Bit windows TWAIN driver on Windows 10 and there the idendity fields were filled.
With the patch applied, the error does not occur any more in sane.ds.
``` Archive: /tmp/unloaddsm.zip Length Date Time Name 5283 2025-10-18 23:31 unloaddsm.c 77104 2025-10-16 14:02 twain.h 161 2025-10-18 15:57 Makefile ```
-- v2: Avoid the syntax of an obsolete GNU extension for struct initialization