🔐 Dotenv C++ Library

A cross-platform C++ library for loading environment variables from .env files

v2.0.0

Overview

Dotenv C++ is a lightweight, cross-platform library that allows you to load environment variables from .env files into your application. Designed for runtime DLL/shared library usage with thread-safe operations.

📁 Simple File Format

Uses standard .env file format with key=value pairs

🔤 Quoted Values

Support for single and double quoted string values

🌐 Cross-Platform

Works on Windows, Linux, and macOS systems

📦 Shared Library

Distributed as DLL/SO/DYLIB for easy integration

🔌 Multiple APIs

C++ and C interfaces available

⚡ Dynamic Loading

Runtime DLL loading supported

🔒 Thread-Safe

Mutex-protected operations for concurrent access

⚠️ Error Handling

Proper error codes and messages

💬 Inline Comments

Support for comments after values

🔄 Escape Sequences

Support for \n, \t, \\ escape sequences

✂️ Whitespace Trimming

Automatic trimming of keys and values

🔧 CMake Support

Modern CMake and Makefile build systems

Installation

Download

Download the latest release from the GitHub releases page.

Files by Platform

Platform Library Import Library
Windows dotenv.dll libdotenv.a
Linux libdotenv.so -
macOS libdotenv.dylib -

Quick Setup

  1. Copy the shared library to your project's library directory
  2. Copy dotenv.hpp to your project's include directory
  3. Link against the library in your build configuration

Requirements

Usage

1. Create a .env file

# .env file
DATABASE_URL=postgresql://localhost/mydb
API_KEY="your-api-key-here"
SECRET='single-quoted-value'
PORT=3000
DEBUG=true

# Inline comments
HOST=localhost # This is the server host

# Escape sequences (in double quotes)
MESSAGE="Line1\nLine2\tTabbed"

# Whitespace is trimmed
  KEY_WITH_SPACES  =  value with spaces trimmed  

2. C++ API

The modern C++ API with error handling:

#include "dotenv.hpp"
#include <iostream>

int main() {
    // Load environment variables from .env
    DotenvError result = Dotenv::load(".env");
    if (result != DotenvError::Success) {
        std::cerr << "Error: " << Dotenv::getLastError() << std::endl;
        return 1;
    }
    
    // Get environment variables with default values
    std::string dbUrl = Dotenv::get("DATABASE_URL", "localhost");
    std::string apiKey = Dotenv::get("API_KEY");
    
    // Check if a variable exists
    if (Dotenv::has("DEBUG")) {
        std::cout << "Debug mode enabled" << std::endl;
    }
    
    // Get all loaded keys
    auto keys = Dotenv::getLoadedKeys();
    for (const auto& key : keys) {
        std::cout << "Loaded: " << key << std::endl;
    }
    
    // Clear all loaded variables
    Dotenv::clear();
    
    return 0;
}

3. C API

For C compatibility and runtime DLL loading:

#include "dotenv.hpp"
#include <stdio.h>

int main() {
    // Load environment variables
    int result = DotenvLoad(".env");
    if (result != 0) {
        printf("Error: %s\n", DotenvGetLastError());
        return 1;
    }
    
    // Get variables with default value
    const char* port = DotenvGet("PORT", "8080");
    printf("Port: %s\n", port);
    
    // Check if variable exists
    if (DotenvHas("DEBUG")) {
        printf("Debug mode enabled\n");
    }
    
    // Check if loaded
    if (DotenvIsLoaded()) {
        printf("Environment loaded successfully\n");
    }
    
    // Clear loaded variables
    DotenvClear();
    
    return 0;
}

4. Dynamic Runtime Loading (Windows)

Load the DLL at runtime without static linking:

#include <windows.h>
#include <iostream>

typedef int (*DotenvLoadFunc)(const char*);
typedef const char* (*DotenvGetFunc)(const char*, const char*);
typedef int (*DotenvHasFunc)(const char*);

int main() {
    // Load the DLL at runtime
    HMODULE hDll = LoadLibrary("dotenv.dll");
    if (!hDll) {
        std::cerr << "Failed to load dotenv.dll" << std::endl;
        return 1;
    }

    // Get function pointers
    auto loadFunc = (DotenvLoadFunc)GetProcAddress(hDll, "DotenvLoad");
    auto getFunc = (DotenvGetFunc)GetProcAddress(hDll, "DotenvGet");
    auto hasFunc = (DotenvHasFunc)GetProcAddress(hDll, "DotenvHas");
    
    if (loadFunc && getFunc) {
        loadFunc(".env");
        std::cout << "API Key: " << getFunc("API_KEY", "") << std::endl;
        
        if (hasFunc && hasFunc("DEBUG")) {
            std::cout << "Debug mode enabled" << std::endl;
        }
    }
    
    // Clean up
    FreeLibrary(hDll);
    return 0;
}

5. Dynamic Runtime Loading (Linux/macOS)

Load the shared library at runtime on Unix systems:

#include <dlfcn.h>
#include <iostream>

typedef int (*DotenvLoadFunc)(const char*);
typedef const char* (*DotenvGetFunc)(const char*, const char*);
typedef int (*DotenvHasFunc)(const char*);

int main() {
    // Load the shared library
    void* handle = dlopen("./libdotenv.so", RTLD_LAZY);  // Use .dylib on macOS
    if (!handle) {
        std::cerr << "Failed to load library: " << dlerror() << std::endl;
        return 1;
    }

    // Get function pointers
    auto loadFunc = (DotenvLoadFunc)dlsym(handle, "DotenvLoad");
    auto getFunc = (DotenvGetFunc)dlsym(handle, "DotenvGet");
    auto hasFunc = (DotenvHasFunc)dlsym(handle, "DotenvHas");
    
    if (loadFunc && getFunc) {
        loadFunc(".env");
        std::cout << "API Key: " << getFunc("API_KEY", "") << std::endl;
        
        if (hasFunc && hasFunc("DEBUG")) {
            std::cout << "Debug mode enabled" << std::endl;
        }
    }
    
    // Clean up
    dlclose(handle);
    return 0;
}

.env File Format

Supported Features:
  • Comments start with #
  • Key-value pairs: KEY=value
  • Double quoted values: KEY="value with spaces"
  • Single quoted values: KEY='value'
  • Inline comments: KEY=value # comment
  • Escape sequences: \n, \t, \\
  • Whitespace trimming on keys and values
  • Empty lines are ignored
# Database Configuration
DATABASE_URL=postgresql://localhost:5432/mydb
DB_USER=admin
DB_PASSWORD="secure_password_123"

# API Settings
API_KEY="sk-1234567890abcdef"
API_ENDPOINT=https://api.example.com

# Application Settings
PORT=3000
DEBUG=true
LOG_LEVEL=info

# Escape sequences example
MULTILINE="Line 1\nLine 2\nLine 3"

API Reference

C++ API (Dotenv class)

Dotenv::load()

static DotenvError Dotenv::load(const std::string& filename = ".env");
static DotenvError Dotenv::load(const std::string& filename, const DotenvOptions& options);

Description: Loads environment variables from the specified file.

Parameter Type Description
filename const std::string& Path to the .env file (default: ".env")
options const DotenvOptions& Optional configuration for loading behavior

Returns: DotenvError enum value indicating success or failure.

Dotenv::get()

static std::string Dotenv::get(const std::string& key, const std::string& defaultValue = "");

Description: Retrieves the value of an environment variable.

Parameter Type Description
key const std::string& The name of the environment variable
defaultValue const std::string& Value to return if variable doesn't exist (default: "")

Returns: The value of the variable or the default value.

Dotenv::has()

static bool Dotenv::has(const std::string& key);

Description: Checks if an environment variable exists.

Returns: true if the variable exists, false otherwise.

Dotenv::getLoadedKeys()

static std::vector<std::string> Dotenv::getLoadedKeys();

Description: Returns a list of all keys that were loaded from the .env file.

Returns: Vector of key names.

Dotenv::clear()

static void Dotenv::clear();

Description: Clears all environment variables that were loaded by the library.

Dotenv::isLoaded()

static bool Dotenv::isLoaded();

Description: Checks if any .env file has been successfully loaded.

Returns: true if loaded, false otherwise.

Dotenv::getLastError()

static std::string Dotenv::getLastError();

Description: Gets the error message from the last operation.

Returns: Error message string.

DotenvOptions Structure

struct DotenvOptions {
    bool overwrite = true;       // Overwrite existing env vars
    bool interpolate = false;    // Interpolate variables ${VAR}
    bool stripQuotes = true;     // Strip quotes from values
    bool trimWhitespace = true;  // Trim whitespace from keys and values
};

Error Codes (DotenvError)

Code Value Description
Success 0 Operation completed successfully
FileNotFound 1 Could not open the specified file
ParseError 2 Error parsing the file contents
InvalidKey 3 Invalid key format encountered

C API Functions

Note: String pointers returned by DotenvGet and DotenvGetLastError are valid until the next call to the same function. Copy the string immediately if you need to preserve the value.

DotenvLoad()

int DotenvLoad(const char* filename);

Description: Loads environment variables from the specified file.

Returns: 0 on success, error code on failure.

DotenvGet()

const char* DotenvGet(const char* key, const char* defaultValue);

Description: Retrieves the value of an environment variable.

Returns: Pointer to the value string or default value.

DotenvHas()

int DotenvHas(const char* key);

Description: Checks if an environment variable exists.

Returns: 1 if exists, 0 otherwise.

DotenvClear()

void DotenvClear(void);

Description: Clears all loaded environment variables.

DotenvIsLoaded()

int DotenvIsLoaded(void);

Description: Checks if the library has loaded any file.

Returns: 1 if loaded, 0 otherwise.

DotenvGetLastError()

const char* DotenvGetLastError(void);

Description: Gets the last error message.

Returns: Pointer to error message string.

CallDotenvLoad() (Legacy)

void CallDotenvLoad(const char* filename_c_str);

Description: Legacy function for backwards compatibility. Use DotenvLoad instead.

C API Summary Table

Function Description
DotenvLoad(filename) Load variables, returns 0 on success
DotenvGet(key, default) Get variable value
DotenvHas(key) Returns 1 if exists, 0 otherwise
DotenvClear() Clear all loaded variables
DotenvIsLoaded() Returns 1 if loaded, 0 otherwise
DotenvGetLastError() Get last error message
CallDotenvLoad(filename) Legacy function (backward compatible)

Building from Source

Requirements

Build with CMake (Recommended)

# Create build directory
mkdir build && cd build

# Configure
cmake ..

# Build
cmake --build .

# Run tests
ctest --output-on-failure

Build with Make

Build the Library

# Build the library (auto-detects platform)
make

# Or explicitly
make lib

Run Tests

make test

Clean Build Artifacts

make clean

Install (Unix Systems)

sudo make install

# Or with custom prefix
sudo make install PREFIX=/usr/local

Compiler Flags

The library is built with the following flags:

Output Files by Platform

Platform Library Object File
Windows bin/dotenv.dll, bin/libdotenv.a bin/dotenv.o
Linux bin/libdotenv.so bin/dotenv.o
macOS bin/libdotenv.dylib bin/dotenv.o

Code Signing (Windows)

For production deployments, you can sign the DLL to verify its authenticity.

Create Test Certificate

For testing purposes, create a self-signed certificate:

powershell -ExecutionPolicy Bypass -File create-cert.ps1
⚠️ Warning: Self-signed certificates will trigger security warnings. For production use, obtain a certificate from a trusted Certificate Authority (CA).

Sign the DLL

make sign

This uses the default certificate (certificate.pfx) with password dotenv123.

Custom Certificate

To use a different certificate:

make sign CERT_FILE=path/to/cert.pfx CERT_PASSWORD=yourpassword

Production Certificates

For production use, purchase a code signing certificate from:

Examples

Web Server Configuration

#include "dotenv.hpp"
#include <iostream>

int main() {
    // Load with error handling
    DotenvError result = Dotenv::load(".env");
    if (result != DotenvError::Success) {
        std::cerr << "Failed to load .env: " << Dotenv::getLastError() << std::endl;
        return 1;
    }
    
    // Get configuration with defaults
    std::string host = Dotenv::get("SERVER_HOST", "localhost");
    std::string port = Dotenv::get("SERVER_PORT", "8080");
    
    std::cout << "Starting server on " << host << ":" << port << std::endl;
    
    // Check debug mode
    if (Dotenv::has("DEBUG") && Dotenv::get("DEBUG") == "true") {
        std::cout << "Debug mode enabled" << std::endl;
    }
    
    return 0;
}

Database Connection

#include "dotenv.hpp"
#include <iostream>

class Database {
public:
    static bool connect() {
        if (Dotenv::load(".env") != DotenvError::Success) {
            std::cerr << "Failed to load configuration" << std::endl;
            return false;
        }
        
        // Check required variables
        if (!Dotenv::has("DATABASE_URL") || 
            !Dotenv::has("DB_USER") || 
            !Dotenv::has("DB_PASSWORD")) {
            std::cerr << "Missing database credentials" << std::endl;
            return false;
        }
        
        std::string url = Dotenv::get("DATABASE_URL");
        std::string user = Dotenv::get("DB_USER");
        std::string pass = Dotenv::get("DB_PASSWORD");
        
        std::cout << "Connecting to database..." << std::endl;
        // Your database connection code here
        
        return true;
    }
};

int main() {
    if (!Database::connect()) {
        return 1;
    }
    return 0;
}

List All Loaded Variables

#include "dotenv.hpp"
#include <iostream>

int main() {
    Dotenv::load(".env");
    
    std::cout << "Loaded environment variables:" << std::endl;
    for (const auto& key : Dotenv::getLoadedKeys()) {
        std::cout << "  " << key << "=" << Dotenv::get(key) << std::endl;
    }
    
    return 0;
}

C API with Runtime Loading

#include <stdio.h>
#include <dlfcn.h>  // Use windows.h on Windows

typedef int (*DotenvLoadFunc)(const char*);
typedef const char* (*DotenvGetFunc)(const char*, const char*);
typedef int (*DotenvHasFunc)(const char*);
typedef void (*DotenvClearFunc)(void);

int main() {
    void* lib = dlopen("./libdotenv.so", RTLD_LAZY);
    if (!lib) {
        printf("Failed to load library\n");
        return 1;
    }
    
    // Get function pointers
    DotenvLoadFunc load = dlsym(lib, "DotenvLoad");
    DotenvGetFunc get = dlsym(lib, "DotenvGet");
    DotenvHasFunc has = dlsym(lib, "DotenvHas");
    DotenvClearFunc clear = dlsym(lib, "DotenvClear");
    
    // Use the library
    if (load(".env") == 0) {
        printf("Database: %s\n", get("DATABASE_URL", "not set"));
        
        if (has("API_KEY")) {
            printf("API Key is configured\n");
        }
    }
    
    // Cleanup
    clear();
    dlclose(lib);
    
    return 0;
}

C# (.NET) P/Invoke Example

Use the library from .NET applications via P/Invoke:

using System;
using System.Runtime.InteropServices;

class DotenvExample
{
    // Modern C API - Recommended
    [DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int DotenvLoad(string filename);

    [DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr DotenvGet(string key, string defaultValue);

    [DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int DotenvHas(string key);

    [DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void DotenvClear();

    [DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int DotenvIsLoaded();

    [DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr DotenvGetLastError();

    // Legacy function (for backwards compatibility)
    [DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void CallDotenvLoad(string filename);

    public static void Main()
    {
        // Load environment variables from .env file
        int result = DotenvLoad(".env");
        if (result != 0)
        {
            IntPtr errorPtr = DotenvGetLastError();
            string error = errorPtr != IntPtr.Zero 
                ? Marshal.PtrToStringAnsi(errorPtr) 
                : "Unknown error";
            Console.WriteLine("Error loading .env: {0}", error);
            return;
        }

        // Check if loaded successfully
        if (DotenvIsLoaded() == 1)
        {
            Console.WriteLine("Environment variables loaded successfully!");
        }

        // Get environment variable with default value
        IntPtr valuePtr = DotenvGet("CONNECTION_STRING", "default_connection");
        string connectionString = Marshal.PtrToStringAnsi(valuePtr);
        Console.WriteLine("Connection string: {0}", connectionString);

        // Check if a variable exists
        if (DotenvHas("API_KEY") == 1)
        {
            IntPtr apiKeyPtr = DotenvGet("API_KEY", "");
            string apiKey = Marshal.PtrToStringAnsi(apiKeyPtr);
            Console.WriteLine("API Key: {0}", apiKey);
        }

        // You can also use standard .NET methods after loading
        string dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
        Console.WriteLine("Database URL from Environment: {0}", dbUrl);

        // Clear loaded variables when done
        DotenvClear();
    }
}
Platform Note: On Linux, use libdotenv.so instead of dotenv.dll. On macOS, use libdotenv.dylib.

Troubleshooting

DLL/Shared Library Not Found

If you get an error that the library cannot be loaded:

Linking Errors

If you get linking errors:

Environment Variables Not Loading

Thread Safety Issues

Cross-Platform Build Issues

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

View on GitHub Report Issue

License

This project is licensed under the MIT License - see the LICENSE file for details.