Export and Import of functions without name mangling/decoration

DLL - Export and Import of functions without name mangling/decoration

by Daniel Marschall, 23 September 2015

When you create an API, name mangling/decoration becomes a problem. It is a good solution to use the plain name of the function and define a uniform calling convention like __stdcall. Only this way your DLL can be used by almost all development environments. The Windows API is using exactly this combination, __stdcall and undecorated names. It is very easy to export and import such functions in Delphi, but rather complex in C++, so I have written this small tutorial.

This tutorial will show you how you can export and import undecorated stdcall functions, exactly the way the WinAPI provides them.

You can download source codes here

If there is anything I can do better, please tell me! I am always open for improvement suggestions and new ideas.


Table of Contents

  1. Embarcadero Delphi
  2. Microsoft Visual C++ 2010
  3. Bloodshed/Orwell Dev-C++
  4. Microsoft Visual C# 2010 (.NET)
  5. Microsoft Visual F# 2010 (.NET)
  6. Microsoft Visual Basic 6
  7. Microsoft Visual Basic 2010 (.NET)
  8. Sun Java

Embarcadero Delphi

Back to Table of Contents

Export

testdll.dpr (Works with Delphi 2.0 - XE5)

library testdll;

uses
  SysUtils,
  Classes;

{$R *.res}

function Subtract(a, b: integer): integer; stdcall;
begin
  result := a - b;
end;

exports
  Subtract;

end.

Import (Early binding)

testdll.pas

unit testdll;

interface

function Subtract(a, b: integer): integer; stdcall;

implementation

const
  {$IFDEF Linux}
  DLL_TESTDLL = 'testdll.so';
  {$ELSE}
  DLL_TESTDLL = 'testdll.dll';
  {$ENDIF}

function Subtract(a, b: integer): integer; stdcall; external DLL_TESTDLL;

end.

dll_import_test.dpr

program dll_import_test;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils,
  testdll in 'testdll.pas';

begin
  try
    WriteLn(Format('%d - %d = %d', [3, 5, Subtract(3, 5)]));
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Import (Late/Run-Time binding)

TestDLL.pas

unit testdll;

interface

function Subtract(a, b: integer): integer;

implementation

uses
  Windows;

type
  TDllNotFound = class(Exception);
  TDllProcNotFound = class(Exception);

resourcestring
  SDllNotFound = 'The module "%s" could not be loaded.';
  SDllProcNotFound = 'Procedure "%s" not found in module "%s".';

function Subtract(a, b: integer): integer; platform;
type
  TSubtract = function(a, b: integer): integer; stdcall;
var
  TestDLL: HModule;
  Subtract: TSubtract;
const
  DllFile = 'testdll.dll';
  FuncName = 'Subtract';
begin
  TestDLL := LoadLibrary(DllFile);
  if TestDLL = 0 then
  begin
    raise TDllNotFound.CreateFmt(SDllNotFound, [DllFile]);
  end;

  @Subtract := GetProcAddress(TestDLL, FuncName);
  if @Subtract = nil then
  begin
    raise TDllProcNotFound.CreateFmt(SDllProcNotFound, [FuncName, DllFile]);
  end;

  result := Subtract(a, b);
end;

dll_import_test.dpr

program dll_import_test;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils,
  testdll in 'testdll.pas';

begin
  try
    WriteLn(Format('%d - %d = %d', [3, 5, Subtract(3, 5)]));
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Another possibility on newer versions of Delphi:

function Subtract(a, b: integer): integer; stdcall; external DLL_TESTDLL delayed;

Attention: If the call fails, you get an EExternalException. Please see and rate my proposal on Embarcadero QC.


Microsoft Visual C++ 2010

Back to Table of Contents

Shared file (used for Import and Export)

testdll.h
#ifdef TESTDLL_EXPORTS
#define TESTDLL_API(ReturnType) extern "C" __declspec(dllexport) ReturnType __stdcall
#else
#define TESTDLL_API(ReturnType) extern "C" __declspec(dllimport) ReturnType __stdcall
#endif

TESTDLL_API(int) Subtract(int a, int b);

Export

testdll.def

LIBRARY TESTDLL
;DESCRIPTION "Test DLL"
EXPORTS
  Subtract

testdll.cpp

// This symbol tells "testdll.h" to use the dllexport instead of dllimport statement can also be defined in
// "Project Properties -> Configuration Propertie -> C/C++ -> Preprocessor -> Preprocessor Definitions"
// But we can also define it here.
#ifndef TESTDLL_EXPORTS
#define TESTDLL_EXPORTS 1
#endif

#include "stdafx.h"
#include "testdll.h"

// Now we define dummy functions. We could implement something here, but since we want to use
// Delphi DLLs, we simply leave them as dummies and replace the generated DLL with the Delphi-generated one.
// Important is the LIB file which will be created as side-product. With this LIB file we can staticly
// link our EXE to the DLL.
TESTDLL_API(int) Subtract(int a, int b) {
  return 0;
}

Things you need to do by hand (cannot be done via code)

You need to include the file testdll.def via "Project Properties -> Configuration Properties -> Linker -> Input -> Module Definition File".

Note, that this won't work (see http://msdn.microsoft.com/en-us/library/7f0aews7.aspx):

#pragma comment( linker, "/DEF:testdll.def" )

Final questions

  1. Does this work also for Win64, or is the name mangling different?

Import (Early binding)

Attention: If you have not developed your DLL in VC++, you MUST create a dummy DLL file (see previous section) so it creates a .LIB file as side product. Without this .LIB file you cannot simply tell VC++ that it should import the function "Subtract" from "testdll.dll". What a shame - in Delphi it is just so easy.

dll_import_test.cpp

#include "stdafx.h"
#include "..\testdll\testdll.h"

int _tmain(int argc, _TCHAR* argv[]) {
  printf("%d - %d = %d", 3, 5, Subtract(3, 5));
  return 0;
}

Things you need to do by hand (cannot be done via code)

You need to include the file testdll.lib via "Project Properties -> Configuration Properties -> Linker -> Input -> Additional Dependencies".

Note, that this won't work (see http://msdn.microsoft.com/en-us/library/7f0aews7.aspx):

#pragma comment( linker, "/IMPLIB:../Release/testdll.lib" )

Import (Late/Run-Time binding)

Coming soon


Bloodshed/Orwell Dev-C++

Back to Table of Contents

Some hint about the "Out of the box" problem

See http://ashishcplusplus.blogspot.de/2012/05/undefined-reference-to.html

  1. Go to Tools => Compiler Options => Directories => Libraries
  2. Delete all paths under Libraries.
  3. Recompile again.
  4. The __dyn_tls_init_callback linker error will no longer appear.

Shared files (used for Import and Export)

Note that the decoration using __stdcall did not work with older versions of Dev-C++. Please download the latest version of Dev-C++, developed by Orwell.

testdll.h

#ifdef TESTDLL_EXPORTS
#define TESTDLL_API(ReturnType) extern "C" __declspec(dllexport) __stdcall ReturnType
#else
#define TESTDLL_API(ReturnType) extern "C" __declspec(dllimport) __stdcall ReturnType
#endif

TESTDLL_API(int) Subtract(int a, int b);

Export into DLL

testdll.cpp

#include <cstdlib>
#include <iostream>

#define TESTDLL_EXPORTS 1
#include "testdll.h"

TESTDLL_API(int) Subtract2(int a, int b) {
    return a-b;
}

Import (Early binding)

Before you start

You need to run following command before

dlltool --input-def testdll.def --dllname testdll.dll --output-lib testdll.a --kill-at

Then go to "Project Options -> Parameters -> Linker" and add testdll.a in the edit box.

main.cpp

#include <cstdlib>
#include <iostream>

#include "testdll.h"

using namespace std;

int main(int argc, char *argv[]) {
    printf("%d - %d = %d\n", 3, 5, Subtract(3, 5));
    system("PAUSE");
    return EXIT_SUCCESS;
}

Import (Late/Run-Time binding)

Coming soon


Microsoft Visual C# 2010 (.NET)

Back to Table of Contents

Export

Coming soon

Import (Early binding)

dll_import_test.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    class TestDLL
    {
        [DllImport("testdll.dll")]
        public static extern int Subtract(int a, int b);
    }

    class Program
    {
        static void Main(string[] args)
        {
            System.Console.WriteLine("{0} - {1} = {2}", 3, 5, TestDLL.Subtract(3, 5));
            System.Console.ReadKey();
        }
    }
}

Import (Late/Run-Time binding)

Coming soon


Microsoft Visual F# 2010 (.NET)

Back to Table of Contents

Export

Coming soon

Import (Early binding)

dll_import_test.fs

#light

open System.Runtime.InteropServices

[<DllImport("testdll.dll")>]
extern int Subtract(int a, int b)

let x = Subtract(3, 5)

printfn "%d - %d = %d" 3 5 x

System.Console.ReadKey() |> ignore

Import (Late/Run-Time binding)

Coming soon


Microsoft Visual Basic 6

Back to Table of Contents

Attention: Not tested!

Export

Coming soon

Import (Early binding)

Main.bas

Public Declare Function Subtract Lib "testdll" (ByVal A As Integer, ByVal B As Integer) As Integer

Sub Main()
    MsgBox 3 & " - " & 5 & " = " & Subtract(3, 5)
End Sub

Import (Late/Run-Time binding)

Coming soon


Microsoft Visual Basic 2010 (.NET)

Back to Table of Contents

Note: As far as I know, Visual Basic can only handle the StdCall calling convention. But I am not sure about this.

Export

Coming soon

Import (Early binding)

Module1.vb

Imports System.Runtime.InteropServices

Module TestDLL

    <DllImport("testdll.dll")> _
    Public Function Subtract(ByVal A As Integer, ByVal B As Integer) As Integer
    End Function

End Module

Module Module1

    Sub Main()
        Console.WriteLine(3 & " - " & 5 & " = " & TestDLL.Subtract(3, 5))
        Console.ReadKey()
    End Sub

End Module

Import (Late/Run-Time binding)

Coming soon


Sun Java

Back to Table of Contents

Note: If you want Java to access a Windows DLL, you need JNA (Java Native Access), not JNI (Java Native Interface). Your Java code is then not platform-independent anymore.

Don't forget to put your DLL in C:\Users\...\workspace\DllTest\test.dll

Setup JNA

Download jna-4.0.0.jar from https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna/4.0.0/jna-4.0.0.jar .

Include it in Eclipse via "Build Path -> Configure Build Path -> Java Build Path -> Libraries -> Add External JARs..."

Export

To my knowledge, exporting code into a DLL is not possible in Java.

Import (Always Late/Run-Time binding)

TestDLL.java

import com.sun.jna.Library;

public interface TestDLL extends Library {
    public int Subtract(int a, int b);
}

CalcTest.java

import com.sun.jna.Native;

public class CalcTest {
    public static void main(String[] args) {
        TestDLL lib = (TestDLL) Native.loadLibrary("testdll", TestDLL.class);
        System.out.println(3 + " - " + 5 + " = " + lib.Subtract(3, 5));
    }
}