Pull to refresh

Compiling fast .exe console applications with PHP 8.1, why not?

Reading time4 min
Views4.9K

With each release, PHP is getting faster, and when JIT (Just-In-Time) compilation is enabled, it reaches almost the same C marks.

Many people at one time probably had a desire to easily write console and window applications. Guys sometimes wrote irreplaceable applications for solving small tasks and shared them on forums, although some of them contained small bugs.

However, times are changing, and people have begun to realize their mistakes, switching, say, to OOP.

To be nostalgic and demonstrate the new Frankenstein, we will build a full-fledged console exe application in PHP.

PHP is an interpreted language, so we just need to have its binary files to work.

To create an application, we will need the main exe file and the dll library of the engine. The 32-bit version of PHP 8.1 takes 7 MB! Hello World with a size of 7 meters does not suit us, agree?

Following instructions, I managed to compile PHP without any additional modules, leaving only JIT and FFI (there is a reason).

The size of the resulting files was 5 MB (x64), which is not bad, but not enough. We will continue to work with x64, since the difference between it and x86 is only 100kb.

php.exe + php8.dll:

Now we need PHP code for our application.

For the sake of example, let's take the Zend Benchmark script and add a few command-line arguments for tests.

To package our application into a single file, we will use the free Enigma Virtual Box utility.

But for this, Enigma needs a main handler that will call the interpreter.

Download the 32 bit version gcc, install, log off / log in to the account, take and do "gcc -o dabro.exe program.c", the contents of program.c (don't forget to change the folder name):

#include <windows.h>
#include <stdio.h>

int main(int argc, char **argv) {
    char command[1024];
    sprintf(command, "\"%s\\folder name\\render.exe\" -f \"%s\\folder name\\exe.c\" %s", getenv("LOCALAPPDATA"), getenv("LOCALAPPDATA"), GetCommandLineA() + strlen(argv[0]) + 1);

    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi;

    BOOL success = CreateProcess(NULL, command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
    if (!success) {
        DWORD error = GetLastError();
        printf("Error: %d\n", error);
        return error;
    }

    WaitForSingleObject(pi.hProcess, INFINITE);

    DWORD exitCode;
    GetExitCodeProcess(pi.hProcess, &exitCode);

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return exitCode;
}
Code for UTF-8 multilingual support (Visual Studio 2013+ required)
#define _CRT_SECURE_NO_WARNINGS

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <locale.h>

int wmain(int argc, wchar_t **argv) {

	wchar_t command[32000];

	if (argc >= 2 && wcslen(argv[1]) > 0 && argv[1][0] == L'-') {
		argv++;
		argc--;
		// Reset argument list if the first one starts with the dash
	}

	wchar_t localAppData[MAX_PATH];
	if (GetEnvironmentVariableW(L"LOCALAPPDATA", localAppData, MAX_PATH) == 0) {
		wprintf(L"Error: Unable to retrieve LOCALAPPDATA environment variable!\n");
		return -1;
	}

	swprintf(command, 32000, L"\"%s\\folder name\\render.exe\" -f \"%s\\folder name\\exe.c\" %s", localAppData, localAppData, GetCommandLineW() + wcslen(argv[0]) + 1);

	STARTUPINFOW si = { sizeof(si) };
	PROCESS_INFORMATION pi;
	if (!CreateProcessW(NULL, command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
		DWORD error = GetLastError();
		wprintf(L"Error: %d\n", error);
		return error;
	}

	WaitForSingleObject(pi.hProcess, INFINITE);

	DWORD exitCode;
	GetExitCodeProcess(pi.hProcess, &exitCode);

	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	return exitCode;
}

You can add the program icon via --icon=file.ico when compiling in gcc, or in Resource Tuner by adding an Icon resource.

Select the handler (input file), drag our source binaries into the Enigma window, use the "Local, %Application Data Folder%". In the Files Option, click on the compression check mark.

In the properties of files using the Virtualization Method, we put on -Write to disk, if not exists-, I advise you to put the script on -Always write to disk-, so that it would be easier to debug the code at first times. In the settings, we also remove all the ticks: "Map executable to temporary files, Allow the launch of virtual exes, etc". Save the .evb project to not to perform same actions every time.
The PHP binary is the file called render.exe , exe.c - is the PHP script.

I used the %temp% folder, since it was experimental
I used the %temp% folder, since it was experimental

After compiling the application, we get a portable file that weighs 2.7 MB!

If we went further and removed all unnecessary built-in dependencies, and also wrote a lightweight handler that unpacks the archive on its own, then perhaps the size of the application would be reduced to hundreds of kilobytes (write in the comments, if you know such).

Compressed .7z archive with the current binaries weighs only 1.5 MB.

For comparison, the compiled Hello World binary of the Go application weighs 1.2 MB.

Testing:

It works like clockwork so that you understand about the declared speed of the assembler code generated by the JIT, my machine passes the benchmark in 0.7s (with the function JIT enabled it's 0.3s), when the JIT is turned off, it sags to 2.4s. Implementing parallelism for resource-intensive computing operations is not a problem (popen() / proc_open functions), you can also use ready-made libraries like amphp/parallel.

The launch of the applications themselves is fast and takes less than a second.

Let's also calculate the root Merkle hash of a large file:

Have you forgotten about FFI?

Thanks to this extension, we can write C code directly in PHP, connect dll libraries, create WinAPI applications, here is an example of MessageBox code:

<?php
$ffi = FFI::cdef("
    int MessageBoxA(void*, const char*, const char*, int);
", "user32.dll");

$ffi->MessageBoxA(null, "Hello!", "Habr", 0);
?>

And here is the result:

Conclusion:

You can write simple executable applications in PHP (first app ever created with these methods). They can act as backend utilities for graphical wrappers, but you should not do this, because PHP has proven itself well in web development, and not in desktop solutions.

Experiments on a raw platform can lead to problems.

Tags:
Hubs:
Total votes 9: ↑8 and ↓1+7
Comments3

Articles