NSIS: calling DLL function with System::Call using absolute path with blanks - dll

I'm trying to call the function int MyFunction() from myfile.dll and to display it's return value in a message box.
What works so far:
SetOutPath "$TEMP"
File "myfile.dll"
;File "System.dll" for some time I thought one needs to copy the plugin, but that's not needed
MessageBox MB_OK "$0"
System::Call '$TEMP\myfile.dll::MyFunction()i.r0'
MessageBox MB_OK "$0"
System::Free 0
As soon as I replace $TEMP by $INSTDIR it doesn't work anymore. Actually it does, as long as $INSTDIR doesn't contain blanks, what C:\Program Files (x86) unfortunately does ...
At one point I figured out from this NSIS forum post that this behavior is a bug, namely this one. I didn't really figure out what's the status of it.
I also tried the workaround with kernel32::LoadLibrary and kernel32::GetProcAddress that was presented in both pages, but I don't know what to use in the line System::Call "::$1(the usual parameters here)".
So how do I call MyFunction() now?

The System plug-in supports quotes in the path now:
System::Call '"$InstDir\MyLibrary.dll"::MyFunction(i 42)'
Calling KERNEL32::AddDllDirectory is required if the DLL depends on other DLLs in the same directory but a single DLL should work without it.
When using kernel32::GetProcAddress the parameters are the same, it is only the path and function name that has changed to a direct address.

What fixed it in the end, was adding $INSTDIR to the search path, as suggested in this answer:
System::Call 'KERNEL32::AddDllDirectory(w "$INSTDIR")'

Related

How To Compress Folder-Contents in 1 Statement on Windows?

I'm attempting to zip a folder containing subfolders and items, using Windows shell CopyHere command:
https://msdn.microsoft.com/en-us/library/windows/desktop/bb787866(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ms723207(v=vs.85).aspx
Update: Note, prefer a native solution-- this is for a distributed Excel VBA tool, so bundling 3rd-party files is not ideal. And, need synchronous compression.
I can easily add a folder and its contents to the zip:
oShell.Namespace(sZipPath).CopyHere "C:\My Folder"
So we know CopyHere can process multiple objects inside a folder in 1 statement.
The problem is, the above command puts the containing-folder at the root of the zip, and it's contents inside of it. But, i don't want the containing folder-- just its contents.
The doc mentions a wildcard (option 128), but when i use a wildcard, i get an error:
oShell.Namespace(sZipPath).CopyHere "C:\My Folder\*"
The file name you specified is not valid or too long.
Perhaps there's a way to use my 1st command above, and then move the items in the zip to the root of the zip?
It would be acceptable to loop through each item in the source folder, adding one at a time to the zip. But, because CopyHere is asynchronous, each subsequent CopyHere fails if the previous CopyHere is not finished. None of the fixes work for this issue:
Comparing number of items in source-folder and destination-zip fail, because if the zip contains a folder, that counts as only 1 item (the items it contains are not counted. https://stackoverflow.com/a/16603850/209942
Waiting a while between each item works, but a timer is unacceptable: it's arbitrary. I cannot guess in advance the size or compress-time of each object.
Checking to see if the zip is locked for access failed for me. If I block my loop until the file is not locked, I still get a file-access error. https://stackoverflow.com/a/6666663/209942
Function FileIsOpen(sPathname As String) As Boolean ' true if file is open
Dim lFileNum As Long
lFileNum = FreeFile
Dim lErr As Long
On Error Resume Next
Open sPathname For Binary Access Read Write Lock Read Write As #lFileNum
lErr = Err
Close #lFileNum
On Error GoTo 0
FileIsOpen = (lErr <> 0)
End Function
Update: VBA can call shell commands synchronously (instead of creating a shell32.shell object in VBA), so if CopyHere works on command-line or PowerShell, that could be the solution. Investigating...
Automating Shell objects really isn't a viable approach as you have already discovered. The Explorer Shell doesn't really expose this capability in any other manner though, at least not before Windows Vista and then not in any fashion easily used from VB6 programs or VBA macros.
Your best bet is a 3rd party ActiveX library, but be careful about 64-bit VBA hosts where you'll need a 64-bit version of such a library.
Another option is to acquire a later copy of the zlibwapi.dll and use some VB6 wrapper code with it. This is also a 32-bit solution.
That's what Zipper & ZipWriter, Zipping from VB programs does. Considering your requirements (which for some reason includes a fear of the Timer control) you could use the synchronous ZipperSync Class. See post #4 there. That code includes a simple AddFolderToZipperSync bundling up the logic to add a folder instead of just a single file.
The downside of the synchronous class is that a large archival operation freezes your program UI until it completes. If you don't want that use the Zipper UserControl instead.
You could also take the ideas from that to write your own wrapper class.
Solution:
Windows contains another native compression utility: CreateFromDirectory at a PowerShell prompt.
https://msdn.microsoft.com/en-us/library/system.io.compression.zipfile.createfromdirectory(v=vs.110).aspx
https://blogs.technet.microsoft.com/heyscriptingguy/2015/03/09/use-powershell-to-create-zip-archive-of-folder/
This requires .Net 4.0 or later:
> Add-Type -AssemblyName System.IO.Compression
> $src = "C:\Users\v1453957\documents\Experiment\rezip\aFolder"
> $zip="C:\Users\v1453957\Documents\Experiment\rezip\my.zip"
> [io.compression.zipfile]::CreateFromDirectory($src, $zip)
Note, you may have to provide the complete pathnames-- active directory was not implicit on my machine.
The above compression is synchronous at the PowerShell prompt, as the OP requests.
Next step is executing synchronously from VBA. The solution there is the .Run method in Windows Script Host Object Model. In VBA, set a reference to that, and do the following, setting the 3rd parameter of .Run command, bWaitOnReturn to True:
Function SynchronousShell(sCmd As String)As Long
Dim oWSH As New IWshRuntimeLibrary.WshShell
ShellSynch = oWSH.Run(sCmd, 3, True)
Set oWSH = Nothing
End Function
Now call SynchronousShell, and pass it the entire compression script.
I believe the only way for this process to work is if CreateFromDirectory is executed in the same session as Add-Type.
So, we must pass the whole thing as 1 string. That is, load all 4 commands into a single sCmd variable, so that Add-Type remains associated with the subsequent CreateFromDirectory. In PowerShell syntax, you can separate them with ;
https://thomas.vanhoutte.be/miniblog/execute-multiple-powershell-commands-on-one-line/
Also, you'll want to use single-quotes instead of double-quotes, else double quotes around the strings are removed when the daisy-chained commands are passed to powershell.exe
https://stackoverflow.com/a/39801732/209942
sCmd = "ps4 Add-Type -AssemblyName System.IO.Compression; $src = 'C:\Users\v1453957\documents\Experiment\rezip\aFolder'; $zip='C:\Users\v1453957\Documents\Experiment\rezip\my.zip'; [io.compression.zipfile]::CreateFromDirectory($src, $zip)"
Solved. The above constitutes the complete solution.
Extra info: Additional comments below are for special circumstances:
Multi-version .Net environments
If a .NET < 4.0 is the active environment on your OS, then System.IO.Compression does not exist-- the Add-Type command will fail. But if your machine has the .NET 4 assemblies available, you can still do this:
Create a batch file which runs PowerShell with .Net 4. See https://stackoverflow.com/a/31279372
In your Add-Type command above, use the exact path to the .Net 4 Compression assembly. On my Win Server 2008:
Add-Type -Path "C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.IO.Compression.FileSystem\v4.0_4.0.0.0__b77a5c561934e089\System.IO.Compression.FileSystem.dll"
Portability
Turns out, on my machine, I can copy the compression dll to any folder, and make calls to the copy and it works:
Add-Type -Path "C:\MyFunnyFolder\System.IO.Compression.FileSystem.dll"
I don't know what's required to ensure this works-- it might require the full .Net 4.0 or 2.0 files to be located in their expected directories. I assume the dll makes calls to other .Net assemblies. Maybe we just got lucky with this one :)
Character Limit
Depending on the depth of our paths and filenames, character-count may be a concern. PowerShell may have a 260-character limit (not sure).
https://support.microsoft.com/en-us/kb/830473
https://social.technet.microsoft.com/Forums/windowsserver/en-US/f895d766-5ffb-483f-97bc-19ac446da9f8/powershell-command-size-limit?forum=winserverpowershell
Since .Run goes through the Windows shell, you also have to worry about that character limit, but at 8k+, it's a bit roomier:
https://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?p=41553
https://stackoverflow.com/a/3205048/209942
Site below offers a 24k+ character method, but i've not studied it yet:
http://itproctology.blogspot.com/2013/06/handling-freakishly-long-strings-from.html
At minimum, since we can put the dll wherever we like, we can put it in a folder near C: root-- keeping our character-count down.
Update: This post shows how we can put the whole thing in a script-file, and call it with ps4.cmd. This may become my preferred answer:
.\ps4.cmd GC .\zipper.ps1 | IEX
-- depending on answer here.
CopyHere:
Re the question: can CopyHere command execute on command-line?
CopyHere can be executed directly at PowerShell prompt (code below). However, even in powershell it's asynchronous-- control returns to PowerShell prompt before the process is finished. Therefore, no solution for the OP. Here's how it's done:
> $shellapp=new-object -com shell.application
> $zippath="test.zip"
> $zipobj=$shellapp.namespace((Get-Location).Path + "\$zippath")
> $srcpath="src"
> $srcobj=$shellapp.namespace((Get-Location).Path + "\$srcpath")
> $zipobj.Copyhere($srcobj.items())

How do I tell Octave where to find functions without picking up other files?

I've written an octave script, hello.m, which calls subfunc.m, and which takes a single input file, a command line argument, data.txt, which it loads with load(argv(){1}).
If I put all three files in the same directory, and call it like
./hello.m data.txt
then all is well.
But if I've got another data.txt in another directory, and I want to run my script on it, and I call
../helloscript/hello.m data.txt
this fails because hello.m can't find subfunc.m.
If I call
octave --path "../helloscript" ../helloscript/hello.m data.txt
then that seems to work fine.
The problem is that if I don't have a data.txt in the directory, then the script will pick up any data.txt that is lying around in ../helloscript.
This seems a bit fragile. Is there any way to tell octave, preferably in the script itself, to get subfunctions from the same directory as the script, but to get everything else relative to the current directory.
The best robust solution I can think of at the moment is to inline the subfunction in the script, which is a bit nasty.
Is there a good way to do this, or is it just a thorny problem that will cause occasional hard to find problems and can't be avoided?
Is this in fact just a general problem with scripting languages that I've just never noticed before? How does e.g. python deal with it?
It seems like there should be some sort of library-load-path that can be set without altering the data-load-path.
Adding all your subfunctions to your program file is not nasty at all. Why would you think so? It is perfectly normal to have function definitions in your script. The only language I know that does not do this is Matlab but that's just braindead.
The other alternative you have is to check that the input file argument, data.txt exists. Like so:
fpath = argv (){1};
[info, err, msg] = stat (fpath);
if (err)
error ("could not stat `%s' : %s", fpath, msg);
endif
## continue your script knowing the file exists
But really, I would recommend you to use both. Add your subfunctions in your main program, the only reason to have it on separate file is if you plan on sharing with other programs, and always check input arguments.

Location of formatter files for VIM

I found the following code snippet on the internet, and want to use it in my own .vimrc.
augroup CodeFormatters
autocmd!
autocmd BufReadPost,FileReadPost *.py :silent %!PythonTidy.py
augroup END
However, I'm not quite sure where to put the PythonTidy.py script, so that it is accessible from everywhere.
Furthermore I read that using BufReadPre is better than BufReadPost, respectively FileReadPre, is that true?
As it stands, PythonTidy.py must be accessible through your PATH. If you have a convenient place already contained in there, e.g. ~/bin, just place it there.
Alternatively, you can place it somewhere into your .vim directory, and use something like expand('<sfile>:p:h') to resolve the directory of your Vimscript, and build a relative path from there.
As you want to filter the read buffer contents with the :%! command, you have to use the BufReadPost event; with BufReadPre, the buffer hasn't yet been read and nothing would be sent to the filter.
PythonTidy is a command line executable: put it somewhere in your $PATH.
You can also put it anywhere and use an absolute path in the autocmd:
autocmd BufReadPost,FileReadPost *.py :silent %!/path/to/PythonTidy.py

Windows 7 environment variable not working in path

I am trying to set up some path using environment variable.
I added an environment variable "MAVEN_HOME" with the value "C:\maven".
Then in the path I added "%MAVEN_HOME%\bin;...rest".
When I type "echo $MAVEN_HOME%" I get the correct "C:\maven" printed on the screen.
But when I type "mvn" which is a batch file in the "bin" directory, it can't find it.
So, I manually added the entire path in PATH.
"C:\maven\bin;...rest"
and it was able to find "mvn" and execute it.
Could someone help me what I did wrong?
Check if there is a space character between the previous path and the next:
Incorrect:
c:\path1; c:\Maven\bin\; c:\path2\
Correct:
c:\path1;c:\Maven\bin\;c:\path2\
I had exactly the same problem, to solve it, you can do one of two things:
Put all variables in System Variables instead of User and add the ones you want to PATH
Or
Put all variables in User Variables, and create or edit the PATH variables in User Variable, not In System. The Path variables in System don't expand the User Variables.
If the above are all correct, but the problem is still present, you need to check the system Registry, in HKEY_CURRENT_USER\Environment, to make sure the "PATH" key type is REG_EXPAND_SZ (not REG_SZ).
My issue turned out to be embarrassingly simple:
Restart command prompt and the new variables should update
Things like having %PATH% or spaces between items in your path will break it. Be warned.
Yes, windows paths that include spaces will cause errors. For example an application added this to the front of the system %PATH% variable definition:
C:\Program Files (x86)\WebEx\Productivity Tools;C:\Sybase\IQ-16_0\Bin64;
which caused all of the paths in %PATH% to not be set in the cmd window.
My solution is to demarcate the extended path variable in double quotes where needed:
"C:\Program Files (x86)\WebEx\Productivity Tools";C:\Sybase\IQ-16_0\Bin64;
The spaces are therefore ignored and the full path variable is parsed properly.
%M2% and %JAVA_HOME% need to be added to a PATH variable in the USER variables, not the SYSTEM variables.
If there is any error at all in the PATH windows will silently disregard it. Things like having %PATH% or spaces between items in your path will break it. Be warned
Also worth making sure you're using the command prompt as an administrator - the system lock on my work machine meant that the standard cmd just reported mvn could not be found when typing
mvn --version
To use click 'start > all programs > accessories', right-click on 'command prompt' and select 'run as administrator'.
To address this problem, I have used setx command which try to set user level variables.
I used below...
setx JAVA_HOME "C:\Program Files\Java\jdk1.8.0_92"
setx PATH %JAVA_HOME%\bin
NOTE: Windows try to append provided variable value to existing variable value. So no need to give extra %PATH%... something like %JAVA_HOME%\bin;%PATH%
If the PATH value would be too long after your user's PATH variable has been concatenated onto the environment PATH variable, Windows will silently fail to concatenate the user PATH variable.
This can easily happen after new software is installed and adds something to PATH, thereby breaking existing installed software. Windows fail!
The best fix is to edit one of the PATH variables in the Control Panel and remove entries you don't need. Then open a new CMD window and see if all entries are shown in "echo %PATH%".
I had this problem in Windows 10 and it seemed to be solved after I closed "explorer.exe" in the Task Manager.
In my Windows 7.
// not working for me
D:\php\php-7.2.6-nts\php.exe
// works fine
D:\php\php-7.2.6-nts
I had the same problem, I fixed it by removing PATHEXT from user variable. It must only exist in System variable with .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
Also remove the variable from user to system and only include that path on user variable
Copy the value of path to notepad and check if this corresponds with the echo %path% in terminal window and make changes if needed. Then delete the old path value and paste the notepad value back in.
I assume some invisible character entered there by some installation corrupted the path value.
Make sure both your System and User paths are set correctly.

How to abort processing CMakeLists for current directory

I have a project structure like:
src/CMakeLists.txt
src/test/component1/CMakeLists.txt
src/test/component2/CMakeLists.txt
For the testing, I'm using Qt - however, I want to make sure that if Qt (or some other test-specific package is not found) I simply skip the package.
I tried
find_package(Qt4 QUIET COMPONENTS QtCore QtTestLib)
if (NOT QT4_FOUND)
message(SEND_ERROR "Qt4 not found - skipping building tests")
endif (NOT QT4_FOUND)
but that doesn't work like I want to since that still prevents the generation of the Makefiles. The only way I can think is to put the entire body of the CMakeLists file into the body of the conditional.
Is there a way to say "skip processing the remainder of this CMakeLists"?
From the CMake documentation
return: Return from a directory or function.
return()
Returns from a directory or function.
When this command is encountered, it
caused process of the current function
or directory to stop and control is
return to the caller of the function,
or the parent directory if any. Note
that a macro is not a function and
does not handle return like a function
does.