To develop an integration model or a substructure model in C code for communication, the developer needs to provide the explicit linkage to the "DataExchange.dll" in the code. This document assumes that the developer is familiar with the C/C++ language.
Link with DataExchange library (C/C++)¶
To explicitly link with the DLL in C/C++, the first thing is to define function pointers which can be considered as aliases for the functions implemented in DataExchange.dll. The name of the function pointers uses the same name as the functions in the DataExchange.dll except that the word "Func" is attached to the end of each function name.
typedef int (*setupconnectionFunc) (int, SOCKET*, int, char*, int);
typedef int (*initializationFunc) (SOCKET, int, int);
typedef uint8_t (*commandFunc) (SOCKET, int, int);
typedef int (*recvdataFunc) (SOCKET, double*, int, int);
typedef int (*senddataFunc) (SOCKET, double*, int, int);
typedef int (*closeFunc) (SOCKET*);
typedef void (*updatemessageheaderFunc) (uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint16_t);
typedef void (*updatecommandFunc) (uint8_t command);
typedef void (*updatedatatypeFunc) (int, int, int, int, int, int, int);
typedef int (*indicatorFunc) (void);
Then in the main{}
funciton, the above function pointers are declared as follows:
setupconnectionFunc _SetupconnectionFunc;
initializationFunc _InitializationFunc;
commandFunc _CommandFunc;
recvdataFunc _RecvDataFunc;
senddataFunc _SendDataFunc;
closeFunc _CloseFunc;
updatemessageheaderFunc _UpdateMessageHeaderFunc;
updatecommandFunc _UpdateCommandFunc;
updatedatatypeFunc _UpdateDatatypeFunc;
indicatorFunc _IndicatorFunc;
Next, The DataExchange.dll is loaded to a DLL instance using the LoadLibLoadLibrary()
function. The error message is given if the program fails to load the library properly.
HINSTANCE hInstLibrary = LoadLibraryW((LPCWSTR)L"DataExchange.dll");
if (hInstLibrary == NULL) {
printf("ERROR: unable to load DLL\n");
}
After that, the mapping of the DLL functions and the pointers are defined as below
_SetupconnectionFunc = (setupconnectionFunc)GetProcAddress(hInstLibrary, "setupconnection");
_InitializationFunc = (initializationFunc)GetProcAddress(hInstLibrary, "initialization");
_CommandFunc = (commandFunc)GetProcAddress(hInstLibrary, "command");
_RecvDataFunc = (recvdataFunc)GetProcAddress(hInstLibrary, "recvdata");
_SendDataFunc = (senddataFunc)GetProcAddress(hInstLibrary, "senddata");
_CloseFunc = (closeFunc)GetProcAddress(hInstLibrary, "close");
_UpdateMessageHeaderFunc = (updatemessageheaderFunc)GetProcAddress(hInstLibrary, "updatemessageheader");
_UpdateCommandFunc = (updatecommandFunc)GetProcAddress(hInstLibrary, "updatecommand");
_UpdateDatatypeFunc = (updatedatatypeFunc)GetProcAddress(hInstLibrary, "updatedatatype");
_IndicatorFunc = (indicatorFunc)GetProcAddress(hInstLibrary, "indicator");
Now the function pointers hold the functions of the DLL and you can use them using the function pointers other than the actual function names in the DataExchange.dll. For example, the way to use the setupconnection()
function to establish the connection is shown below
iResult = _SetupconnectionFunc(PortNumber, &sockfd, flag, machineInetAddr, TCP_IP);
Integration Model (C/C++)¶
In addition to the above code to link with the DataExchange.dll, the main{}
function of the integration model starts with declaring variables required for communication.
int iResult;
int PortNumber = 8090; // port number
char* machineInetAddr = "127.0.0.1"; // IP address;
SOCKET sockfd = 0; // initialize socket number
int flag = 1; // 1 - integration module; 2 - substructure module
int numdofs = 3; // total interface DOFs
where
- PortNumber: port number. This number should be consistent with the port number specified in the substructure model below.
- machineInetAddr - IP address of the machine with the substructure model. For the case that the integration and substructure models are modelled in the same Windows machine, the standard IP address of "127.0.0.1" can be used.
- sockfd: a SOCKET type variable for a newly generated socket
- flag: a variable to be used in the DataExchange.dll functions to indicate the model is an integration model or a substructure model.
- numdofs: a variable defines the total number of degrees of freedom at the interface nodes
Communication Initialization¶
Then, three DLL functions are called to initialize the data exchange format, set up the connection with the substructure model, and send the initialized data exchange format to the substructure model.
// initialize data exchange format
_UpdateMessageHeaderFunc(2, 0, Software_only, VecTor2, Double_precision, numdofs);
// setup connection
iResult = _SetupconnectionFunc(PortNumber, &sockfd, flag, machineInetAddr, TCP_IP);
if (iResult != 0) {
cout << "Connection failed.\n";
return -1;
} else {
cout << "Connection done.\n";
}
// send the data exchange format to the substructure model
if (_InitializationFunc(sockfd, flag, TCP_IP) < 0) {
cout << "Initialization failed.\n";
return -1;
} else {
cout << "Initialization done.\n";
}
where _UpdateMessageHeaderFunc(version, command, testtype, subtype, precision, numdofs) function is used to initialize the following information defined in the header block of the data exchanged through UTNP (see here for more details).
- version: this parameter corresponds to the Version parameter in the data exchange format. It is 2 for the current release version.
- command: this parameter corresponds to the Command parameter in the data exchange format. It can be defined as zero at this initialization stage.
- testtype: this parameter corresponds to the Test type parameter in the data exchange format. Depending on the simulation methods, one of the following values can be used to initialize the parameter.
- 1 - Pseudo-dynamic (ramp-hold) simulation
- 2 - Pseudo-dynamic (continuous) simulation
- 4 - Numerical multi-platform simulation
subtype: this parameter corresponds to the Substructure type parameter in the data exchange format. Depending on the substructure modules, one of the following values can be used to initialize the parameter
- 1 - OpenSees
- 2 - Zeus-NL
- 3 - ABAQUS
- 4 - VecTor2
- 5 - NICON-AIO or NICON-NIO
- 6 - VecTor4
- 7 - LS-Dyna
precision: this parameter corresponds to the Precision parameter in the data exchange format. It can be defined as 1 for single precision and 2 for double precision
- numdofs: this parameter corresponds to the Number of DOFs parameter in the data exchange format. It is equal to the total number of effective DOFs at the interface nodes for communication.
Sending Data¶
Once communication is established and the data format parameters are initialized, the integration module starts with sending displacement command to the substructure.
// update the command in the message header block for imposing target displacement
_UpdateCommandFunc (Impose_TargetValues);
// define the type of data to be appended to the message header
// Updatetype (disp, vel, accel, force, stiff, mass,temperature)
// use 1 and 0 to enable and disable the data in the function.
_UpdateDatatypeFunc (1, 0, 0, 0, 0, 0, 0);
// send the updated data header to the substructure module
action = _CommandFunc(sockfd, flag, TCP_IP);
// calculate the data size to be appended to the message header
lens = _IndicatorFunc();
sdata = new double[lens];
for (int j = 0; j < lens; j++)
sdata[j] = unn[j];
// send the data to the substructure module
iResult = _SendDataFunc(sockfd, sdata, lens, TCP_IP);
if (iResult != 0) {
cout << "Failed to send disp.\n";
return -1;
} else {
cout << "Disp sent.\n";
}
// deallocation of variable
delete[] sdata;
sdata = NULL;
Receiving Data¶
Once the restoring force is computed in the substructure module, the integration module sends another command to receive the computed force from the substructure module.
// update the command in the message header for receiving data from the substructure module
_UpdateCommandFunc (Report_Values);
// define the type of data to be appended to the message header
// Updatetype (disp, vel, accel, force, stiff, mass,temperature)
// use 1 and 0 to enable and disable the data in the function.
_UpdateDatatypeFunc (0, 0, 0, 1, 0, 0, 0);
// send the updated message header to the substructure module
action = _CommandFunc(sockfd, flag, TCP_IP);
// calculate the data size to be appended to the message header
lens = _indicatorFunc();
rdata = new double[lens];
// receive the computed restoring force from substructure module
iResult = _RecvDataFunc(sockfd, rdata, lens, TCP_IP);
if (iResult != 0) {
cout << "Failed to receive force.\n";
return -1;
} else {
cout << "Force received.\n";
}
// Formulate the equivalent force to solve the system's motion equation
for (int j = 0; j < lens; j++) {
force[j] = rdata[j];
Pe[j] = P[j] - force[j];
}
delete [] rdata;
rdata = NULL;
The above sending and receiving data steps are repeated until the end of the simulation.
Disconnection¶
Once the simulation is done, the following is included in the code to disconnect the integration module from the substructure module.
// update the command in the message header
_UpdateCommandFunc (Terminate);
// send the updated message header to the substructure module
action = _CommandFunc(sockfd, flag, TCP_IP);
// disconnect and close socket
_CloseFunc(&sockfd);
cout<<"Simulation done."<<endl;
Substructure Model (C/C++)¶
Similar to the above integration module, the variables for the port number, IP address, socket number, and indicator of module type should be defined after the program is linked with the DataExchange.dll.
// define socket variables
int iResult;
int PortNumber = 8090; // port number
char* machineInetAddr=NULL; // ipAddress of server;
SOCKET sockfd = 0; // initialize socket number
int flag = 2; // 1 - integration module; 2 - substructure module
Communication Initialization¶
The same _UpdateMessageHeaderFunc, _SetupconnectionFunc, _InitializationFunc are used in the substructure module to initialize the message header and connect with the integration module. The only difference is the value of the flag which indicates that the dll functions are being called by a substructure module.
// declare data exchange format
_UpdateMessageHeaderFunc(0, 0, 0, 0, Double_precision, 0);
// setup connection
iResult = _SetupconnectionFunc(PortNumber, &sockfd, flag, machineInetAddr, TCP_IP);
if (iResult != 0) {
cout << "Connection failed.\n";
return -1;
} else {
cout << "Connection done.\n";
}
// receive the message header from the integration module
if (_InitializationFunc(sockfd, flag, TCP_IP) < 0) {
cout << "Initialization failed.\n";
return -1;
} else {
cout << "Initialization done.\n";
}
After the connection is built, a switch statement is included, which defines different actions based on the received command values in the message header.
// receive data format from integration module
int action = _CommandFunc(sockfd, flag, TCP_IP);
switch(action) {
case Impose_TargetValues:
// code to receive data from the integration module (see below)
break;
case Report_Values:
// code to send computed data to the integration module (see below)
break;
case Terminate: // exit the switch statement
exitFlag = 0;
break;
default:
cout << "Invalid action received" << endl;
break;
Receiving Data¶
In the above case of Impose_TargetValues, the substructure module receives the displacement from the integration module. Detailed code is shown below:
// calculate the size to be appended to the message header
lens = _indicatorFunc();
rdata = new double[lens];
// receive displacement from the integration module
iResult = _RecvDataFunc(sockfd, rdata, lens, TCP_IP);
if (iResult != 0) {
cout << "Failed to receive trial displacement.\n";
return -1;
} else {
cout << "Trial displacement received.\n";
}
// save the displacement for analysis
for (int i = 0; i < 3; i++)
Tdisp[i] = rdata[i];
// deallocation of variable
delete [] rdata;
rdata = NULL;
Sending Data¶
In the case of Report_Values, the substructure module sends the computed restoring force back to the integration module. Detailed code is shown below:
// calculate the size to be appended to the message header
lens = _indicatorFunc();
// calculate the restoring force with respect to the received displacement
for (int i = 0; i < 3; i++) {
RF[i] = 0.;
for (int j = 0; j < 3; j++) {
RF[i] += K[i][j] * Tdisp[j];
}
}
sdata = new double [lens];
// initialize force vector
for (int i = 0; i<lens; i++)
sdata[i] = RF[i];
// send the computed force to integration module
iResult = _SendDataFunc(sockfd, sdata, lens, TCP_IP);
// deallocation of variable
delete[] sdata;
sdata = NULL;
Disconnection¶
At the end of the simulation, the communication is disconnected.
// disconnect and close socket
_CloseFunc(&sockfd);
cout << "Simulation done" << endl;
Source Code Downloading¶
The source code of the above communication example in C++ can be downloaded from here.