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.
After opening Visual Studio, in order:
New Project - To create a new project
Visual Studio C++ - Specializing to project to C++
A Win32 - Further specializing the project to Win32 (which covers both 32 and 64 bit executables)
A generic Win32 project (not a console)
Specify destination folders
Make sure we can change further settings
We want a DLL
The default configuration is not correct
We want a new configuration with 64-bit
You may want to turn off precompiled headers:
Select your project, use the “Project -> Properties” menu and
go to the “Configuration Properties -> C/C++ -> Precompiled Headers” section,
then change the “Precompiled Header” setting to “Not Using Precompiled Headers” option.
Coding the function
1// HaversineDLL.cpp : Implements the exported Haversine function for the DLL application.
2
3#ifdef _MSC_VER
4#define WIN32_LEAN_AND_MEAN
5#include <windows.h>
6#endif
7#define _USE_MATH_DEFINES
8#include <math.h>
9
10#ifdef __cplusplus
11#define DLL_EXPORT_PROTO(type) extern "C" __declspec(dllexport) type WINAPI
12#else
13#define DLL_EXPORT_PROTO(type) extern __declspec(dllexport) type WINAPI
14#endif
15
16DLL_EXPORT_PROTO(double) Haversine(double lat1, double lon1, double lat2, double lon2);
17
18static double toRadians(double angle)
19{
20 return M_PI * angle / 180.0;
21}
22DLL_EXPORT_PROTO(double) Haversine(double lat1, double lon1, double lat2, double lon2)
23{
24 double R = 6372.8; // In kilometers
25 double dLat = toRadians(lat2 - lat1);
26 double dLon = toRadians(lon2 - lon1);
27 lat1 = toRadians(lat1);
28 lat2 = toRadians(lat2);
29
30 double a = sin(dLat / 2) * sin(dLat / 2) + sin(dLon / 2) * sin(dLon / 2) * cos(lat1) * cos(lat2);
31 double c = 2 * asin(sqrt(a));
32 return R * 2 * asin(sqrt(a));
33}
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
Select release
Select x64
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
depends22_x86\depends.exe
for the 32 bit dll:<HaversineDLL>\release\HaversineDLL.dll
.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.
depends22_x64\depends.exe
for the 64 bit dll:<HaversineDLL>\x64\release\HaversineDLL.dll
.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.
Copy the dll’s into the AIMMS project directory.
Copy the
<HaversineDLL>\release\HaversineDLL.dll
to the AIMMS project subfolderexternal\Windows\x86
Copy the
<HaversineDLL>\x64\release\HaversineDLL.dll
to the AIMMS project subfolderexternal\Windows\x64
Declare the external function in AIMMS.
1ExternalFunction fnc_Haversine { 2 Arguments: (latFrom,lonFrom,latTo,lonTo); 3 DllName: "external\\Windows\\x64\\HaversineDLL.dll"; 4 ReturnType: double; 5 BodyCall: Haversine(scalar: latFrom, scalar: lonFrom, scalar: latTo, scalar: lonTo); 6 Parameter latFrom { 7 Property: Input; 8 } 9 Parameter lonFrom { 10 Property: Input; 11 } 12 Parameter latTo { 13 Property: Input; 14 } 15 Parameter lonTo { 16 Property: Input; 17 } 18}
Test the external function in AIMMS.
1Procedure MainExecution { 2 Body: { 3 p_DistNashvilleLosAngeles := fnc_Haversine(36.12, -86.67, 33.94, -118.40); 4 5 p_dist1(i_slocFrom, i_slocTo) := fnc_Haversine( p_Latitude(i_slocFrom), p_Longitude(i_slocFrom), p_Latitude(i_slocTo), p_Longitude(i_slocTo) ); 6 7 display p_DistNashvilleLosAngeles ; 8 } 9}
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.