Architecture Overview
simple_env provides a clean Eiffel interface to environment variables using direct Win32 API calls. Unlike the standard Eiffel EXECUTION_ENVIRONMENT, it's designed specifically for SCOOP compatibility and uses inline C for performance.
+------------------+
| SIMPLE_ENV |
| (Facade) |
+------------------+
| + get() |
| + set() |
| + unset() |
| + expand() |
| + has() |
| + all_names() |
+------------------+
|
| Inline C externals
v
+------------------+
| simple_env.h |
| (C Header) |
+------------------+
| se_get_env() |
| se_set_env() |
| se_unset_env() |
| se_expand_env() |
| se_env_exists() |
+------------------+
|
v
+------------------+
| Win32 API |
+------------------+
| GetEnvironment |
| VariableA() |
| SetEnvironment |
| VariableA() |
| ExpandEnvironment|
| StringsA() |
+------------------+
Design Decisions
Why Not Use EXECUTION_ENVIRONMENT?
The standard Eiffel EXECUTION_ENVIRONMENT class has several limitations:
- Not SCOOP-compatible - Uses internal caching that isn't thread-safe
- No string expansion - Can't expand %VAR% references
- No enumeration - Can't list all variables
- Limited error handling - No way to check if operations succeed
Why Direct Win32 API?
- SCOOP compatibility - No shared state between calls
- Full functionality - Access to all environment features
- Performance - No intermediate layers
- Reliability - Direct system calls with proper error handling
Why Inline C Pattern?
Following Eric Bezault's inline C pattern, all C code is embedded directly in the Eiffel source:
- No separate .c/.obj files to compile
- Simpler build process
- All code in one place for maintenance
- Portable across Eiffel compilers
C Implementation
Header File Structure
The simple_env.h header contains all C implementations:
// simple_env.h - Environment variable operations
#include <windows.h>
#include <stdlib.h>
#include <string.h>
// Get environment variable value
// Returns allocated string (caller must free) or NULL
static inline char* se_get_env(const char* name) {
DWORD size = GetEnvironmentVariableA(name, NULL, 0);
if (size == 0) return NULL;
char* buffer = (char*)malloc(size);
if (buffer == NULL) return NULL;
GetEnvironmentVariableA(name, buffer, size);
return buffer;
}
// Set environment variable
// Returns 1 on success, 0 on failure
static inline int se_set_env(const char* name, const char* value) {
return SetEnvironmentVariableA(name, value) ? 1 : 0;
}
// Unset environment variable
static inline int se_unset_env(const char* name) {
return SetEnvironmentVariableA(name, NULL) ? 1 : 0;
}
// Expand environment strings (%VAR% -> value)
static inline char* se_expand_env(const char* input) {
DWORD size = ExpandEnvironmentStringsA(input, NULL, 0);
if (size == 0) return NULL;
char* buffer = (char*)malloc(size);
if (buffer == NULL) return NULL;
ExpandEnvironmentStringsA(input, buffer, size);
return buffer;
}
// Check if variable exists
static inline int se_env_exists(const char* name) {
DWORD size = GetEnvironmentVariableA(name, NULL, 0);
return (size > 0 || GetLastError() != ERROR_ENVVAR_NOT_FOUND) ? 1 : 0;
}
Memory Management
The C functions allocate memory that must be freed by the caller:
-- Eiffel side: always free allocated memory
l_result := c_se_get_env (l_name.item)
if l_result /= default_pointer then
Result := pointer_to_string (l_result)
c_free (l_result) -- Essential: prevent memory leak
end
Eiffel External Bindings
Inline C Pattern
feature {NONE} -- C externals
c_se_get_env (a_name: POINTER): POINTER
-- Get environment variable value. Caller must free result.
external "C inline use %"simple_env.h%""
alias "return se_get_env((const char*)$a_name);"
end
c_se_set_env (a_name, a_value: POINTER): INTEGER
-- Set environment variable. Returns 1 on success.
external "C inline use %"simple_env.h%""
alias "return se_set_env((const char*)$a_name, (const char*)$a_value);"
end
c_se_expand_env (a_input: POINTER): POINTER
-- Expand environment strings. Caller must free result.
external "C inline use %"simple_env.h%""
alias "return se_expand_env((const char*)$a_input);"
end
c_free (a_ptr: POINTER)
-- Free allocated memory.
external "C inline use <stdlib.h>"
alias "free($a_ptr);"
end
String Conversion
Eiffel strings are converted to C strings using C_STRING:
get (a_name: READABLE_STRING_GENERAL): detachable STRING_32
local
l_name: C_STRING
l_result: POINTER
do
-- Convert Eiffel string to C string
create l_name.make (a_name.to_string_8)
-- Call C function
l_result := c_se_get_env (l_name.item)
-- Convert result back to Eiffel string
if l_result /= default_pointer then
Result := pointer_to_string (l_result)
c_free (l_result)
end
end
Variable Enumeration
Implementation Strategy
Enumerating all environment variables requires accessing the process environment block:
// C implementation: Get all variable names
char* se_get_all_names(void) {
char* env_block = GetEnvironmentStringsA();
if (env_block == NULL) return NULL;
// Count total size needed
size_t total_size = 0;
char* ptr = env_block;
while (*ptr) {
char* eq = strchr(ptr, '=');
if (eq && eq != ptr) {
total_size += (eq - ptr) + 1; // name + null
}
ptr += strlen(ptr) + 1;
}
total_size++; // Final null terminator
// Allocate and copy names only
char* result = (char*)malloc(total_size);
// ... copy logic ...
FreeEnvironmentStringsA(env_block);
return result;
}
Eiffel Parsing
The Eiffel side parses the null-separated string block:
all_names: ARRAYED_LIST [STRING_32]
local
l_ptr: POINTER
l_pos: INTEGER
l_name: STRING_8
l_char: CHARACTER
do
create Result.make (100)
l_ptr := c_se_get_all_names
if l_ptr /= default_pointer then
from
l_pos := 0
create l_name.make_empty
until
-- Double null = end of block
c_char_at (l_ptr, l_pos) = '%U' and
c_char_at (l_ptr, l_pos + 1) = '%U'
loop
l_char := c_char_at (l_ptr, l_pos)
if l_char = '%U' then
-- End of one name
if not l_name.is_empty then
Result.extend (l_name.to_string_32)
create l_name.make_empty
end
else
l_name.append_character (l_char)
end
l_pos := l_pos + 1
end
c_free (l_ptr)
end
end
SCOOP Compatibility
Design Principles
simple_env is designed for SCOOP from the ground up:
- No shared state - Each operation is independent
- No internal caching - Always queries the system
- Atomic operations - Each Win32 call is atomic
- Instance independence - Multiple instances work correctly
Safe Usage Pattern
local
env: separate SIMPLE_ENV
do
create env
use_environment (env)
end
use_environment (a_env: separate SIMPLE_ENV)
local
config_path: STRING_32
do
-- String must be copied for use outside separate block
if attached a_env.get ("CONFIG") as v then
config_path := v.twin
end
-- Now config_path can be used outside the handler
end
Why EXECUTION_ENVIRONMENT Isn't SCOOP-Safe
-- EXECUTION_ENVIRONMENT uses internal caching:
-- 1. First call: reads from system, stores in cache
-- 2. Second call: returns cached value
-- Problem: cache is shared mutable state!
-- SIMPLE_ENV: no caching, always direct API call
-- 1. Every call: reads directly from system
-- 2. No shared state between calls
Design by Contract
Preconditions
get (a_name: READABLE_STRING_GENERAL): detachable STRING_32
require
name_not_empty: not a_name.is_empty
set (a_name, a_value: READABLE_STRING_GENERAL)
require
name_not_empty: not a_name.is_empty
expand (a_string: READABLE_STRING_GENERAL): STRING_32
require
string_not_empty: not a_string.is_empty
Postconditions
set (a_name, a_value: READABLE_STRING_GENERAL)
ensure
variable_set: last_operation_succeeded implies attached get (a_name)
unset (a_name: READABLE_STRING_GENERAL)
ensure
variable_removed: last_operation_succeeded implies not has (a_name)
all_names: ARRAYED_LIST [STRING_32]
ensure
result_attached: Result /= Void
Win32 API Reference
| Win32 Function | SIMPLE_ENV Feature | Purpose |
|---|---|---|
GetEnvironmentVariableA |
get, has | Retrieve variable value |
SetEnvironmentVariableA |
set, unset | Set or remove variable |
ExpandEnvironmentStringsA |
expand | Expand %VAR% references |
GetEnvironmentStringsA |
all_names | Get environment block |
FreeEnvironmentStringsA |
all_names (internal) | Free environment block |
Note: The "A" suffix indicates ANSI (8-bit) character versions. Unicode (UTF-16) versions exist with "W" suffix but are not currently used.