dynarithmic / twain_library

Dynarithmic TWAIN Library, Version 5.x
Apache License 2.0
56 stars 24 forks source link

Issue setting a custom directory where the TWAIN Data Source Manager should be searched for #52

Closed mateo-jukic closed 1 year ago

mateo-jukic commented 1 year ago

Hi

I was playing around with the C# demo and I wanted to set a custom directory where the twain_32.dll should be searched for by using the DTWAIN_SetDSMSearchOrderEx method. I want to set it to either the current directory or give it a path to it.

I tried calling the DTWAIN_SetDSMSearchOrderEx method like this

TwainAPI.DTWAIN_SetDSMSearchOrderExA("C", string.Empty);

assuming it would use the current working directory for the search but it also sadly didn't work for me.

In the end I prefer this more explicit approach but even when I removed the leading backspaces it doesn't find it.

public DTwainDemo()
{
        //
        // Required for Windows Form Designer support
        //
        InitializeComponent();
        //
        // TODO: Add any constructor code after InitializeComponent call
        //
        sOrigTitle = this.Text;

        var workingDirectory = AppDomain.CurrentDomain.BaseDirectory;

        // Removing the leading backspaces
        workingDirectory = workingDirectory.Substring(0, workingDirectory.Length - 1);

        TwainAPI.DTWAIN_SetDSMSearchOrderExA("U", workingDirectory);

        var pathInt = -200;

        var sb = new StringBuilder(10000);

        var pathCharacterCount = TwainAPI.DTWAIN_GetDSMFullName(TwainAPI.DTWAIN_TWAINDSM_LEGACY, sb, int.MaxValue, ref pathInt);

        var pathFullName = sb.ToString();

        var success = TwainAPI.DTWAIN_SysInitialize();

        SelectedSource = IntPtr.Zero;

        if ( TwainAPI.DTWAIN_IsTwainAvailable() == 0)
        {
            SelectSource.Enabled = false;
            SelectSourceByNameBox.Enabled = false;
        }
}
dynarithmic commented 1 year ago

TwainAPI.DTWAIN_SetDSMSearchOrderExA("C", string.Empty);

The current directory will be the path of the DTWAIN DLL that is loaded. Is the DLL loaded at this location?

dynarithmic commented 1 year ago

Also, I am not sure if this will work using the current Windows OSes starting with Windows 10. There is a twain32_dll.mui in the en-US folder under the Windows system directory that also needs to be loaded when twain_32.dll is loaded., thus twain_32.dll is probably not being loaded due to the missing components. I haven't kept up with what else needs to be present for twain_32.dll to be loaded, but twain32_dll.mui is one of the components.

Also, you could run your application without trying to customize the loading of twain_32.dll, and then use a tool such as process explorer from SysInternals to see what DLLs are actually loaded when the application successfully starts a TWAIN session (or use the Output Window of Visual Studio to see what was loaded). Then try to gather the missing components, place it in the current directory, and see if that works.

However, another thing to consider is that in this day and age of malicious programs, there is a possibility of an application no longer being able to load twain_32.dll from any directory, as it may be detected as malicious by some hyperactive virus checker.

The new TWAINDSM.DLL does support being placed in any directory, so this may be the only choice for customizing which directory the DSM will get loaded.

mateo-jukic commented 1 year ago

TwainAPI.DTWAIN_SetDSMSearchOrderExA("C", string.Empty);

The current directory will be the path of the DTWAIN DLL that is loaded. Is the DLL loaded at this location?

I did put the twain_32.dll inside of the same folder as the dtwain32u.dll. I assume the dtwain32u.dll did get loaded correctly since it does allow me to use its functions and the demo app doesn't throw any errors regarding dtwain32u.dll.

dynarithmic commented 1 year ago

To test, I wrote a very simple program that calls the Windows API LoadLibrary(), giving the path to twain_32.dll. No DTWAIN is involved, just the API call.

1) If TWAIN_32.DLL is located in the Windows directory, LoadLibrary() returns successfully. 2) If TWAIN_32.DLL is located in another directory other than the Windows directory, LoadLibrary() fails.

So this indicates that there are missing components that twain_32.dll requires at runtime that do not exist outside the Windows directory. What those components are, I am not sure, except that the one named mui is required. But again, this may be all done for security reasons -- that is what I am suspecting, so that another application cannot hijack the twain_32.dll that exists for Windows.

Note that TWAINDSM.DLL is not protected, as this DSM is open source, thus is free to be placed anywhere on the system.

mateo-jukic commented 1 year ago

Thank you for your quick and detailed replies.

I tried the same thing with the 32-bit version of TWAINDSM.DLL. I did manage to get the correct path back and scan an image after connecting to my scanner.

Both DTWAIN_SetDSMSearchOrderEx and TwainAPI.DTWAIN_SetDSMSearchOrder methods were able to find the TWAINDSM.DLL that is located in the same file with the built executable.

The DTWAIN_IsTwainAvailable() method returns false in both cases strangely. If I comment out that check I am able to connect to my scanner and acquire the file.

public DTwainDemo()
{
            //
            // Required for Windows Form Designer support
            //
            InitializeComponent();

            //
            // TODO: Add any constructor code after InitializeComponent call
            //

            sOrigTitle = this.Text;

            var workingDirectory = AppDomain.CurrentDomain.BaseDirectory;

            TwainAPI.DTWAIN_SysInitialize();

            TwainAPI.DTWAIN_SetTwainDSM(TwainAPI.DTWAIN_TWAINDSM_VERSION2);

            //TwainAPI.DTWAIN_SetDSMSearchOrderExA("U", workingDirectory);

            TwainAPI.DTWAIN_SetDSMSearchOrder(TwainAPI.DTWAIN_TWAINDSMSEARCH_O);

            var pathInt = -200;

            var sb = new StringBuilder(10000);

            var pathCharacterCount = TwainAPI.DTWAIN_GetDSMFullName(TwainAPI.DTWAIN_TWAINDSM_VERSION2, sb, int.MaxValue, ref pathInt);

            var pathFullName = sb.ToString();

            SelectedSource = IntPtr.Zero;
            if ( TwainAPI.DTWAIN_IsTwainAvailable() == 0)
            {
                SelectSource.Enabled = false;
                SelectSourceByNameBox.Enabled = false;
            }
}
dynarithmic commented 1 year ago

Ok. It looks like the search for 32-bit TWAINDSM.DLL when using DTWAIN_IsTwainAvailable() will always looks for TWAIN_32.dll.

I will address this in the upcoming 5.3.0.4 version of DTWAIN.

dynarithmic commented 1 year ago

I commited a fix for the issue to the development-32bit and development-64bit branches.

You can test the DLL's to see if this issue has been resolved.

Note that you will need to call DTWAIN_IsTwainAvailable after calling DTWAIN_SysInitialize, since other changes to DTWAIN in the development version have been made to make DTWAIN better behaved when running in a multithreaded environment.

mateo-jukic commented 1 year ago

I can confirm that the DTWAIN_IsTwainAvailable() method correctly detects TWAINDSM.DLL now.

Thank you once again for everything.