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.
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.
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.
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.
#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);
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
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" )
Coming soon
See http://ashishcplusplus.blogspot.de/2012/05/undefined-reference-to.html
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);
testdll.cpp
#include <cstdlib> #include <iostream> #define TESTDLL_EXPORTS 1 #include "testdll.h" TESTDLL_API(int) Subtract2(int a, int b) { return a-b; }
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; }
Coming soon
Coming soon
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(); } } }
Coming soon
Coming soon
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
Coming soon
Attention: Not tested!
Coming soon
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
Coming soon
Note: As far as I know, Visual Basic can only handle the StdCall calling convention. But I am not sure about this.
Coming soon
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
Coming soon
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
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..."
To my knowledge, exporting code into a DLL is not possible in Java.
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)); } }