Create an External Function with Visual Studio

Haversine code in various computer languages is publicly available to compute the distance between locations. In this article, we use it as an illustration of how to create an external function using Microsoft Visual Studio.

Creating a Visual Studio Project

A Visual Studio Project facilitates incremental building of software components in various computer languages. Here we just use it to create a .dll containing external functions for Haversine.

../../_images/01NewProjectDLL.PNG

After opening Visual Studio, in order:

  1. New Project - To create a new project

  2. Visual Studio C++ - Specializing to project to C++

  3. A Win32 - Further specializing the project to Win32 (which covers both 32 and 64 bit executables)

  4. A generic Win32 project (not a console)

  5. Specify destination folders

  6. Make sure we can change further settings

    ../../_images/02NewProjectDLL.PNG
  7. We want a DLL

    ../../_images/03NewProjectDLL.PNG
  8. The default configuration is not correct

    ../../_images/04NewProjectDLL.PNG
  9. We want a new configuration with 64-bit

    ../../_images/06NewProjectDLL.PNG
  10. You may want to turn off precompiled headers:

    1. Select your project, use the “Project -> Properties” menu and
    2. go to the “Configuration Properties -> C/C++ -> Precompiled Headers” section,
    3. then change the “Precompiled Header” setting to “Not Using Precompiled Headers” option.

Coding the function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// HaversineDLL.cpp : Implements the exported Haversine function for the DLL application.

#ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#define _USE_MATH_DEFINES
#include <math.h>

#ifdef __cplusplus
#define DLL_EXPORT_PROTO(type) extern "C" __declspec(dllexport) type WINAPI
#else
#define DLL_EXPORT_PROTO(type) extern __declspec(dllexport) type WINAPI
#endif

DLL_EXPORT_PROTO(double) Haversine(double lat1, double lon1, double lat2, double lon2);

static double toRadians(double angle)
{
    return M_PI * angle / 180.0;
}
DLL_EXPORT_PROTO(double) Haversine(double lat1, double lon1, double lat2, double lon2)
{
    double R = 6372.8; // In kilometers
    double dLat = toRadians(lat2 - lat1);
    double dLon = toRadians(lon2 - lon1);
    lat1 = toRadians(lat1);
    lat2 = toRadians(lat2);

    double a = sin(dLat / 2) * sin(dLat / 2) + sin(dLon / 2) * sin(dLon / 2) * cos(lat1) * cos(lat2);
    double c = 2 * asin(sqrt(a));
    return R * 2 * asin(sqrt(a));
}
  • Line 18-21: Helper function, convert angle from degrees to radians.
  • Line 22: Function declaration, use C calling convention
  • Line 24 - 32: based on Haversine code

Building using Visual Studio

  1. Select release
  2. Select x64
  3. Press Build

Verifying the build

We need to verify that the .dll’s built are valid 32 bit and 64 bit .dll’s. To do this, we use a nifty free of charge utility named depends.exe. You may download the x86 and x64 from this website. Installing is just unzipping. Then start the executable and browse the .dll

  1. depends22_x86\depends.exe for the 32 bit dll: <HaversineDLL>\release\HaversineDLL.dll.

    ../../_images/32BitsDependsCheck.PNG
    • Missing functions in MSVCR120.dll may be reported, but those are covered when starting AIMMS.
    • Important are the functions exported, as highlighted in the red rectangle; at least Haversine needs to be there. 32 bits Win32 will prefix using an _ and postfix using the @ sign and the number of bytes passed over the stack. The Haversine functions passes four doubles, so the postfix @32 is to be expected.
  2. depends22_x64\depends.exe for the 64 bit dll: <HaversineDLL>\x64\release\HaversineDLL.dll.

    ../../_images/64BitsDependsCheck.PNG
    • Missing functions in MSVCR120.dll may be reported, but those are covered when starting AIMMS.
    • Important are the functions exported, as highlighted in the red rectangle; at least Haversine needs to be there. 64 bits Win32 will not use prefix, nor postfix.

Testing the DLL’s

Create a separate AIMMS project just for testing.

  1. Copy the dll’s into the AIMMS project directory.

    • Copy the <HaversineDLL>\release\HaversineDLL.dll to the AIMMS project subfolder external\Windows\x86
    • Copy the <HaversineDLL>\x64\release\HaversineDLL.dll to the AIMMS project subfolder external\Windows\x64
  2. Declare the external function in AIMMS.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ExternalFunction fnc_Haversine {
       Arguments: (latFrom,lonFrom,latTo,lonTo);
       DllName: "external\\Windows\\x64\\HaversineDLL.dll";
       ReturnType: double;
       BodyCall: Haversine(scalar: latFrom, scalar: lonFrom, scalar: latTo, scalar: lonTo);
       Parameter latFrom {
             Property: Input;
       }
       Parameter lonFrom {
             Property: Input;
       }
       Parameter latTo {
             Property: Input;
       }
       Parameter lonTo {
             Property: Input;
       }
    }
    
  3. Test the external function in AIMMS.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Procedure MainExecution {
       Body: {
             p_DistNashvilleLosAngeles := fnc_Haversine(36.12, -86.67, 33.94, -118.40);
    
             p_dist1(i_slocFrom, i_slocTo) := fnc_Haversine( p_Latitude(i_slocFrom), p_Longitude(i_slocFrom), p_Latitude(i_slocTo), p_Longitude(i_slocTo) );
    
             display p_DistNashvilleLosAngeles ;
       }
    }
    

The resulted Listing file is as below with the expected value.

p_DistNashvilleLosAngeles := 2887.260 ;

Good performance; my desktop requires less than 0.3 seconds to fill a 274 X 274 distance matrix.

Downloads

Last Updated: November, 2019