365 lines
10 KiB
OpenEdge ABL
365 lines
10 KiB
OpenEdge ABL
[Platform::] Windows Platform.
|
|
|
|
A version of our operating system interface suitable for Microsoft Windows.
|
|
|
|
@ This Foundation module comes with two variant versions of the |Platform::|
|
|
section of code. The one you're reading compiles on Windows, and the other
|
|
on a POSIX operating system.
|
|
|
|
@h Microsoft Windows.
|
|
|
|
@d PLATFORM_STRING "windows"
|
|
@d LOCALE_IS_ISO
|
|
@d FOLDER_SEPARATOR '\\'
|
|
@d SHELL_QUOTE_CHARACTER '\"'
|
|
@d WINDOWS_JAVASCRIPT
|
|
@d INFORM_FOLDER_RELATIVE_TO_HOME ""
|
|
@d HTML_MAP_FONT_SIZE 11
|
|
|
|
= (very early code)
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <io.h>
|
|
#include <sys/stat.h>
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#include <shlobj.h>
|
|
#undef IN
|
|
#undef OUT
|
|
|
|
@ A Windows-safe form of |isdigit|. Annoyingly, the C specification allows
|
|
the implementation to have |char| either signed or unsigned. On Windows it's
|
|
generally signed. Now, consider what happens with a character value of
|
|
acute-e. This has an |unsigned char| value of 233. When stored in a |char|
|
|
on Windows, this becomes a value of |-23|. When this is passed to |isdigit()|,
|
|
we need to consider the prototype for |isdigit()|:
|
|
|
|
|int isdigit(int);|
|
|
|
|
So, when casting to int we get |-23|, not |233|. Unfortunately the return value
|
|
from |isdigit()| is only defined by the C specification for values in the
|
|
range 0 to 255 (and also EOF), so the return value for |-23| is undefined.
|
|
And with Windows GCC, |isdigit(-23)| returns a non-zero value.
|
|
|
|
@d isdigit(x) Platform::Windows_isdigit(x)
|
|
|
|
=
|
|
int Platform::Windows_isdigit(int c) {
|
|
return ((c >= '0') && (c <= '9')) ? 1 : 0;
|
|
}
|
|
|
|
@h Folder separator.
|
|
When using a Unix-like system such as Cygwin on Windows, it's inevitable that
|
|
paths will sometimes contain backslashes and sometimes forward slashes, meaning
|
|
a folder (i.e. directory) divide in either case. So:
|
|
(a) When writing such a divider, always write |FOLDER_SEPARATOR|, a backslash;
|
|
(b) When testing for such a divider, call the following.
|
|
|
|
=
|
|
int Platform::is_folder_separator(wchar_t c) {
|
|
return ((c == '\\') || (c == '/'));
|
|
}
|
|
|
|
@h Environment variables.
|
|
|
|
= (very early code)
|
|
char *Platform::getenv(const char *name) {
|
|
char *env = getenv(name);
|
|
if (env == 0) {
|
|
char value[MAX_PATH];
|
|
if (strcmp(name, "PWD") == 0) {
|
|
if (GetCurrentDirectoryA(MAX_PATH, value) != 0)
|
|
_putenv_s(name, value);
|
|
} else if (strcmp(name, "HOME") == 0) {
|
|
if (SHGetFolderPathA(0, CSIDL_PERSONAL, 0, SHGFP_TYPE_CURRENT, value) == 0)
|
|
_putenv_s(name, value);
|
|
}
|
|
env = getenv(name);
|
|
}
|
|
return env;
|
|
}
|
|
|
|
@h Executable location.
|
|
Fill the wide-char buffer |p| with the path to the current executable, up to
|
|
length |length|. This function is guaranteed to be called from only one
|
|
thread. Should the information be unavailable, or fail to fit into |p|,
|
|
truncate |p| to zero length. (On some platforms, the information will
|
|
always be unavailable: that doesn't mean we can't run on those platforms,
|
|
just that installation and use of Foundation-built tools is less convenient.)
|
|
|
|
=
|
|
void Platform::where_am_i(wchar_t *p, size_t length) {
|
|
DWORD result = GetModuleFileNameW(NULL, p, (DWORD)length);
|
|
if ((result == 0) || (result == length)) p[0] = 0;
|
|
}
|
|
|
|
@h Snprintf.
|
|
The C standard library function |snprintf| is not as standard as one might
|
|
like, and is oddly represented in some Cygwin libraries for Windows,
|
|
sometimes being differently named.
|
|
|
|
We would like to provide a wrapper function but this is troublesome with
|
|
variadic arguments, so instead here is a macro for the function name.
|
|
Happily, the Inform tools make very little use of this.
|
|
|
|
@d PLATFORM_SNPRINTF snprintf
|
|
|
|
@h Shell commands.
|
|
|
|
= (very early code)
|
|
int Platform::system(const char *cmd) {
|
|
char cmd_line[10*MAX_PATH];
|
|
|
|
/* Check if the command should be executed with the Windows cmd interpreter
|
|
or a Unix-like shell, depending on whether or not the executable to run is
|
|
given with a quoted path. */
|
|
int unix = (cmd[0] != '\"');
|
|
if (unix) {
|
|
/* For a Unix shell command, escape any double quotes and backslashes. */
|
|
char *pcl;
|
|
const char *pc;
|
|
strcpy(cmd_line, "sh -c \"");
|
|
for (pc = cmd, pcl = cmd_line+strlen(cmd_line); *pc != 0; ++pc, ++pcl) {
|
|
if ((*pc == '\\') || (*pc == '\"'))
|
|
*(pcl++) = '\\';
|
|
*pcl = *pc;
|
|
}
|
|
*(pcl++) = '\"';
|
|
*(pcl++) = 0;
|
|
} else {
|
|
/* Otherwise, run with the Windows command interpreter. */
|
|
strcpy(cmd_line, "cmd /s /c \"");
|
|
strcat(cmd_line, cmd);
|
|
strcat(cmd_line, "\"");
|
|
}
|
|
|
|
STARTUPINFOA start;
|
|
memset(&start, 0, sizeof start);
|
|
start.cb = sizeof start;
|
|
start.dwFlags = STARTF_USESHOWWINDOW;
|
|
start.wShowWindow = SW_HIDE;
|
|
|
|
PROCESS_INFORMATION process;
|
|
if (CreateProcessA(0, cmd_line, 0, 0, FALSE, CREATE_NO_WINDOW, 0, 0, &start, &process) == 0) {
|
|
if (unix)
|
|
fprintf(stderr, "A Unix-like shell \"sh\" (such as that from Cygwin) must be in the path.\n");
|
|
return -1;
|
|
}
|
|
|
|
CloseHandle(process.hThread);
|
|
if (WaitForSingleObject(process.hProcess, INFINITE) != WAIT_OBJECT_0) {
|
|
CloseHandle(process.hProcess);
|
|
return -1;
|
|
}
|
|
|
|
DWORD code = 10;
|
|
GetExitCodeProcess(process.hProcess, &code);
|
|
CloseHandle(process.hProcess);
|
|
|
|
return (int)code;
|
|
}
|
|
|
|
@h Directory handling.
|
|
|
|
=
|
|
int Platform::mkdir(char *transcoded_pathname) {
|
|
errno = 0;
|
|
int rv = mkdir(transcoded_pathname);
|
|
if (rv == 0) return TRUE;
|
|
if (errno == EEXIST) return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
void *Platform::opendir(char *dir_name) {
|
|
DIR *dirp = opendir(dir_name);
|
|
return (void *) dirp;
|
|
}
|
|
|
|
int Platform::readdir(void *D, char *dir_name,
|
|
char *leafname) {
|
|
char path_to[2*MAX_FILENAME_LENGTH+2];
|
|
struct _stat file_status;
|
|
int rv;
|
|
DIR *dirp = (DIR *) D;
|
|
struct dirent *dp;
|
|
if ((dp = readdir(dirp)) == NULL) return FALSE;
|
|
sprintf(path_to, "%s%c%s", dir_name, FOLDER_SEPARATOR, dp->d_name);
|
|
rv = _stat(path_to, &file_status);
|
|
if (rv != 0) return FALSE;
|
|
if (S_ISDIR(file_status.st_mode))
|
|
sprintf(leafname, "%s%c", dp->d_name, FOLDER_SEPARATOR);
|
|
else strcpy(leafname, dp->d_name);
|
|
return TRUE;
|
|
}
|
|
|
|
void Platform::closedir(void *D) {
|
|
DIR *dirp = (DIR *) D;
|
|
closedir(dirp);
|
|
}
|
|
|
|
@h Sync.
|
|
|
|
=
|
|
void Platform::rsync(char *transcoded_source, char *transcoded_dest) {
|
|
printf("Platform::rsync() is not yet implemented!\n");
|
|
}
|
|
|
|
@h Sleep. The Windows |Sleep| call measures time in milliseconds, whereas
|
|
POSIX |sleep| is for seconds.
|
|
|
|
=
|
|
void Platform::sleep(int seconds) {
|
|
Sleep((DWORD)(1000*seconds));
|
|
}
|
|
|
|
@h Notifications.
|
|
|
|
=
|
|
void Platform::notification(text_stream *text, int happy) {
|
|
}
|
|
|
|
@h Terminal setup.
|
|
The idea of this function is that if anything needs to be done to enable the
|
|
output of ANSI-standard coloured terminal output, then this function has the
|
|
chance to do it; similarly, it may need to configure itself to receive console
|
|
output with the correct locale (calling |Locales::get(CONSOLE_LOCALE)| to
|
|
find this).
|
|
|
|
=
|
|
#define WIN32CONS_RESET_MODE 1
|
|
#define WIN32CONS_RESET_OUTCP 2
|
|
|
|
int Win32_ResetConsole = 0;
|
|
DWORD Win32_ConsoleMode = 0;
|
|
UINT Win32_ConsoleOutCP = 0;
|
|
|
|
void Platform::Win32_ResetConsole(void) {
|
|
if (Win32_ResetConsole & WIN32CONS_RESET_MODE) {
|
|
HANDLE cons = GetStdHandle(STD_ERROR_HANDLE);
|
|
if (cons) SetConsoleMode(cons, Win32_ConsoleMode);
|
|
}
|
|
if (Win32_ResetConsole & WIN32CONS_RESET_OUTCP)
|
|
SetConsoleOutputCP(Win32_ConsoleOutCP);
|
|
}
|
|
|
|
void Platform::configure_terminal(void) {
|
|
HANDLE cons = GetStdHandle(STD_ERROR_HANDLE);
|
|
if (cons) {
|
|
if (GetConsoleMode(cons, &Win32_ConsoleMode)) {
|
|
if ((Win32_ConsoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0) {
|
|
if (SetConsoleMode(cons, Win32_ConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
|
|
Win32_ResetConsole |= WIN32CONS_RESET_MODE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Win32_ConsoleOutCP = GetConsoleOutputCP();
|
|
UINT newCP = 0;
|
|
int loc = Locales::get(CONSOLE_LOCALE);
|
|
if (loc == FILE_ENCODING_ISO_STRF)
|
|
newCP = 28591; /* ISO 8859-1 Latin */
|
|
else if (loc == FILE_ENCODING_UTF8_STRF)
|
|
newCP = CP_UTF8;
|
|
if ((newCP != 0) && SetConsoleOutputCP(newCP))
|
|
Win32_ResetConsole |= WIN32CONS_RESET_OUTCP;
|
|
|
|
if (Win32_ResetConsole != 0) atexit(Platform::Win32_ResetConsole);
|
|
}
|
|
|
|
@h Concurrency.
|
|
|
|
= (very early code)
|
|
typedef HANDLE foundation_thread;
|
|
typedef int foundation_thread_attributes;
|
|
|
|
struct Win32_Thread_Start { void *(*fn)(void *); void* arg; };
|
|
|
|
@
|
|
=
|
|
DWORD WINAPI Platform::Win32_Thread_Func(LPVOID param) {
|
|
struct Win32_Thread_Start* start = (struct Win32_Thread_Start*)param;
|
|
(start->fn)(start->arg);
|
|
free(start);
|
|
return 0;
|
|
}
|
|
|
|
int Platform::create_thread(foundation_thread *pt, const foundation_thread_attributes *pa,
|
|
void *(*fn)(void *), void *arg) {
|
|
struct Win32_Thread_Start* start = (struct Win32_Thread_Start*) malloc(sizeof (struct Win32_Thread_Start));
|
|
start->fn = fn;
|
|
start->arg = arg;
|
|
HANDLE thread = CreateThread(0, 0, Platform::Win32_Thread_Func, start, 0, 0);
|
|
if (thread == 0) {
|
|
free(start);
|
|
return 1;
|
|
} else {
|
|
*pt = thread;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int Platform::join_thread(foundation_thread pt, void** rv) {
|
|
return (WaitForSingleObject(pt, INFINITE) == WAIT_OBJECT_0) ? 0 : 1;
|
|
}
|
|
|
|
void Platform::init_thread(foundation_thread_attributes* pa, size_t size) {
|
|
}
|
|
|
|
size_t Platform::get_thread_stack_size(foundation_thread_attributes* pa) {
|
|
return 0;
|
|
}
|
|
|
|
@ To do: replace this with something finding the answer correctly. It ought
|
|
to be the number of logical cores (i.e., twice the number of physical cores
|
|
if there's hyperthreading).
|
|
|
|
=
|
|
int Platform::get_core_count(void) {
|
|
return 4;
|
|
}
|
|
|
|
@h Timestamp and file size.
|
|
There are implementations of the C standard library where |time_t| has
|
|
super-weird behaviour, but on almost all POSIX systems, time 0 corresponds to
|
|
midnight on 1 January 1970. All we really need is that the "never" value
|
|
is one which is earlier than any possible timestamp on the files we'll
|
|
be dealing with.
|
|
|
|
=
|
|
time_t Platform::never_time(void) {
|
|
return (time_t) 0;
|
|
}
|
|
|
|
time_t Platform::timestamp(char *transcoded_filename) {
|
|
struct stat filestat;
|
|
if (stat(transcoded_filename, &filestat) != -1) return filestat.st_mtime;
|
|
return Platform::never_time();
|
|
}
|
|
|
|
off_t Platform::size(char *transcoded_filename) {
|
|
struct stat filestat;
|
|
if (stat(transcoded_filename, &filestat) != -1) return filestat.st_size;
|
|
return (off_t) 0;
|
|
}
|
|
|
|
@h Mutexes.
|
|
|
|
@d CREATE_MUTEX(name)
|
|
static struct Win32_Mutex name = { INIT_ONCE_STATIC_INIT, { 0 }};
|
|
@d LOCK_MUTEX(name) {
|
|
BOOL pending;
|
|
InitOnceBeginInitialize(&(name.init), 0, &pending, 0);
|
|
if (pending) {
|
|
InitializeCriticalSection(&(name.crit));
|
|
InitOnceComplete(&(name.init), 0, 0);
|
|
}
|
|
EnterCriticalSection(&(name.crit));
|
|
}
|
|
@d UNLOCK_MUTEX(name) {
|
|
LeaveCriticalSection(&(name.crit));
|
|
}
|
|
|
|
= (very early code)
|
|
struct Win32_Mutex { INIT_ONCE init; CRITICAL_SECTION crit; };
|