How to convert this sample code

Hello,

I have this sample code, that calls a function in a DLL. The function Callback is provided to the DLL as an argument, in order for the DLL to notify my program of relevant changes.

sample:
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
#include <iostream>
#include <conio.h>
#include <windows.h>
#include <winbase.h>
#include "TcAdsDef.h"
#include "TcAdsApi.h"
 
using namespace std;
 
void _stdcall Callback(AmsAddr*, AdsNotificationHeader*, unsigned long);
 
void main()
{ 
  long                   nErr, nPort; 
  AmsAddr                Addr; 
  PAmsAddr               pAddr = &Addr; 
  ULONG                  hNotification, hUser; 
  AdsNotificationAttrib  adsNotificationAttrib;
  char                                   szVar []={"MAIN.PlcVar"};
 
  // Open communication port on the ADS router
  nPort = AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = 801;
 
  // Set the attributes of the notification
  adsNotificationAttrib.cbLength = 4;
  adsNotificationAttrib.nTransMode = ADSTRANS_SERVERONCHA;
  adsNotificationAttrib.nMaxDelay = 0;
  adsNotificationAttrib.nCycleTime = 10000000; // Unit = 100ns, 1sec = 10000000
 
  // Get variable handle
  nErr = AdsSyncReadWriteReq(pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(hUser), &hUser, sizeof(szVar), szVar);
  if (nErr) cerr << "Error: AdsSyncReadWriteReq: " << nErr << '\n';
 
  // Initiate the transmission of the PLC-variable 
  nErr = AdsSyncAddDeviceNotificationReq(pAddr, ADSIGRP_SYM_VALBYHND, hUser, &adsNotificationAttrib, Callback, hUser, &hNotification);
 
  if (nErr) cerr << "Error: AdsSyncAddDeviceNotificationReq: " << nErr << '\n';
  cout.flush();
 
  // Wait for user intraction (keystroke)
  _getch();
 
  // Finish the transmission of the PLC-variable 
  nErr = AdsSyncDelDeviceNotificationReq(pAddr, hNotification);
  if (nErr) cerr << "Error: AdsSyncDelDeviceNotificationReq: " << nErr << '\n';
 
  // Release  the variable handle
  nErr = AdsSyncWriteReq(pAddr, ADSIGRP_SYM_RELEASEHND, 0, sizeof(hUser), &hUser); 
  if (nErr) cerr << "Error: AdsSyncWriteReq: " << nErr << '\n';
 
  // Close the communication port
  nErr = AdsPortClose();
  if (nErr) cerr << "Error: AdsPortClose: " << nErr << '\n';
}
 
// Callback-function
void __stdcall Callback(AmsAddr* pAddr, AdsNotificationHeader* pNotification, ULONG hUser)
{
  int                     nIndex; 
  static ULONG            nCount = 0; 
  SYSTEMTIME              SystemTime, LocalTime; 
  FILETIME                FileTime; 
  LARGE_INTEGER           LargeInteger; 
  TIME_ZONE_INFORMATION   TimeZoneInformation; 
 
  cout << ++nCount << ". Notification:\n"; 
 
  // print (to screen)) the value of the variable 
  cout << "Value: " << *(ULONG *)pNotification->data << '\n'; 
  cout << "Notification: " << pNotification->hNotification << '\n';
 
  // Convert the timestamp into SYSTEMTIME
  LargeInteger.QuadPart = pNotification->nTimeStamp;
  FileTime.dwLowDateTime = (DWORD)LargeInteger.LowPart;
  FileTime.dwHighDateTime = (DWORD)LargeInteger.HighPart;
  FileTimeToSystemTime(&FileTime, &SystemTime);
 
  // Convert the time value Zeit to local time
  GetTimeZoneInformation(&TimeZoneInformation);
  SystemTimeToTzSpecificLocalTime(&TimeZoneInformation, &SystemTime, &LocalTime);
 
  // Print out the timestamp
  cout << LocalTime.wHour << ":" << LocalTime.wMinute << ":" << LocalTime.wSecond << '.' << LocalTime.wMilliseconds;
 
  // Print out the ADS-address of the sender
  cout << "\nServerNetId: ";
  for (nIndex = 0; nIndex < 6; nIndex++)
    cout << (int)pAddr->netId.b[nIndex] << ".";
  cout << " Port: " << pAddr->port << "\n\n";
  cout.flush();
}


I would like to change this code, so that there is a Main class that opens the connection and there are several separate classes (as below) that register themselves for a specific variable and get notifications if that value is changed. The reason for this is that I want to get several notifications for several independent events and I don't want them to mix.

I figured this should look something like this:
class.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef INACLASS_H
#define INACLASS_H
 
#include "Main.h"
 
class InAClass
{
    public:
        InAClass(Main* mainClass, std::string iolocation);
        virtual ~InAClass();
        void Callback(AmsAddr*, AdsNotificationHeader*, unsigned long);
 
    protected:
    private:
        long                            nErr;
        ULONG                           hNotification, hUser;
        AdsNotificationAttrib           adsNotificationAttrib;
};
 
#endif // INACLASS_H
 


class.cpp
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
#include "InAClass.h"
 
InAClass::InAClass(Main* mainClass, std::string iolocation)
{
    // connect to TwinCAT notifier
    adsNotificationAttrib.cbLength = 2; // size of the variable in bytes
    adsNotificationAttrib.nTransMode = ADSTRANS_SERVERONCHA;
    adsNotificationAttrib.nMaxDelay = 0;
    adsNotificationAttrib.nCycleTime = 10000000; // Unit = 100ns, 1sec = 10000000
 
    // Get variable handle
    char szVar[iolocation.length()];
    for(int i = 0;i < iolocation.length();i++) szVar[i] = iolocation[i];
    nErr = AdsSyncReadWriteReq(*mainClass->getAdsPortPointer(), ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(hUser), &hUser, sizeof(szVar), szVar);
    if (nErr)
    {
        // do something smart to recover?
    }
    // Initiate the transmission of the PLC-variable
    nErr = AdsSyncAddDeviceNotificationReq(*mainClass->getAdsPortPointer(), ADSIGRP_SYM_VALBYHND, hUser, &adsNotificationAttrib, Callback, hUser, &hNotification);
    if (nErr)
    {
        // do something smart to recover?
    }
}
 
InAClass::~InAClass()
{
     // Clean up will be implemented when there is something that works to clean up.
}
 
// Callback-function
void  __stdcall InAClass::Callback(AmsAddr* pAddr, AdsNotificationHeader* pNotification, unsigned long hUser)
{
  int                     nIndex;
  static ULONG            nCount = 0;
  SYSTEMTIME              SystemTime, LocalTime;
  FILETIME                FileTime;
  LARGE_INTEGER           LargeInteger;
  TIME_ZONE_INFORMATION   TimeZoneInformation;
 
  cout << ++nCount << ". Notification:\n";
 
  // print (to screen)) the value of the variable
  cout << "Value: " << *(ULONG *)pNotification->data << '\n';
  cout << "Notification: " << pNotification->hNotification << '\n';
 
  // Convert the timestamp into SYSTEMTIME
  LargeInteger.QuadPart = pNotification->nTimeStamp;
  FileTime.dwLowDateTime = (DWORD)LargeInteger.LowPart;
  FileTime.dwHighDateTime = (DWORD)LargeInteger.HighPart;
  FileTimeToSystemTime(&FileTime, &SystemTime);
 
  // Convert the time value Zeit to local time
  GetTimeZoneInformation(&TimeZoneInformation);
  SystemTimeToTzSpecificLocalTime(&TimeZoneInformation, &SystemTime, &LocalTime);
 
  // Print out the timestamp
  cout << LocalTime.wHour << ":" << LocalTime.wMinute << ":" << LocalTime.wSecond << '.' << LocalTime.wMilliseconds;
 
  // Print out the ADS-address of the sender
  cout << "\nServerNetId: ";
  for (nIndex = 0; nIndex < 6; nIndex++)    cout << (int)pAddr->netId.b[nIndex] << ".";
  cout << " Port: " << pAddr->port << "\n\n";
  cout.flush();
}
}
 


Unfortunately this gives me an error:
error: cannot convert 'InAClass::Callback' from type 'void (InAClass::)(AmsAddr*, AdsNotificationHeader*, long unsigned int)' to type 'PAdsNotificationFuncEx {aka void (__attribute__((__stdcall__)) *)(AmsAddr*, AdsNotificationHeader*, long unsigned int)}'

At first I thought this was because I don't have the namespace "using namespace std;" on top, but then I should be able to find something that specifically needs to come from the std namespace and is not marked as such. I don't want to rule the option out, but so far I could not find anything like that.
An alternative explanation might be that the Callback function needs to be global, but if I make it global, how can I distinguish between several Callback functions?

Does anyone have experience in this area? Or does anyone maybe have an educated guess on how to solve this issue?

Kind regards, Nico
Where does the error appear?
What is that '__stdcall'?
All WinAPI functions expect pointer to finctions using stdcall calling convention.
Member function of the classes uses thiscall calling convention which is different on different compilers, and has a hidden parameter (therefore you cannot make it meet signature requirements easily). You can: (a) make callback function static or (b) use old plain functions.

Info on calling conventions: en.wikipedia.org/wiki/X86_calling_conventions
Last edited on
Thank you for your reply, it made me wonder if my compiler is suitable. It turned out that my compiler (GCC) deals with it in a way that is compatible with the DLL (which was build using visual studio).

I found the Callback function can only be called if it is global (not inside a class). In hindsight I can imagine this is required, because the DLL can't have any knowledge about the classes it notifies. Still I would prefer the notification to be directed to a specific instance of a class instead of a global function. If there is anyone who knows a way to do that I would be very happy to hear it.

Kind regards, Nico
"Lower level API" in http://qt-project.org/doc/qt-5/plugins-howto.html
does something like that.

Main prog has interface class. Plugin (DLL) has derived class and some init function that returns a pointer to derived object. When the library is (explicitly) loaded, the function is called and main prog stores the pointer. Later on the interface functions are used to invoke code within the library. Essentially you do have an object to work with.

I don't know how to exploit the idea.
Thanks a lot. I will look into that and see if I can use it.

Kind regards, Nico
Make the callback function static. Pass a pointer to the object instance as the user data parameter, cast it back to a pointer of your class, call a member function - Job done.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef INACLASS_H
#define INACLASS_H
 
#include "Main.h"
 
class InAClass
{
    public:
        InAClass(Main* mainClass, std::string iolocation);
        virtual ~InAClass();
        static void Callback(AmsAddr*, AdsNotificationHeader*, unsigned long);
        // Add this...
        void HandleCallback(AmsAddr*, AdsNoificationHeader*);
    protected:
    private:
        long                            nErr;
        ULONG                           hNotification, hUser;
        AdsNotificationAttrib           adsNotificationAttrib;
};
 
#endif // INACLASS_H 

Change line 20 to:
1
2
3
//...
nErr = AdsSyncAddDeviceNotificationReq(pAddr, ADSIGRP_SYM_VALBYHND, hUser, &adsNotificationAttrib, InAClass::Callback, static_cast<unsigned long>(this), &hNotification);
//... 


Then when your callback is invoked, cast the hUser back to a pointer of your class:
1
2
3
4
5
6
void InAClass::Callback(AmsAddr* pAddr, AdsNotificationHeader* pNotification, unsigned long hUser)
{
  InAClass* ptr = static_cast<InAClass*>(hUser);
  // Now you have full access to your class instance.
  ptr->HandleCallback(pAddr, pNotification);
}


The "hUser" parameter is provided for you to pass your own data into the callback function when it is invoked by whatever framework you are using (this is a common construct).
Last edited on
Topic archived. No new replies allowed.