Wednesday, February 26, 2014

Simple DLL Project

Using Visual Studio 2008, aka Visual Studio 9.0
(also works with Visual C++ 2008 Express Edition [which is free])

The project that is the DLL:

vs9
 File > New > Project
  Visual C++ > Win32 > Win32 Console Application
   Name: SimpleDll
   Location: C:\Code\C++ (see note below)
   Create directory for solution: [unchecked]
  > Ok
  > Next
   DLL
   Empty Project
  > Finish

Note: the above creates a new folder (C:\Code\C++\SimpleDll) based on your project name, and creates the project files (SimpleDLL.*) inside that folder. Thus location is the location of your project folders, not the location of this project. If you had checked the "Create directory for solution" box it would have created two folders, one inside the other, one for the .sln and one for the .vcproj file.

[right-click] Header Files > Add > New Item > Header File
 Name: SimpleDll.h
 > Add

[right-click] Source Files > Add > New Item > C++ File
 Name: SimpleDll.cpp
 > Add

SimpleDll.h

#ifndef SIMPLE_DLL_H
#define SIMPLE_DLL_H

namespace SimpleDll
{
  class Functionality1
  {
  public:
    static __declspec(dllexport) int Add(int a, int b);
  };
}

#endif SIMPLE_DLL_H

SimpleDll.cpp

#include "SimpleDll.h"

namespace SimpleDll
{
  int Functionality1::Add(int a, int b)
  { return a + b; }
}

Build it:

Build > Build Solution

result:
C:\Code\C++\SimpleDll\SimpleDll.h
C:\Code\C++\SimpleDll\Debug\SimpleDll.dll
C:\Code\C++\SimpleDll\Debug\SimpleDll.lib

In the top-bar, switch the Solutions Configuration dropdown from Debug to Release and build again.

Build > Build Solution

result:
C:\Code\C++\SimpleDll\SimpleDll.h
C:\Code\C++\SimpleDll\Release\SimpleDll.dll
C:\Code\C++\SimpleDll\Release\SimpleDll.lib

The project that is the binary that uses the DLL:

vs9
 File > New > Project
  Visual C++ > Win32 > Win32 Console Application
   Name: SimpleExe
   Location: C:\Code\C++ (see note above)
   Create directory for solution: [unchecked]
  > Ok
  > Next
   Console Application
   Empty Project
  > Finish

[right-click] Source Files > Add > New Item > C++ File
 Name: main.cpp
 > Add

[right-click] SimpleExe (in the sidebar) > Properties

> Configuration Properties
 > Debugging > Environment
  PATH=%PATH%;..\SimpleDll\Debug  (which gets C:\Code\C++\SimpleDll\Debug\SimpleDll.dll)
 > C/C++ > General > Additional Include Directories
  ..\SimpleDll   (which gets C:\Code\C++\SimpleDll\SimpleDll.h)
 > Linker
  > General > Additional Library Directories
   ..\SimpleDll\Debug   (which supports C:\Code\C++\SimpleDll\Debug\SimpleDll.lib)
  > Input > Additional Dependencies
   SimpleDll.lib  (which gets C:\Code\C++\SimpleDll\Debug\SimpleDll.lib)
> Apply

In the configuration dropdown at top-left switch from Active(Debug) to Release.

> Configuration Properties
 > Debugging > Environment
  PATH=%PATH%;..\SimpleDll\Release   (which gets C:\Code\C++\SimpleDll\Release\SimpleDll.dll)
 > C/C++ > General > Additional Include Directories
  ..\SimpleDll   (which gets C:\Code\C++\SimpleDll\SimpleDll.h)
 > Linker
  > General > Additional Library Directories
   ..\SimpleDll\Release   (which supports C:\Code\C++\SimpleDll\Release\SimpleDll.lib)
  > Input > Additional Dependencies
   SimpleDll.lib  (which gets C:\Code\C++\SimpleDll\Release\SimpleDll.lib)
> Ok

main.cpp

#include <iostream>
#include <SimpleDll.h>
int main()
{
  int a = 2;
  int b = 3;
  int c = SimpleDll::Functionality1::Add(a,b);
  printf("%i + %i = %i\n",a,b,c);

  char whatever[512];
  printf("Hit Enter To Exit");
  gets(whatever);
  return 0;
}

Build it:

Build > Build Solution

result:
C:\Code\C++\SimpleExe\Debug\SimpleExe.exe

Run it:

Debug > Start Debugging

result:
 2 + 3 = 5
 Hit Enter To Exit

In the top-bar, switch the Solutions Configuration dropdown from Debug to Release and build again.

Build > Build Solution

result:
C:\Code\C++\SimpleExe\Release\SimpleExe.exe

Run it:

Debug > Start Debugging

result:
 2 + 3 = 5
 Hit Enter To Exit

Mangled Symbols

The above approach works fine if the DLL will be consumed by another c++ application compiled by Visual Studio. However if you want to load a DLL in something like Delphi and want to specify the symbols by name in code instead of using the compile time binding shown above then you probably want unmangled exports.

You can see the mangled exports from the above using the dumpbin utility that comes with visual studio.

dumpbin /exports SimpleDll.dll

To run the above dumpbin, I had to put the following on my path:

C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\
C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\

stackoverflow explains how to suppress the mangling. Note that I've opted to keep the object oriented structure, however I've exposed extern "C" exported global functions with the namespacing achieved by function name prefix.

The project that is the DLL:

vs9
 File > New > Project
  Visual C++ > Win32 > Win32 Console Application
   Name: UnmangledDll
   Location: C:\Code\C++ (see note above)
   Create directory for solution: [unchecked]
  > Ok
  > Next
   DLL
   Empty Project
  > Finish
[right-click] Header Files > Add > New Item > Header File
 Name: UnmangledDll.h
 > Add

[right-click] Source Files > Add > New Item > C++ File
 Name: UnmangledDll.cpp
 > Add

UnmangledDll.h

#ifndef UNMANGLED_DLL_H
#define UNMANGLED_DLL_H

namespace UnmangledDll
{
  class Functionality1
  {
  public:
    static __declspec(dllexport) int Add(int a, int b);
  };
}

extern "C"
{
  __declspec(dllexport) int UnmangledDll_Add(int a, int b)
  { return UnmangledDll::Functionality1::Add(a,b); }
};

#endif UNMANGLED_DLL_H

UnmangledDll.cpp

#include "UnmangledDll.h"

namespace UnmangledDll
{
  int Functionality1::Add(int a, int b)
  { return a + b; }
}

Build it:

Build > Build Solution

result:
C:\Code\C++\UnmangledDll\Debug\UnmangledDll.dll

In the top-bar, switch the Solutions Configuration dropdown from Debug to Release and build again.

Build > Build Solution

result:
C:\Code\C++\UnmangledDll\Release\UnmangledDll.dll

The project that is the binary that uses the DLL:

vs9
 File > New > Project
  Visual C++ > Win32 > Win32 Console Application
   Name: UnboundExe
   Location: C:\Code\C++ (see note above)
   Create directory for solution: [unchecked]
  > Ok
  > Next
   Console Application
   Empty Project
  > Finish

[right-click] Source Files > Add > New Item > C++ File
 Name: main.cpp
 > Add

[right-click] UnboundExe (in the sidebar) > Properties

> Configuration Properties
 > Debugging > Environment
  PATH=%PATH%;..\UnmangledDll\Debug  (which gets C:\Code\C++\UnmangledDll\Debug\UnmangledDll.dll)
> Apply

In the configuration dropdown at top-left switch from Active(Debug) to Release.

> Configuration Properties
 > Debugging > Environment
  PATH=%PATH%;..\UnmangledDll\Release   (which gets C:\Code\C++\UnmangledDll\Release\UnmangledDll.dll)
> Ok

main.cpp

#include <windows.h>
#include <stdio.h> 
typedef int (__cdecl *DLL_ADD)(int,int); 
void work()
{
  HINSTANCE handle = LoadLibrary(TEXT("UnmangledDll.dll"));
  if (handle == NULL)
  {
    printf("Failed to load DLL.\n");
    return;
  }

  DLL_ADD dll_add = (DLL_ADD)GetProcAddress(handle,"UnmangledDll_Add");  
  if (dll_add == NULL)
  {
    printf("Failed to bind function.\n");
    return;
  }

  int val = (dll_add)(2,3);
  printf("2 + 3 = %i\n",val);
}
int main()
{
  work();
  char whatever[512];
  printf("Hit Enter To Exit");
  gets(whatever);
  return 0;
}

Build it:

Build > Build Solution

result:
C:\Code\C++\UnboundExe\Debug\UnboundExe.exe

Run it:

Debug > Start Debugging

result:
 2 + 3 = 5
 Hit Enter To Exit

In the top-bar, switch the Solutions Configuration dropdown from Debug to Release and build again.

Build > Build Solution

result:
C:\Code\C++\UnboundExe\Release\UnboundExe.exe

Run it:

Debug > Start Debugging

result:
 2 + 3 = 5
 Hit Enter To Exit

The above GetProcAddress technique is described on msdn.

Using Delphi XE5

The project that is the binary that uses the DLL:

File > New > VCL Forms Application

File > Save Project As >
 C:\Code\Delphi
 > [right-click] > New > Folder > UnboundExe
 C:\Code\Delphi\UnboundExe
 Main.pas
 > Save
 UnboundExe.dproj
 > Save

From the Tool Palette (at bottom right) expand the Standard pane and drag a TButton onto the main form. In the properties tab (at bottom left), change its caption and name to LoadDLL. In the events tab (at bottom left), double-click the space to the right of the OnClick event to create a LoadDLLClick function. At this stage you should have the following code.

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs;

type
  TForm1 = class(TForm)
    LoadDLL: TButton;
    procedure LoadDLLClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.LoadDLLClick(Sender: TObject);
begin

end;

end.

Update it as follows. Be sure to not save prior to filling out the auto-created LoadDLLClick or the IDE will delete the stub during the save.

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs;

type
  TForm1 = class(TForm)
    LoadDLL: TButton;
    procedure LoadDLLClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  DLL_ADD: function (a: Integer; b: Integer):Integer; stdcall = nil;

implementation

{$R *.dfm}

var
  DLLHandle: THandle = 0;

procedure TForm1.LoadDLLClick(Sender: TObject);
var
  sum: Integer;
  msg: string;
begin
  DLLHandle:= LoadLibrary('UnmangledDll.dll');
  if (DLLHandle= 0) then
  begin
    ShowMessage('Failed to load DLL.');
    Exit;
  end;

  @DLL_ADD := GetProcAddress(DLLHandle,'UnmangledDll_Add');
  if (@DLL_ADD= nil) then
  begin
    ShowMessage('Failed to bind function.');
    Exit;
  end;

  sum := DLL_ADD(2,3);
  ShowMessage('2 + 3 = '+intToStr(sum));
end;

end.

Before running the project, add the location of the DLL to your path.

XE5 > Project > Options > Debugger > Environment Block > User Overrides > New
name: PATH
value: %PATH%;C:\Code\C++\UnmangledDll\Release

Exporting from a DLL Using DEF Files

This technique is described on msdn. It is an alternative to the use of __declspec(dllexport). Basically you manually create a .DEF text file that lists the function names and optionally ordinals to be exported.

If you don't use extern "C" then you must use dumpbin to discover the decorated names; however, decorated names are compiler specific, so they can only be consumed by binaries that are built using the same version of visual c++.

The project that is the DLL:

vs9
 File > New > Project
  Visual C++ > Win32 > Win32 Console Application
   Name: DefDll
   Location: C:\Code\C++ (see note above)
   Create directory for solution: [unchecked]
  > Ok
  > Next
   DLL
   Empty Project
  > Finish
[right-click] Header Files > Add > New Item > Header File
 Name: DefDll.h
 > Add

[right-click] Source Files > Add > New Item > C++ File
 Name: DefDll.cpp
 > Add

DefDll.h

#ifndef DEF_DLL_H
#define DEF_DLL_H

namespace DefDll
{
  class Functionality1
  {
  public:
    static int Add(int a, int b);
  };
}

extern "C"
{
  int DefDll_Add(int a, int b)
  { return DefDll::Functionality1::Add(a,b); }
};

#endif DEF_DLL_H

DefDll.cpp

#include "DefDll.h"

namespace DefDll
{
  int Functionality1::Add(int a, int b)
  { return a + b; }
}

In the top-bar, switch the Solutions Configuration dropdown from Debug to Release and build it:

Build > Build Solution

result:
C:\Code\C++\DefDll\Release\DefDll.dll

So far, our DLL exports nothing. It seems that visual studio express doesn't support .def in the Add Item interface. See details on the msdn fourm.

Outside of the IDE create the following text file.

C:\Code\C++\DefDll\DefDll.def

LIBRARY   DEFDLL
EXPORTS
   DefDll_Add

Then add it to your visual studio project and recompile.

vs9
 [right-click] DefDll > Add > Existing Item > DefDll.def

 [right-click] DefDll > Properties

  Configuration: Active(Release)
  > Configuration Properties > Linker > Input > Module Definition File: DefDll.def 
  > Apply

  Configuration: Debug
  > Configuration Properties > Linker > Input > Module Definition File: DefDll.def 
  > Ok

Build > Build Solution

Confirm the exports with dumpbin:

cd /d C:\Code\C++\DefDll\Release
dumpbin /exports DefDll.dll

result:
   ordinal hint RVA      name
         1    0 00001000 DefDll_Add = _DefDll_Add

The msdn article talks about using dumpbin or /MAP to obtain the decorated names, but without __declspec(dllexport), I was unable to get these tools to display the mangled name of the DefDll::Functionality1::Add symbol, so it seems the only option for export via .def file is to use extern "C".

To use this DLL from Delphi, the only modifications to the above Delphi project are as follows:

DLLHandle:= LoadLibrary('DefDll.dll');

@DLL_ADD := GetProcAddress(DLLHandle,'DefDll_Add');

XE5 > Project > Options > Debugger > Environment Block > User Overrides > New
name: PATH
value: %PATH%;C:\Code\C++\DefDll\Release

Link by Ordinal

If you really don't want to use extern "C" you could instead link by ordinal from the original SimpleDll project.

cd /d C:\Code\C++\SimpleDll\Release
dumpbin /exports SimpleDll.dll

result:
   ordinal hint RVA      name
         1    0 00001000 ?Add@Functionality1@SimpleDll@@SAHHH@Z = ?Add@Functionality1@SimpleDll@@SAHHH@Z (public: static int __cdecl SimpleDll::Functionality1::Add(int,int))

To use this DLL from Delphi, the only modifications to the above Delphi project are as follows:

DLLHandle:= LoadLibrary('SimpleDll.dll');

@DLL_ADD := GetProcAddress(DLLHandle,PWideChar(1));

XE5 > Project > Options > Debugger > Environment Block > User Overrides > New
name: PATH
value: %PATH%;C:\Code\C++\SimpleDll\Release

Exporting Classes

All of the above are just different mechanisms to expose static entry points to the DLL. They don't for example let you expose an entire object. codeproject.com has a good article on exporting classes from a DLL.

When Windows DLLs were introduced, the only viable option to speak to broad development audience was C language. So, Windows DLLs exposed their functionality as C functions and data.

In the C++ there is no recognized application binary interface (ABI). In practice, it means that binary code that is generated by a C++ compiler is not compatible with other C++ compilers. Moreover, the binary code of the same C++ compiler may be incompatible with other versions of this compiler.

The problem is solved for C-style exports by exposing a factory/handle interface from the DLL. Fore example, you have a function that creates an instance of an object and returns its handle. You have another function that takes a handle and arguments and calls the member function of the object found by the handle with the provided arguments. This is a bit cumbersome, but it works.

You can of course __declspec(dllexport) an entire class, but because all the symbols are mangled, it can only be imported by binaries built with the same compiler version.

Non-Object-Oriented Interface

The project that is the DLL:

vs9
 File > New > Project
  Visual C++ > Win32 > Win32 Console Application
   Name: NonObjectDll
   Location: C:\Code\C++ (see note above)
   Create directory for solution: [unchecked]
  > Ok
  > Next
   DLL
   Empty Project
  > Finish
[right-click] Header Files > Add > New Item > Header File
 Name: NonObjectDll.h
 > Add

[right-click] Source Files > Add > New Item > C++ File
 Name: NonObjectDll.cpp
 > Add

NonObjectDll.h

#ifndef NONOBJECT_DLL_H
#define NONOBJECT_DLL_H

class MyObject
{
public:
  int Add(int a, int b);
};

extern "C"
{
  __declspec(dllexport) int NonObjectDll_Add(int a, int b);
};

#endif NONOBJECT_DLL_H

NonObjectDll.cpp

#include "NonObjectDll.h"

int MyObject::Add(int a, int b)
{ return a + b; }

MyObject instance;

int NonObjectDll_Add(int a, int b)
{ return instance.Add(a,b); }

Build it:

In the top-bar, switch the Solutions Configuration dropdown from Debug to Release.

Build > Build Solution

result:
C:\Code\C++\NonObjectDll\Release\NonObjectDll.dll

The project that is the binary that uses the DLL. This project can use a different compiler or be written in a different language than the DLL. It doesn't link to the DLL at compile time. All exported symbols are unmangled.

vs9
 File > New > Project
  Visual C++ > Win32 > Win32 Console Application
   Name: NonObjectExe
   Location: C:\Code\C++ (see note above)
   Create directory for solution: [unchecked]
  > Ok
  > Next
   Console Application
   Empty Project
  > Finish

[right-click] Source Files > Add > New Item > C++ File
 Name: main.cpp
 > Add

In the top-bar, switch the Solutions Configuration dropdown from Debug to Release.

[right-click] NonObjectExe > Properties
 Configuration: Active(Release)
 > Configuration Properties > Debugging > Environment
  PATH=%PATH%;..\NonObjectDll\Release   (which gets C:\Code\C++\NonObjectDll\Release\NonObjectDll.dll)
> Ok

main.cpp

#include <windows.h>
#include <stdio.h> 
typedef int (__cdecl *DLL_ADD)(int,int); 
void work()
{
  HINSTANCE handle = LoadLibrary(TEXT("NonObjectDll.dll"));
  if (handle == NULL)
  {
    printf("Failed to load DLL.\n");
    return;
  }

  DLL_ADD dll_add = (DLL_ADD)GetProcAddress(handle,"NonObjectDll_Add");  
  if (dll_add == NULL)
  {
    printf("Failed to bind function.\n");
    return;
  }

  int val = (dll_add)(2,3);
  printf("2 + 3 = %i\n",val);
}
int main()
{
  work();
  char whatever[512];
  printf("Hit Enter To Exit");
  gets(whatever);
  return 0;
}

Build it:

Build > Build Solution

result:
C:\Code\C++\NonObjectExe\Release\NonObjectExe.exe

Run it:

Debug > Start Debugging

result:
 2 + 3 = 5
 Hit Enter To Exit

Example Factory/Handle Interface

The project that is the DLL:

vs9
 File > New > Project
  Visual C++ > Win32 > Win32 Console Application
   Name: FactoryHandleDll
   Location: C:\Code\C++ (see note above)
   Create directory for solution: [unchecked]
  > Ok
  > Next
   DLL
   Empty Project
  > Finish
[right-click] Header Files > Add > New Item > Header File
 Name: FactoryHandleDll.h
 > Add

[right-click] Source Files > Add > New Item > C++ File
 Name: FactoryHandleDll.cpp
 > Add

FactoryHandleDll.h

#ifndef FACTORYHANDLE_DLL_H
#define FACTORYHANDLE_DLL_H

class MyObject
{
public:
  int Add(int a, int b);
};

extern "C"
{
  __declspec(dllexport) void* FactoryHandleDll_Instance(); // TODO delete
  __declspec(dllexport) int FactoryHandleDll_Add(void* handle, int a, int b);
};

#endif FACTORYHANDLE_DLL_H

FactoryHandleDll.cpp

#include "FactoryHandleDll.h"

int MyObject::Add(int a, int b)
{ return a + b; }

void* FactoryHandleDll_Instance()
{ return new MyObject(); }

int FactoryHandleDll_Add(void* handle, int a, int b)
{ return ((MyObject*)handle)->Add(a,b); }

Build it:

In the top-bar, switch the Solutions Configuration dropdown from Debug to Release.

Build > Build Solution

result:
C:\Code\C++\FactoryHandleDll\Release\FactoryHandleDll.dll

The project that is the binary that uses the DLL. This project can use a different compiler or be written in a different language than the DLL. It doesn't link to the DLL at compile time. All exported symbols are unmangled.

vs9
 File > New > Project
  Visual C++ > Win32 > Win32 Console Application
   Name: FactoryHandleExe
   Location: C:\Code\C++ (see note above)
   Create directory for solution: [unchecked]
  > Ok
  > Next
   Console Application
   Empty Project
  > Finish

[right-click] Source Files > Add > New Item > C++ File
 Name: main.cpp
 > Add

In the top-bar, switch the Solutions Configuration dropdown from Debug to Release.

[right-click] FactoryHandleExe> Properties
 Configuration: Active(Release)
 > Configuration Properties > Debugging > Environment
  PATH=%PATH%;..\FactoryHandleDll\Release   (which gets C:\Code\C++\FactoryHandleDll\Release\FactoryHandleDll.dll)
> Ok

main.cpp

#include <windows.h>
#include <stdio.h> 
typedef void* (__cdecl *DLL_INSTANCE)(); 
typedef int (__cdecl *DLL_ADD)(void*,int,int); 
void work()
{
  HINSTANCE handle = LoadLibrary(TEXT("FactoryHandleDll.dll"));
  if (handle == NULL)
  {
    printf("Failed to load DLL.\n");
    return;
  }

  DLL_INSTANCE dll_instance = (DLL_INSTANCE )GetProcAddress(handle,"FactoryHandleDll_Instance");  
  if (dll_instance == NULL)
  {
    printf("Failed to bind function.\n");
    return;
  }

  DLL_ADD dll_add = (DLL_ADD)GetProcAddress(handle,"FactoryHandleDll_Add");  
  if (dll_add == NULL)
  {
    printf("Failed to bind function.\n");
    return;
  }

  void* instance = (dll_instance)();

  int val = (dll_add)(instance,2,3);
  printf("2 + 3 = %i\n",val);
}
int main()
{
  work();
  char whatever[512];
  printf("Hit Enter To Exit");
  gets(whatever);
  return 0;
}

Build it:

Build > Build Solution

result:
C:\Code\C++\FactoryHandleExe\Release\FactoryHandleExe.exe

Run it:

Debug > Start Debugging

result:
 2 + 3 = 5
 Hit Enter To Exit
{ "loggedin": false, "owner": false, "avatar": "", "render": "nothing", "trackingID": "UA-36983794-1", "description": "", "page": { "blogIds": [ 436 ] }, "domain": "holtstrom.com", "base": "\/michael", "url": "https:\/\/holtstrom.com\/michael\/", "frameworkFiles": "https:\/\/holtstrom.com\/michael\/_framework\/_files.4\/", "commonFiles": "https:\/\/holtstrom.com\/michael\/_common\/_files.3\/", "mediaFiles": "https:\/\/holtstrom.com\/michael\/media\/_files.3\/", "tmdbUrl": "http:\/\/www.themoviedb.org\/", "tmdbPoster": "http:\/\/image.tmdb.org\/t\/p\/w342" }