# ExeWatch SDK - DLL Version

**SDK Version: 0.20.0** | **ABI Version: 1**

Use the ExeWatch SDK from **any Delphi version** (Delphi 5 through Delphi 13 Florence) or any language that can call Windows DLLs.

## Supported languages and toolchains

The DLL exports an `extern "C"` + `__stdcall` + `wchar_t*` + `#pragma pack(1)` surface — the lowest-common-denominator Windows ABI. Verified working with:

- **Delphi 5 through 13 Florence** (via the bundled `ExeWatchSDKv1Imports.pas`)
- **Embarcadero C++Builder**
- **Microsoft Visual C++** (MSVC / `cl.exe`, VS 2022 Build Tools tested with `/W4` = zero warnings)
- **Python 3.7+** via `ctypes` (though if you're writing a real Python app, the native [ExeWatch Python SDK](https://exewatch.com/ui/docs#python-sdk) is much nicer — `pip install exewatch`)

Expected to work without modification in any Windows-FFI-capable runtime: **MinGW-w64 gcc/g++**, **Clang on Windows**, **Rust** (`extern "stdcall"`), **Go** (`syscall.NewLazyDLL`), **C#** (`DllImport` with `CallingConvention.StdCall`), **VBA** (`Declare Function`), **AutoIt** (`DllCall`), and friends.

## Runnable samples

Full samples live in the public [exewatchsamples](https://github.com/danieleteti/exewatchsamples) repository:

- **C++Builder** VCL → `CPPBuilderWithDLLSDK/`
- **Microsoft Visual C++** console → `MSVCWithDLLSDK/`
- **Delphi** VCL via `ExeWatchSDKv1Imports.pas` → `DelphiWithDLLSDK/`

Each sample is a self-contained project with realistic init/log/breadcrumbs/timing/metrics patterns — not just ABI smoke tests.

## Files

| File | Description |
|------|-------------|
| `ExeWatchSDKv1DLL.dll` | 32-bit DLL (Win32) |
| `ExeWatchSDKv1DLL_x64.dll` | 64-bit DLL (Win64) |
| `ExeWatchSDKv1Imports.pas` | Delphi import unit (Delphi 5+ compatible) |
| `ExeWatchSDKv1.h` | C/C++ header (MSVC, MinGW, Clang, C++Builder) — supports static + dynamic loading via `#define EW_DYNAMIC_LOAD` |
| `ExeWatchSDKv1.dynload.c` | Optional C/C++ runtime-loader implementation (`LoadLibrary` + `GetProcAddress`) |

## Quick Start

### 1. Copy files to your project

Copy the appropriate DLL and `ExeWatchSDKv1Imports.pas` to your project directory.

### 2. Add the import unit

```pascal
uses
  ExeWatchSDKv1Imports;
```

### 3. Initialize and use

**Delphi 2009+ (Unicode Delphi):**
```pascal
// Using convenience wrappers (recommended)
EWInitialize('ew_win_your_api_key', 'customer-123', '1.0.0');
EWInfo('Application started');
EWDebug('Loading config', 'startup');
ew_Shutdown;
```

**Delphi 5/6/7/2005/2006/2007 (pre-Unicode):**
```pascal
// Option A: Use convenience wrappers (handle conversion automatically)
EWInitialize('ew_win_your_api_key', 'customer-123', '1.0.0');
EWInfo('Application started');

// Option B: Direct ew_* calls with manual WideString cast
ew_Initialize(
  PWideChar(WideString('ew_win_your_api_key')),
  PWideChar(WideString('customer-123')),
  PWideChar(WideString('1.0.0'))
);
ew_Info(PWideChar(WideString('Application started')), nil);
```

### 4. Shutdown before exit

```pascal
ew_Shutdown;
```

## Loading Modes

### Static Linking (default)

The DLL must be present when your application starts. If missing, the app won't launch.

```pascal
uses
  ExeWatchSDKv1Imports;  // Links at load time
```

### Dynamic Loading

Define `EW_DYNAMIC_LOAD` **before** the unit is included. The DLL is loaded on demand, allowing graceful degradation.

```pascal
{$DEFINE EW_DYNAMIC_LOAD}
uses
  ExeWatchSDKv1Imports;

// In your code:
if EWLoadDLL then
begin
  EWInitialize('ew_win_your_api_key', 'customer-123');
  EWInfo('SDK loaded successfully');
end
else
  ShowMessage('ExeWatch DLL not found - monitoring disabled');

// On shutdown:
ew_Shutdown;
EWUnloadDLL;
```

You can also specify a custom DLL path:
```pascal
EWLoadDLL('C:\MyApp\Libs\ExeWatchSDKv1DLL.dll');
```

## Advanced Configuration

Use `ew_InitializeEx` for full control over SDK settings:

```pascal
var
  Config: TEWDLLConfig;
begin
  EWConfigInit(Config);  // Fill with defaults
  Config.ApiKey := PWideChar(WideString('ew_win_your_api_key'));
  Config.CustomerId := PWideChar(WideString('customer-123'));
  Config.AppVersion := PWideChar(WideString('2.0.0'));
  Config.BufferSize := 50;
  Config.FlushIntervalMs := 10000;  // 10 seconds
  Config.SampleRate := 0.5;         // 50% sampling
  ew_InitializeEx(@Config);
end;
```

## Error Handling

All `ew_*` functions return an integer error code:

| Code | Constant | Meaning |
|------|----------|---------|
| 0 | `EW_OK` | Success |
| -1 | `EW_ERR_NOT_INITIALIZED` | SDK not initialized |
| -2 | `EW_ERR_ALREADY_INITIALIZED` | Already initialized |
| -3 | `EW_ERR_INVALID_PARAM` | Nil or invalid parameter |
| -4 | `EW_ERR_EXCEPTION` | Internal exception |
| -5 | `EW_ERR_BUFFER_TOO_SMALL` | Output buffer too small |
| -6 | `EW_ERR_VERSION_MISMATCH` | DLL/import ABI mismatch |

Get the last error message:
```pascal
var
  Msg: WideString;
begin
  Msg := EWGetLastErrorStr;
  // or manually:
  // ew_GetLastError(@Buf[0], Length(Buf));
end;
```

## API Reference

### Logging

```pascal
ew_Log(Level, Msg, Tag, ExtraDataJson);  // Full control
ew_Debug(Msg, Tag);     // Shortcut for debug level
ew_Info(Msg, Tag);      // Shortcut for info level
ew_Warning(Msg, Tag);   // Shortcut for warning level
ew_Error(Msg, Tag);     // Shortcut for error level
ew_Fatal(Msg, Tag);     // Shortcut for fatal level

// Error with stack trace (for exception handlers)
ew_ErrorWithStackTrace(Msg, Tag, StackTrace, ExceptionClass);
```

`Tag` can be `nil` (defaults to `'main'`). `ExtraDataJson` is optional JSON: `'{"key":"value"}'`.

### Exception Handling with Stack Trace

The DLL cannot hook into the host application's RTL, so stack traces must be captured
by the host app and passed explicitly:

```pascal
// In Application.OnException or try/except:
procedure OnAppException(Sender: TObject; E: Exception);
var
  StackTrace: string;
begin
  // If using JCL:  StackTrace := JclDebug.GetExceptionStackInfoAsString(E);
  // If using madExcept: StackTrace := madExcept.GetCrashStackTrace;
  // Without a library: pass empty string (exception class + message still logged)
  StackTrace := '';
  EWErrorWithStackTrace(E.Message, 'exception', StackTrace, E.ClassName);
end;
```

The SDK won't attempt its own stack trace capture when one is provided.

### Breadcrumbs

```pascal
ew_AddBreadcrumb(EW_BT_CLICK, 'ui', 'Button clicked', nil);
ew_AddBreadcrumb(EW_BT_NAVIGATION, 'nav', 'Opened settings', nil);
ew_ClearBreadcrumbs;
```

### Timing / Profiling

```pascal
ew_StartTiming(PWideChar('db-query'), nil);
// ... do work ...
ew_EndTiming(PWideChar('db-query'), ElapsedMs);
// ElapsedMs now contains duration in milliseconds

// Stack-based (LIFO):
ew_StartTiming(PWideChar('operation'), nil);
ew_EndLastTiming(ElapsedMs);
```

### Metrics

```pascal
ew_IncrementCounter(PWideChar('api_calls'), 1.0, nil);
ew_RecordGauge(PWideChar('memory_mb'), 256.5, nil);
```

### User Identity

```pascal
ew_SetUser(PWideChar('user-42'), PWideChar('john@example.com'), PWideChar('John'));
ew_ClearUser;
```

### Tags

```pascal
ew_SetTag(PWideChar('environment'), PWideChar('production'));
ew_RemoveTag(PWideChar('environment'));
ew_ClearTags;
```

### Customer ID

```pascal
ew_SetCustomerId(PWideChar('new-customer-456'));
```

### Device Info

```pascal
ew_SendDeviceInfo;  // Re-send hardware info
ew_SetCustomDeviceInfo(PWideChar('license'), PWideChar('enterprise'));
ew_SendCustomDeviceInfo;
```

### Callbacks

```pascal
procedure MyErrorHandler(ErrorMsg: PWideChar); stdcall;
begin
  WriteLn('ExeWatch error: ', WideString(ErrorMsg));
end;

// Register callback
ew_SetOnError(@MyErrorHandler);
```

**Important:** Callbacks may fire on a background thread (the shipper thread). If you need to update the UI from a callback, use `PostMessage`, `TThread.Synchronize`, or your framework's thread-safe mechanism.

## Feature Comparison

| Feature | Source SDK (.pas) | DLL Version |
|---------|:-----------------:|:-----------:|
| Logging (all levels) | Yes | Yes |
| Breadcrumbs | Yes | Yes |
| Timing/Profiling | Yes | Yes |
| Counters & Gauges | Yes | Yes |
| User Identity | Yes | Yes |
| Global Tags | Yes | Yes |
| Device Info | Yes | Yes |
| Custom Device Info | Yes | Yes |
| Customer ID | Yes | Yes |
| Callbacks | Yes | Yes |
| Format-style logging | Yes | No* |
| RegisterPeriodicGauge | Yes | No* |
| ErrorWithException | Yes | No* |
| ClientListener | Yes | No* |
| VCL/FMX exception hooks | Yes | No* |

\* These features require Delphi language constructs that cannot cross a DLL boundary safely. Use alternatives:
- **Format-style logging**: Format the string before calling `ew_Log`
- **PeriodicGauge**: Call `ew_RecordGauge` from a TTimer instead
- **ErrorWithException**: Extract `E.Message` and call `ew_Error`
- **VCL exception hook**: Install `Application.OnException` and call `ew_Error` from there

## Building from Source

Requires Delphi 12 Athens or later. The DLL project compiles the full SDK internally.

```bash
# 32-bit
dcc32 -NSSystem;System.Win;Winapi;System.Net;Data.Win -U<lib-path>/win32/release ExeWatchSDKv1DLL.dpr

# 64-bit
dcc64 -NSSystem;System.Win;Winapi;System.Net;Data.Win -U<lib-path>/win64/release ExeWatchSDKv1DLL.dpr
```
