[Python] Make a small launcher for the packaged program of PyInstaller

package release

In the previous article, we released a script of Farmers World [Farmers World]:
"Chain Game Farmers World [Farmers World] Explodes, Publishing a Free and Open Source Auxiliary Hangup Script"

The script is developed using python, and it needs to be packaged into an exe to facilitate distribution to players.

Generally, we will use [PyInstaller] to package python programs

Of course, [Nuitka] , which has been popular in recent years, is also very powerful. When [Nuitka] packages a Python program, it will use a C compiler to compile the Python code into machine code, that is, the final generated executable file exe contains only machine code. , unlike [PyInstaller], [PyInstaller] just compresses the Python code into the exe in the form of resources , and it is easy to decompile and extract the python code using a tool like [pyinstxttractor] .

So [Nuitka] is very advantageous for python programs that need to protect the source code, but our python code is originally open source, so we don’t care about this issue. We use [PyInstaller] to package, which is faster and more compatible. .

Regardless of [Nuitka] or [PyInstaller], when packaging a python program, you can choose to package it into a single executable file exe, or package it into a directory. In addition to the executable file exe, there are a lot of dependencies in the directory .

Usually, we recommend packaging into a directory.

Because in principle, it is packaged into a single executable file. In fact, he just compresses the pile of dependent files in the original directory and puts them in the exe. When you double-click to run the exe, it will first decompress the pile of files to C. disk's temporary directory, and then start the exe from the temporary directory. This has some disadvantages:

  1. The startup is slow, and it takes a while to see the window after double-clicking, and the mouse hourglass keeps spinning.
  2. The temporary directory will not be cleaned up if the program terminates unexpectedly
  3. If multiple instances are started, it will be decompressed multiple times and decompressed to multiple temporary directories, occupying space on the C drive

So we usually pack it into a directory, like most commercial software, such as [Youdao Cloud Notes], you can see its installation directory, which is also a lot of files, including the main program.exe

Of course, commercial software generally provides an installation package. In addition to decompressing the entire directory to a specified location, the installation package will also create a desktop shortcut and start menu, so that users do not need to find the exe in the installation directory to double-click to run.

Of course, our script program can also make an installation package and generate shortcuts, but that is obviously not "green". We hope to provide it as green software, but we don't want users to search for exe in a large number of file directories. , and data files and configuration files are also mixed in it, which will be very ugly.
insert image description here
As shown in the figure above, openfarmer is packaged into a directory. After the user decompresses it, there will be a bunch of dependent files. The user needs to double-click the executable file gui.exe, and the configuration file user.yml and the log folder logs are also mixed in this directory. , appearing disorganized.

Launcher

In the end, we refer to the idea of ​​packaging and publishing unity games, and provide a launcher [launcher]. This launcher is responsible for starting and updating the game, and the local game and dependencies are placed in a directory.

We use C++ to develop this launcher.exe, compile it with /MT, and statically link to the CRT runtime library, so that this launcher.exe does not depend on any dlls (except system dlls) when running on windows, so that this launcher.exe will never run On most windows systems, it can be run directly by double-clicking.

What launcher.exe has to do is also very simple, start the target exe in the dist directory, and pass in the parameters intact, and the working directory of the program is in the directory where launcher.exe is located, not in the dist directory.

Openfarmer is packaged into a directory, and after adding launcher.exe, it looks like this:
insert image description here
clean and refreshing, all the clutter is in the dist directory packaged by PyInstaller, but you never need to open it.

Instructions

This launcher is open source:
✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱
https://github.com/encoderlee/launcher
✱✱✱ ✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱

It needs to be compiled with VS2022, and if the icon needs to be replaced, just cover favicon.ico and recompile.
Of course, the easiest and most trouble-free way is to download our compiled version directly from [Releases] on the right side of github, and then rename launcher.exe to be the same as the target exe file name in the dist folder. Pay attention to the name of the folder Must be "dist".

key code

#include <windows.h>
#include <string>
#include <filesystem>
using namespace std;


wstring GetExecutablePath()
{
    
    
	wchar_t buffer[MAX_PATH + 1] = {
    
     0 };
	GetModuleFileName(NULL, buffer, MAX_PATH);
	wstring path = buffer;
	size_t pos = path.rfind(L'\\');
	path.erase(pos + 1);
	return path;
}

wstring GetExecutableName()
{
    
    
	wchar_t buffer[MAX_PATH + 1] = {
    
     0 };
	GetModuleFileName(NULL, buffer, MAX_PATH);
	wstring path = buffer;
	size_t pos = path.rfind(L'\\');
	path.erase(0, pos + 1);
	return path;
}

bool Exec(wstring path, wstring cmdline)
{
    
    
	cmdline = L"\"" + path + L"\" " + cmdline;
	STARTUPINFO start_info = {
    
     sizeof(start_info) };
	start_info.dwFlags = STARTF_FORCEOFFFEEDBACK;
	PROCESS_INFORMATION process_info = {
    
     0 };
	if (!CreateProcess(NULL, (LPWSTR)cmdline.c_str(), NULL, NULL, FALSE, NULL, NULL, NULL, &start_info, &process_info))
		return false;
	WaitForInputIdle(process_info.hProcess, INFINITE);
	CloseHandle(process_info.hThread);
	CloseHandle(process_info.hProcess);
	return true;
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    
    
	wstring path = GetExecutablePath();
	wstring exe_name = GetExecutableName();
	wstring path_target = path + L"dist\\" + exe_name;
	if (!std::filesystem::exists(path_target))
	{
    
    
		MessageBox(NULL, (L"not find file: " + path_target).c_str(), L"error", MB_OK);
		return 2;
	}
	if (!Exec(path_target, lpCmdLine))
		return 3;
	return 0;
}

exchange discussion

insert image description here

Guess you like

Origin blog.csdn.net/CharlesSimonyi/article/details/124255129