How to compile a DLL ?

Hey guys

I'm working with simulations in Ansys (AQWA) and I'm having trouble compiling a dll that will export functions for the simulation.

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149

//user_force64.cpp

#include "user_force.h"
#include "pch.h"
#include <stdio.h>

extern "C"
{

	__declspec(dllexport) void _stdcall USER_FORCE(int* Mode, int I_Control[100], float R_Control[100],
		int* Nstruc, float* Time, float* TimeStep, int* Stage,
		float Position[][6], float Velocity[][6], float Cog[][3],
		float Force[][6], float Addmass[][6][6], int* ErrorFlag)

	{

		//
		// *** Visual C++ Template
		// -----------------------
		//
		// 1. Uses stdcall calling convention
		// 2. Routine name MUST be in upper case
		// 3. All parameters are passed as pointers
		//
		// Input Parameter Description:
		//
		// Mode int*       - 0  = Initialisation. This routine is called once with mode 0
		//                        before the simulation. All parameters are as described
		//                        below except for STAGE, which is undefined. FORCES and
		//                        ADDMAS are assumed undefined on exit.
		//                        IERR if set to > 0 on exit will cause
		//                        the simulation to stop.
		//
		//                   1  = Called during the simulation. FORCE/ADDMAS output expected.
		//
		//                   99 = Termination. This routine is called once with mode 99
		//                        at the end of the simulation.
		//
		// I_Control[100]  - User-defined integer control parameters input in .DAT file.
		// (int*)
		//
		// R_Control[100]  - User-defined real control parameters input in .DAT file.
		// (float*)
		//
		// Nstruc int*     - Number of structures in the the simulation
		//
		// Time float*     - The current time (see Stage below)
		//
		// Timestep float* - The current timestep (DT, see Stage below)
		//
		// Stage int*      - The stage of the integration scheme. AQWA time integration is
		//                   based on a 2-stage predictor corrector method. This routine is
		//                   therefore called twice at each timestep, once with STAGE=1 and
		//                   once with STAGE=2. On stage 2 the position and velocity are
		//                   predictions of the position and velocity at TIME+DT.
		//                   e.g. if the initial time is 0.0 and the step 1.0 seconds then
		//                   calls are as follows for the 1st 3 integration steps:
		//
		//                   CALL USER_FORCE(.....,TIME=0.0,TIMESTEP=1.0,STAGE=1 ...)
		//                   CALL USER_FORCE(.....,TIME=0.0,TIMESTEP=1.0,STAGE=2 ...)
		//                   CALL USER_FORCE(.....,TIME=1.0,TIMESTEP=1.0,STAGE=1 ...)
		//                   CALL USER_FORCE(.....,TIME=1.0,TIMESTEP=1.0,STAGE=2 ...)
		//                   CALL USER_FORCE(.....,TIME=2.0,TIMESTEP=1.0,STAGE=1 ...)
		//                   CALL USER_FORCE(.....,TIME=2.0,TIMESTEP=1.0,STAGE=2 ...)
		//
		// Cog[Nstruc][3]  - Position of the Centre of Gravity in the Definition axes.
		//
		// Position[Nstruc][6] - Position of the structure in the FRA - angles in radians
		// (float*)
		//
		// Velocity[Nstruc][6] - Velocity of the structure in the FRA
		// (float*)              angular velocity in rad/s
		//
		//
		// Output Parameter Description:
		//
		// Force[Nstruc][6] - Force on the Centre of gravity of the structure. NB: these
		// (float)           forces are applied in the Fixed Reference axis e.g.
		//                   the surge(X) force is ALWAYS IN THE SAME DIRECTION i.e. in
		//                   the direction of the X fixed reference axis.
		//
		// Addmass[Nstruc][6][6]
		// (float)         - Added mass matrix for each structure. As the value of the
		//                   acceleration is dependent on FORCES, this matrix may be used
		//                   to apply inertia type forces to the structure. This mass
		//                   will be added to the total added mass of the structure at each
		//                   timestep at each stage.
		//
		// Errorflag int*  - Error flag. The program will abort at any time if this
		//                   error flag is non-zero. The values of the error flag will
		//                   be output in the abort message.


	

		int i, j;
		int struc = 1;

		//------------------------------------------------------------------------
		// MODE#0 - Initialise any summing variables/open/create files.
		//          This mode is executed once before the simulation begins.
		//------------------------------------------------------------------------

		if (*Mode == 0)
		{
		}

		//------------------------------------------------------------------------
		// MODE#1 - On-going - calculation of forces/mass
		//------------------------------------------------------------------------

		else if (*Mode == 1)
		{
			for (struc = 0; struc < *Nstruc; struc++)
			{
				for (i = 0; i < 6; i++)
				{
					Force[struc][i] = 2 * Velocity[struc][i];
					for (j = 0; j < 6; j++)
					{
						Addmass[struc][j][i] = 0.0;
					}
				}
			}
			*ErrorFlag = 0;
		}

		//------------------------------------------------------------------------
		// MODE#99 - Termination - Output/print any summaries required/Close Files
		//           This mode is executed once at the end of the simulation
		//------------------------------------------------------------------------

		else if (*Mode == 99)
		{
		}

		//------------------------------------------------------------------------
		// MODE# ERROR - OUTPUT ERROR MESSAGE
		//------------------------------------------------------------------------

		else
		{
		}

		return;

	}
}


1
2
3
4
5
6
7
8
9
10
11
12
//user_force.h
#pragma once

extern "C"
{

	__declspec(dllexport) void _stdcall USER_FORCE(int* Mode, int I_Control[100], float R_Control[100],
		int* Nstruc, float* Time, float* TimeStep, int* Stage,
		float Position[][6], float Velocity[][6], float Cog[][3],
		float Force[][6], float Addmass[][6][6], int* ErrorFlag)
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// dllmain.cpp : Define o ponto de entrada para o aplicativo DLL.
#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}


After using the dumpbin / exports command, this is the message. The function name is different than it should be and I can't find the reason for it

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
 Section contains the following exports for user_force64.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00011046 _USER_FORCE@52 = @ILT+65(_USER_FORCE@52)

  Summary

        1000 .00cfg
        1000 .data
        1000 .idata
        1000 .msvcjmc
        2000 .rdata
        1000 .reloc
        1000 .rsrc
        6000 .text
       10000 .textbss


Can someone please help me compile correctly?
When using the dumpbin / exports command in a functional DLL, this is the output

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
Dump of file user_force64.dll

File Type: DLL

  Section contains the following exports for user_force64.dll

    00000000 characteristics
    5D3F15AA time date stamp Mon Jul 29 12:50:02 2019
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00001000 USER_FORCE

  Summary

        1000 .data
        1000 .pdata
        1000 .rdata
        1000 .reloc
        1000 .rsrc
        1000 .text


Obs: I'm using VS 2019, with the Dynamic Link Library template
Last edited on
1. remove extern "C"
2. replace __declspec(dllexport) with macros
4. Understand what is name mangling: https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/rzarg/name_mangling.htm

In any case your dumpbin output looks just fine, and you should be able to link to dll. unless you link from C code?
Thanks for the answers!

Furry Guy I just followed this tutorial. However I don't know what to put the file user_force.h.
Malibor, thanks for the suggestions. I'm sorry, but I'm very new to this. Could you help me better? Which macros?

The Ansys manual says: "A DLL can be created to calculate a force based on time, position and / or velocity of a structure. Example interfaces are provided for C or FORTRAN ™, but any programming language that can produce a DLL can be employed"
Last edited on
Another option is to use module-definitions files:
https://www.tutorialspoint.com/dll/dll_writing.htm
Could you help me better? Which macros?


Create a completely new header called ie. export.h and put these macros into export.h header:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// export.h header file
#ifndef MY_EXPORT_H
#define MY_EXPORT_H

// DLL export symbols
#ifdef COMPILE_MY_DLL
#define MY_API __declspec(dllexport)
#elif defined (LINK_MY_DLL)
#define MY_API __declspec(dllimport)
#else // Compile LIB or link LIB
#define MY_API
#endif // COMPILE_MY_DLL

#endif // MY_EXPORT_H 


Now every header in your DLL project from which you want to export functions, structs, classes etc., first include this header, for example:

1
2
3
4
//user_force.h
#include export.h

// other header includes go here ... 


After this step mark all functions that you want to export with MY_API macro, ex:

1
2
3
4
5
6
7
8
9
10
11
12
13
// my_header.h

// this function is exported from DLL, it needs this macro
MY_API void ExportedFunctionName(int a, int b);

// This class is exported from DLL, it needs this macro
class MY_API ClassName
{
       // class function, no need for macro here!
       void SomeFunction();

       // other class declaration...
};


You also need to add this macro in cpp file for all functions whose definitions resides in cpp file, except for class functions! ex:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// my_source.cpp
#include "my_header.h"

// this function is exported from DLL
// it needs this macro in source file too!
MY_API void ExportedFunctionName(int a, int b)
{
      // function implementation goes here
}

// this function is exported from DLL, it does not need macro
// because it's part of a class
vod ClassName::SomeFunction()
{
      // function implementation goes here
}



Do so for all your headers and sources in your DLL project.

Next step before you compile the DLL, is to define COMPILE_MY_DLL macro in your project propertis, so that all sources and headers see this macro, you do this in:
Project Properties -> C/C++ -> Preprocessor -> preprocessor definitions

put COMPILE_MY_DLL into that box and build DLL project.

Now in your other project where you want to link the DLL, you only have to put LINK_MY_DLL into the same project property field.

and MY_API macro will expand to importing a symbol not exporting like in DLL.

Note that using functions in target project does not require any macros, except defining LINK_MY_DLL in project properties.

Note1: that if you compile the DLL as static library, you must not define COMPILE_MY_DLL in project properties or any other macro must not be defined except those in export.h, and there will be no exports or imports in any case, macros in export.h will handle this as you can see.

Note2: _declspec(dllImport) is not required, it's optimization feature.

Note3: Do not use extern "C" unless your DLL is C code, that is compiled as C instead of C++.

There is much more you should learn about DLL's but this should be enough to get what you want.
Last edited on
If someone wants to learn more about DLLs:
https://www.youtube.com/watch?v=JPQWQfDhICA
Now in your other project where you want to link the DLL, you only have to put LINK_MY_DLL into the same project property field.



Malibor, a USER_FORCE function is for ANSYS (a commercial simulation software) that supports DLLs for calculating forces in time. Would I also have to create a macro for the software that will receive the DLL function? I apologize if I got it wrong

And thank you very much for your help, I was able to understand a little more about macros
Last edited on
I see you are dealing with DLL code that is not under your control...
I think the easies way is to load the dll, take the address of function from DLL and use it.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Load DLL into current process:
HMODULE hDLL = LoadLibrary(TEXT("user_force64.dll"));

// define function prototype you want to import:
typedef void (_stdcall* USER_FORCE_FN)(int* Mode, int I_Control[100], float R_Control[100],
		int* Nstruc, float* Time, float* TimeStep, int* Stage,
		float Position[][6], float Velocity[][6], float Cog[][3],
		float Force[][6], float Addmass[][6][6], int* ErrorFlag);

// Get address of a function from DLL:
USER_FORCE_FN USER_FORCE = reinterpret_cast<USER_FORCE_FN>(GetProcAddress(hDLL, "USER_FORCE"));

// Use DLL function here however you need ...
USER_FORCE(...);

// when done release DLL
FreeLibrary(hDLL);

// NOTE: you may want to check for return values of these functions,
// to check if they failed so that you can fix the problem. 


Note that you don't need (and must not) to include a header here!

You said you have problem compiling the DLL, but the DLL is just fine from the dumpbins you shown, it exports symbols, so all you have to do is get function address as shown above and use it.


Would I also have to create a macro for the software that will receive the DLL function?


Software which gets shipped with the DLL will receive already compiled DLL, so no;
Unless you plan to share your software as open source, in which case yes, feel free to rename those macros how ever you want.

If you don't want to modify this DLL as explained in my previous post, then use this alternative method.
You know how to link DLL library into your project right? (needed for steps in my previous post only, not this alternative method)
For the alternative method described in this post you only need to ensure that the compiled DLL in is in PATH environment variable.

For more information about loading a DLL see documentation:
https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw
https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary
https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress
Last edited on
Topic archived. No new replies allowed.