# DLL Symbol Visibility in Nim - Hiding NimMain

Developing DLLs with Nim for Windows presents unique challenges, especially regarding symbol visibility. A common issue is the exposure of `NimMain`, an automatic inclusion in Nim-compiled DLLs that is essential for initializing the runtime but not intended for public use. Its consistent presence across DLLs creates a recognizable signature, which can be problematic for various use cases. Additionally, managing the visibility of other symbols, such as ensuring certain functions remain private, demands careful attention. This guide covers the use of a `.def` file in conjunction with Nim's pragmas to conceal `NimMain` and selectively expose or hide other functions.

## Challenge: Hiding `NimMain` and Specific Functions

`NimMain` serves as a clear indicator of a DLL's origins from Nim, which might not always be desirable. Furthermore, it is important to control which functions are exposed outside the DLL.

## Solution: Employing a `.def` File

To address these challenges, we use a `.def` file:

```plaintext
EXPORTS
      NimMain NONAME PRIVATE
```

This configuration ensures `NimMain` is kept private, effectively removing it from the DLL's export table and mitigating its use as a Nim signature.

A .def file is typically used to define all public symbols/functions. However, in this case, we will use it to hide `NimMain` and manually control the visibility of other functions in our library using `{.exportc, dynlib.}`.

## Nim Code Adjustments

Adjustments in the Nim code involve using `{.exportc, dynlib.}` pragmas to mark functions for export, similar to `__declspec(dllexport)` in C/C++. Additionally, ensure that functions meant to be private, like `subtract`, do not carry these pragmas:

```plaintext
import winim/lean

{.passl: "dll.def".}

proc NimMain() {.cdecl, importc.}

# Exporting the 'add' function
proc add*(a, b: int): int {.exportc, dynlib.} = a + b

# Keeping 'subtract' private
proc subtract(a, b: int): int = a - b

# Custom DllMain for Nim runtime initialization
proc DllMain(hinstDLL: HINSTANCE, fdwReason: DWORD, lpReserved: LPVOID): BOOL {.stdcall, exportc, dynlib.} =
  if fdwReason == DLL_PROCESS_ATTACH: NimMain()
  return TRUE
```

In this setup, `add` is explicitly marked for export, making it publicly available, while `subtract` remains internal to the DLL.

## Compilation Strategy

Compile the DLL using the following command to include the `.def` file, ensuring that `NimMain` and unwanted symbols like `subtract` remain hidden:

```sh
nim c -d=release -d=strip --opt=size --mm=orc --threads=on --app:lib --nomain --cpu:amd64 --out=app.dll dll.nim
```

In this approach, the DLL interface is designed to be clean and focused, with `NimMain` concealed in order to obscure the DLL's origin in Nim. This selective exposure of functions ensures that only the relevant ones are visible to the user.

## Conclusion

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707253977355/349a146d-f5e2-4b65-a979-663b94631cb7.png align="center")

As demonstrated, the `NimMain` function is no longer visible in the DLL, even though it is technically still exported. The function has been marked as invisible to maintain the desired level of obscurity.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707254122397/3cfceb21-22b5-43ea-96e4-41d2f54527de.png align="center")

When you try to examine the invisible symbols within the DLL, you will find that the function name is no longer `NimMain`; it is randomly `Ordinal2`. This subtle change further reinforces the concealment of the DLL's Nim origin, making it more difficult for users to identify its true source.

Using a `.def` file and Nim's `{.exportc, dynlib.}` pragmas provides precise control over DLL symbol visibility in Nim, similar to `__declspec(dllexport)` in C/C++. This method ensures `NimMain` stays hidden, addressing the issue of its presence as a signature in Nim-compiled DLLs. This maintains a minimal and secure interface for your DLLs.
