Introduction

TwinCAT ADS-DLL: Introduction

引言

ADS--DLL提供了通过TwinCAT路由器和其他ADS设备通信的功能。 在TwinCAT ADS.下你将找到更多的关于ADS的信息。

所有在本指南中用到的例子都是在Visual C++，Serviece Pack3上实现的。所有的例子都比较简单, 所以并不需要很深的C/C++的知识.


Linking to Borland C++ Builder

TwinCAT ADS-DLL: Reference

Borland C++ Builder中的链接

必需的文件

你需要以下文件来开发程序:

  • AdsDll.dll - 动态函数库
  • AdsApi.h - ADS函数声明 (C++ 头文件)
  • AdsDef.h - 结构体和常量声明(C++ 头文件)

AdsDll.dll 位于 Windows NT/2000 'System32' 目录下， AdsDll.lib, AdsApi.h 和 AdsDef.h 在 TwinCAT 'Ads API' 目录下.你必须确定这些文件会在TwinCAT安装中会被安装好. 如果没有安装好，那就再安装一次，并且当到达'Select components' 对话框时选中'TwinCAT Ads Dll'

其他必需的工具

  • IMPLIB.EXE - 为 ADSDLL创建导入库

建议

不能把TwinCAT安装时装载的AdsDLL.lib用在Borland C++工程中。如果你试图去编译这个库，你会看到一下错误消息：

[Linker Error] 'E:\BORLAND\CBUILDER5\PROJECTS\SAMPLE1\ADSDLL.LIB' contains invalid OMF record, type 0x21 (possibly COFF)

IMPLIB 直接从 DLL中创建导入库.. 用 IMPLIB 创建一个新的导入库并添加到工程中.例如 ：

IMPLIB -a AdsDll AdsDll.dll

转换符号-a是为了和Microsoft 库相兼容

在AdsDEF.h中编译器会分配一个4字节的INT给枚举类型。

将这两个头文件都包含到工程中。例如:

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

#include "c:\TwinCAT\ADS Api\AdsDEF.h"
#include "c:\TwinCAT\ADS Api\AdsAPI.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

添加这个新生成的 AdsDll.Lib库到工程中。


AdsAmsRegisterRouterNotification

AdsAmsRegisterNotificationReq()函数用来检测TwinCAT路由器状态的改变。这个回调函数会在每次状态发生改变的时候被调用，通过AdsAmsUnRegisterNotification()函数才能再次结束对路由状态的检测。

LONG AdsAmsRegisterRouterNotification(  PAmsRouterNotificationFunc   pNoteFunc,
);

参数

pNoteFunc

[输入] 回调函数的名称

返回值

返回函数 错误状态.

例子

见例 11.


AdsAmsUnRegisterRouterNotification

通过AdsAmsUnRegisterNotification() 函数可以停止检测路由器的状态. 见AdsAmsRegisterNotificationReq().

LONG AdsAmsUnRegisterRouterNotification(
  void
);

参数

-

返回值

返回函数的错误状态.

例子

见例 11.


AdsGetDllVersion

返回ADS_DLL版本号。

LONG AdsGetDllVersion(
  void
);

参数

-

返回值

long类型的以编码形式包含在ADS-DLL相关的3个项目中。

例子

见例 1.


AdsGetLocalAddress

返回本地NetId和端口号。

LONG AdsGetLocalAddress(
  PAmsAddr     pAddr
);

参数

pAddr

[输出] AmsAddr的结构体指针.

返回值

返回函数的 错误状态.

例子

见例 2.


AdsPortClose

关闭通信端口.

LONG AdsPortClose(
  void
);

参数

-

返回值

返回函数的错误状态.

例子

见例 2.


AdsPortOpen

建立一个和TwinCAT消息路由通信的通信端口.

LONG AdsPortOpen(
  void
);

参数

-

返回值

返回一个分配好的端口号。

例子

见例2.


AdsSyncAddDeviceNotificationReq

ADS设备中的一个消息处理的定义。当一个特定的消息发生就会引起一个ADS客户端的函数调用。

LONG AdsSyncAddDeviceNotificationReq(
  PAmsAddr                 pAddr,
  ULONG                    nIndexGroup,
  ULONG                    nIndexOffset,
  PAdsNotificationAttrib   pNoteAttrib,
  PAdsNotificationFunc     pNoteFunc,
  ULONG                    hUser,
  PULONG                   pNotification
);

参数

pAddr

[输入] ADSserver上的NetId和端口号的结构。

nIndexGroup

[输入] 索引组.

nIndexOffset

[输入] 偏移量

pNoteAttrib

[输入]包含信息的结构体指针.

pNoteFunc

[输入] 回调函数的名称.

hUser

[输入] 传递给回调函数的32位值.

pNotification

[输出] 指向接受消息处理句柄的一个变量地址的指针.

返回值

返回函数的 错误状态.

备注

如果TwinCAT路由器停止然后再次启动，消息处理就会失效。你可以通过AdsAmsRegisterRouterNotification()函数捕捉信息 .

例子

见例8.


AdsSyncDelDeviceNotificationReq

从ADSserver上删除创建过的消息处理。

LONG AdsSyncDelDeviceNotificationReq(
  PAmsAddr       pAddr,
  ULONG          hNotification
);

参数

pAddr

[输入] ADSserver的NetId和端口号结构。

hNotification

[输出] 包含消息处理句柄的变量地址。

返回值

返回值 错误状态.

例子

见例 8.


AdsSyncReadDeviceInfoReq

读ADS server上的标识和版本号。

LONG AdsSyncReadDeviceInfoReq(
  PAmsAddr        pAddr,
  PCHAR           pDevName,
  PAdsVersion     pVersion
);

参数

pAddr

[输入] ADSserver的NetId和端口号结构。

pDevName

[输出] 接收ADS设备的名字字符串指针。

pVersion

[输出] 指向AdsVersion类型的用来接收版本号的一个变量地址指针。

返回值

返回函数的 错误状态.

例子

见例 5.


AdsSyncReadReq

同步读写ADSserver数据.

LONG AdsSyncReadReq(
  PAmsAddr  pAddr,
  ULONG     nIndexGroup,
  ULONG     nIndexOffset,
  ULONG     nLength,
  PVOID     pData
);

参数

pAddr

[输入]ADSserver的NetId和端口号结构 .

nIndexGroup

[输入]索引组.

nIndexOffset

[输入]偏移量 .

nLength

[输入]数据长度 .

pData

[输出] 指向缓冲区的指针.

返回值

返回函数的 错误状态.

例子

见例 3.


AdsSyncReadReqEx

同步读ADSserver数据

LONG AdsSyncReadReqEx(
  PAmsAddr  pAddr,
  ULONG     nIndexGroup,
  ULONG     nIndexOffset,
  ULONG     nLength,
  PVOID     pData,
  ULONG*    pcbReturn
);

参数

pAddr

[输入] ADSserver的NetId和端口号结构

nIndexGroup

[输入] 索引组.

nIndexOffset

[输入]偏移量

nLength

[输入]数据字节长度 .

pData

[输出] 指向缓冲区的指针。
pcbReturn
[输出]变量指针，这个变量返回的是成功读取数据的字节数。

返回值

返回函数 错误状态.


AdsSyncReadStateReq

从ADSserver上读取ADS状态和设备状态。

LONG AdsSyncReadStateReq(
  PAmsAddr  pAddr,
  USHORT    *pAdsState,
  PUSHORT   pDeviceState
);

参数

pAddr

[输入]ADSserver上的NetId和端口号结构。

pAdsState

[输入] 接收ADS状态的变量地址指针(见字节数据ADSSTATE)。

pDeviceState

[输入] 接收设备状态的变量地址指针。

返回值

返回函数的 错误状态.

备注

任意ADS设备能够通知另外ADS设备关于它的当前状态。设备本身的状态和ADS设备的ADS接口状态还是有区别的。ADS规范中指定了ADS接口能够通用的状态。
例 11 描述了在回调函数帮助下怎么样检测状态的变化。

例子

见例 4.


AdsSyncReadWriteReq

从ADSserver上同步接收和读取数据

LONG AdsSyncReadWriteReq(
  PAmsAddr      pAddr,
  ULONG         nIndexGroup,
  ULONG         nIndexOffset,
  ULONG         nReadLength,
  PVOID         pReadData,
  ULONG         nWriteLength,
  PVOID         pWriteData
);

参数

pAddr

[输入] ADSserver的NetId和端口号结构

nIndexGroup

[输入] 索引组

nIndexOffset

[输入] 偏移量

nReadLength

[输入] ADS设备返回的数据字节长度

pReadData

[输出] 指向ADS设备返回数据的缓冲区的指针

nWriteLength

[输入] 写到ADS设备的数据字节长度

pWriteData

[输出]指向写到ADS设备的数据缓冲区指针

返回值

返回函数的 错误状态.

例子

见例 7.


AdsSyncReadWriteReqEx

写数据到ADSserver并从ADS设备中接收数据

LONG AdsSyncReadWriteReqEx(
  PAmsAddr      pAddr,
  ULONG         nIndexGroup,
  ULONG         nIndexOffset,
  ULONG         nReadLength,
  PVOID         pReadData,
  ULONG         nWriteLength,
  PVOID         pWriteData,
  ULONG*        pcbReturn
);

参数

pAddr

[输入] ADSserver的NetId和端口号结构

nIndexGroup

[输入] 索引组

nIndexOffset

[输入] 偏移量

nReadLength

[输入] ADS设备返回的数据字节长度

pReadData

[输出] 指向ADS设备返回的数据缓存区指针

nWriteLength

[输入] 写到ADS设备的数据字节长度.

pWriteData

[输出] 指向写到ADS设备的数据缓存区指针
pcbReturn
[输出]变量指针，该变量返回的是成功读取数据的字节数

返回值

返回函数的 错误状态.


AdsSyncSetTimeout

为ADS函数更改超时时间。标准 值是5000ms

LONG AdsSyncSetTimeout(
  LONG           nMs,
);

参数

nMs

[输入] 超时时间

返回值

返回函数的 错误状态.

例子

-


AdsSyncWriteControlReq

改变ADS和ADSserver的设备状态

LONG AdsSyncWriteControlReq(
  PAmsAddr      pAddr,
  USHORT        nAdsState,
  USHORT        nDeviceState,
  ULONG         nLength,
  PVOID         pData
);

参数

pAddr

[输入] ADSserver的NetId和端口号结构

nAdsState

[输入]新的ADS状态

nDeviceState

[输入] 新的ADS设备状态

nLength

[输入] 数据字节长度

pData

[输入]指向缓存区的指针

返回值

返回函数的错误状态.

备注

除了改变ADS和设备状态，它还可能发送数据到ADS服务器上为了转换更多的信息。在当前的ADS设备（PLC,NC..)中，这个数据不会产生进一步的影响。任意ADS设备能够通知另外ADS设备关于它的当前状态。设备本身的状态和ADS设备的ADS接口状态还是有区别的。ADS规范中指定了ADS借口能够通用的状态。

例子

见例 6.


AdsSyncWriteReq

同步写数据到ADS设备

LONG AdsSyncWriteReq(
  PAmsAddr    pAddr,
  ULONG       nIndexGroup,
  ULONG       nIndexOffset,
  ULONG       nLength,
  PVOID       pData
);

参数

pAddr

[输入] ADSserver的NetId和端口号结构

nIndexGroup

[输入] 索引组

nIndexOffset

[输入] 偏移量

nLength

[输入] 写到ADS服务器上的数据字节长度

pData

[输入] 指向缓存区的指针

返回值

返回函数的 错误状态.

例子

见例2.


AdsNotificationHeader

这个结构也是传递给回调函数

typedef struct {
  ULONG           hNotification;
  __int64         nTimeStamp;
  ULONG           cbSampleSize;
  UCHAR           data[ANYSIZE_ARRAY];
} AdsNotificationHeader, *PAdsNotificationHeader;

成员

hNotification

消息处理的句柄，在消息处理定义的时候就指定好的。

nTimeStamp

FILETIME 格式的时间标记

cbSampleSize

传输的字节数

data[ANY_SIZE_ARRAY]

传输的数据数组

备注

这个时间标记是通过一个为64位的变量的格式传输的，代表一个从01.01.1601开始的100ns的时间和日期，本地的时间不考虑；协调全局时间时需要用到。如果你想进入个人元素（天，月，年，小时，分钟，秒）你需要将格式从FILETIME转换到SYSTEMTIME,并且计算在考虑本地转换时间的时间。

例子

见例 8.


AdsVersion

包含版本号，复版本号和创建号的结构

typedef struct {
  UCHAR           version;
  UCHAR           revision;
  USHORT          build;
} AdsVersion, *PAdsVersion;

成员

version

版本号

revision

复版本号

build

创建号


AmsAddr

一个ADS设备完整的地址结构

typedef struct {
  AmsNetId        netId;
  USHORT          port;
} AmsAddr, *PAmsAddr;

成员E

NetId

网络标识.

port

端口号


AmsNetId

ADS设备的网络标识可用该结构表示

typedef struct {
  UCHAR        b[6];
} AmsNetId, *PAmsNetId;

成员

b[6]

网络标识，由6个数字组成

备注

该就结构由6个UCHAR类型的元素组成的数组。每个数组中的元素的值在1到255间。网络标识可以在TwinCAT系统设备的帮助下完成设置


ADSSTATE

typedef enum nAdsState {
  ADSSTATE_INVALID		= 0,
  ADSSTATE_IDLE		= 1,
  ADSSTATE_RESET		= 2,
  ADSSTATE_INIT		= 3,
  ADSSTATE_START		= 4,
  ADSSTATE_RUN		= 5,
  ADSSTATE_STOP		= 6,
  ADSSTATE_SAVECFG		= 7,
  ADSSTATE_LOADCFG		= 8,
  ADSSTATE_POWERFAILURE	= 9,
  ADSSTATE_POWERGOOD	= 10,
  ADSSTATE_ERROR		= 11,
  ADSSTATE_MAXSTATES
} ADSSTATE;


ADSTRANSMODE

typedef enum nAdsTransMode {
  ADSTRANS_NOTRANS		= 0,
  ADSTRANS_CLIENTCYCLE	= 1,
  ADSTRANS_CLIENT1REQ	= 2,
  ADSTRANS_SERVERCYCLE	= 3,
  ADSTRANS_SERVERONCHA	= 4
} ADSTRANSMODE;


Overview

TwinCAT ADS-DLL: Examples

概述

Description | Source text
例 1: 读DLL版本 | ADS-DLL Sample01.exe
例 2: 同步写标志到PLC | ADS-DLL Sample02.exe
例 3: 从PLC同步读标志 | ADS-DLL Sample03.exe
例 4: 读ADS状态 | ADS-DLL Sample04.exe
例 5: 读ADS信息 | ADS-DLL Sample05.exe
例 6: 启动/停止PLC | ADS-DLL Sample06.exe
例 7: 访问PLC矩阵 | ADS-DLL Sample07.exe
例 8: 事件驱动方式读 | ADS-DLL Sample08.exe
例 9: 通过变量名进入 | ADS-DLL Sample09.exe
例 10: 读PLC变量声明 | ADS-DLL Sample10.exe
例 11: 检测TwinCAT的路由器和PLC的状态改变 | ADS-DLL Sample11.exe
例 12: Delphi: 同步读写PLC变量 | ADS-DLL Sample12.exe
例 13: 事件驱动方式检测符号表的改变 | ADS-DLL Sample13.exe
例 14: Delphi: 检测TwinCAT路由器和PLC的状态改变 | ADS-DLL Sample14.exe
例 15: Delphi: 启动/停止PLC | ADS-DLL Sample15.exe
例 16: 读DLL版本 | ADS-DLL Sample16.exe


Read DLL version

TwinCAT ADS-DLL: Examples

读取DLL版本

该程序能确定DLL文件的版本

#include <iostream.h>
#include <conio.h>
#include <windows.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void main()
{
  long        nTemp;
  AdsVersion* pDLLVersion;

  nTemp = AdsGetDllVersion();
  pDLLVersion = (AdsVersion *)&nTemp;
  cout << "Version: " << (int)pDLLVersion->version << '\n';
  cout << "Revision: " << (int)pDLLVersion->revision << '\n';
  cout << "Build: " << pDLLVersion->build << '\n';
  cout.flush();
  getch();
}

打开 '读DLL版本' 示例程序.


Write flag synchronously into the PLC

TwinCAT ADS-DLL: Examples

同步写标志到

在这个示例程序中，用户输入的值是写到标志的双字0

#include <iostream.h>
#include <windows.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void main()
{
  long     nErr, nPort;
  AmsAddr  Addr;
  PAmsAddr pAddr = &Addr;
  DWORD    dwData;

  // Open communication port for local PLC (Run-time system 1)
  nPort = AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = AMSPORT_R0_PLC_RTS1;

  // Read value from user that is to be written to the PLC
  cout << "Value: ";
  cin >> dwData;

  // Write value to MD0
  nErr = AdsSyncWriteReq( pAddr, 0x4020, 0x0, 0x4, &dwData );
  if (nErr) cerr << "Error: AdsSyncWriteReq: " << nErr << '\n';

  // Close communication port
  nErr = AdsPortClose();
  if (nErr) cerr << "Error: AdsPortClose: " << nErr << '\n';
}

打开示例程序 '同步写标志到PLC'


Read flag synchronously from the PLC

TwinCAT ADS-DLL: Examples

从PLC中同步读标志

在这个示例程序中，标志中双字0标志显示到屏幕上

#include <iostream.h>
#include <windows.h>
#include <conio.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void main()
{
  long      nErr, nPort;
  AmsAddr   Addr;
  PAmsAddr  pAddr = &Addr;
  DWORD     dwData;

  // Open communication port for local PLC (Run-time system 1)
  nPort = AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = AMSPORT_R0_PLC_RTS1;

  // Read value from MD0 and display
  do
  {
    nErr = AdsSyncReadReq(pAddr, 0x4020, 0x0, 0x4, &dwData);
    if (nErr) cerr << "Error: AdsSyncReadReq: " << nErr << '\n';
    cout << dwData << '\n';
    cout.flush();
  }
  while (getch() == '\r');    // mit Carriage return nächsten Wert auslesen, sonst Ende

  // Close communication port
  nErr = AdsPortClose();
  if (nErr) cerr << "Error: AdsPortClose: " << nErr << '\n';
}

打开示例程序 '从PLC中同步读标志'


Read ADS status

TwinCAT ADS-DLL: Examples

读取ADS状态

这个程序读取PLC的状态。ADSSTATE类型的变量包含了例如PLC是否在运行或者停止状态的信息。

#include <iostream.h>
#include <windows.h>
#include <conio.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void main()
{
  ADSSTATE   nAdsState;
  USHORT	nDeviceState;
  long	nErr, nPort;
  AmsAddr	Addr;
  PAmsAddr   pAddr = &Addr;

  // Open communication port for local PLC (Run-time system 1)
  nPort = AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = AMSPORT_R0_PLC_RTS1;

  do
  {
    nErr = AdsSyncReadStateReq(pAddr, &nAdsState, &nDeviceState);
    if (nErr)
      cerr << "Error: AdsSyncReadStateReq: " << nErr << '\n';
    else
    {
      cout << "AdsState: " << nAdsState << '\n';
      cout << "DeviceState: " << nDeviceState << '\n';
    }
    cout.flush();
  }
  while ( getch() == '\r'); // continue on a carriage return, finish for any other key

  // Close communication port
  nErr = AdsPortClose();
  if (nErr) cerr << "Error: AdsPortClose: " << nErr << '\n';
}

打开 '读取ADS状态' 示例程序


Read ADS information

TwinCAT ADS-DLL: Examples

读取ADS信息

每个ADS设备都包含了一个版本号和一个识别。示例程序从PLC中读取信息并显示到屏幕上

#include <iostream.h>
#include <windows.h>
#include <conio.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void main()
{
  LONG           nErr, nPort;
  AdsVersion     Version;
  AdsVersion     *pVersion = &Version;
  char           pDevName[50];
  AmsAddr        Addr;
  PAmsAddr       pAddr = &Addr;

  // Open communication port for local PLC (Run-time system 1)
  nPort = AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = AMSPORT_R0_PLC_RTS1;

  nErr = AdsSyncReadDeviceInfoReq(pAddr, pDevName, pVersion);
  if (nErr)
    cerr << "Error: AdsSyncReadDeviceInfoReq: " << nErr << '\n';
  else
  {
    cout << "Name: " << pDevName << '\n';
    cout << "Version: " << (int)pVersion->version << '\n';
    cout << "Revision: " << (int)pVersion->revision << '\n';
    cout << "Build: " << pVersion->build << '\n';
  }
  cout.flush();
  getch();

  // Close communication port
  nErr = AdsPortClose();
  if (nErr) cerr << "Error: AdsPortClose: " << nErr << '\n';
}

打开示例程序 '读取ADS信息'


Start/stop PLC

TwinCAT ADS-DLL: Examples

启动/停止PLC

下面的程序是用来启动或停止PLC中的run-time系统1

#include <iostream.h>
#include <windows.h>
#include <conio.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void main()
{
  USHORT      nAdsState;
  USHORT      nDeviceState = 0;
  long        nErr, nPort;
  int         ch;
  void        *pData = NULL;
  AmsAddr     Addr;
  PAmsAddr    pAddr = &Addr;

  // Open communication port for local PLC (Run-time system 1)
  nPort = AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = AMSPORT_R0_PLC_RTS1;

  cout << "(R) -> PLC Run\n";
  cout << "(S) -> PLC Stop\n";
  cout.flush();
  ch = getch();
  ch = toupper(ch);
  while ( (ch == 'R') || (ch == 'S') )
  {
    switch (ch)
    {
      case 'R':
        nAdsState = ADSSTATE_RUN;
        break;
      case 'S':
        nAdsState = ADSSTATE_STOP;
        break;
    }
    nErr = AdsSyncWriteControlReq (pAddr, nAdsState, nDeviceState, 0, pData);
    if (nErr) cerr << "Error: AdsSyncWriteControlReq: " << nErr << '\n';
    ch = getch();
    ch = toupper(ch);
  }

  // Close communication port
  nErr = AdsPortClose();
  if (nErr) cerr << "Error: AdsPortClose: " << nErr << '\n';
}

打开示例程序'启动/停止PLC'.


Access an array in the PLC

TwinCAT ADS-DLL: Examples

访问PLC中的数组

位于PLC中的数组将通过读取命令来进行读取。变量是通过它的变量名来定位的。这个程序和读取离变量有一点区别。数组的长度可以作为函数AdsSyncReadReq()的长度。把变量名作为数组的首地址。

#include <iostream.h>
#include <windows.h>
#include <conio.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void main()
{
  long          nErr, nPort;
  AmsAddr       Addr;
  PAmsAddr      pAddr = &Addr;
  unsigned long lHdlVar;
  int           nIndex;
  short         Data[10];
  char          szVar []={"MAIN.PLCVar"};

  // Open communication port for local PLC (Run-time system 1)
  nPort = AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = AMSPORT_R0_PLC_RTS1;

  // Fetch handle for the PLC variable
  nErr = AdsSyncReadWriteReq(pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(lHdlVar), &lHdlVar, sizeof(szVar), szVar);
  if (nErr) cerr << "Error: AdsSyncReadWriteReq: " << nErr << '\n';
  // Read values of the PLC variables (by handle)
  nErr = AdsSyncReadReq(pAddr, ADSIGRP_SYM_VALBYHND, lHdlVar, sizeof(Data), &Data[0]);
  if (nErr)
    cerr << "Error: AdsSyncReadReq: " << nErr << '\n';
  else
  {
    for (nIndex = 0; nIndex < 10; nIndex++)
      cout << "Data[" << nIndex << "]: " << Data[nIndex] << '\n';
  }
  cout.flush();
  getch();
  // Close communication port
  nErr = AdsPortClose();
  if (nErr) cerr << "Error: AdsPortClose: " << nErr << '\n';
}

打开示例程序 '访问PLC中的数组'


Event driven reading

TwinCAT ADS-DLL: Examples

事件驱动方式读取

如果使用函数AdsSyncReadReq()连续读取来自于PLC或NC的数据,则程序的运行效率会很低, 因为该函数必须被循环调用.除了上述方法外,我们还可以通过定义消息处理 (messages)读取来自于PLC或NC的数据. 两种方法的区别在于TwinCAT服务器循环地传输数据还是仅仅在数据发生变化时才开始传输数据.

消息处理是从函数 AdsSyncAddDeviceNotificationReq() 开始的. 然后回调函数会自动地被TwinCAT所调用。 AdsSyncDelDeviceNotificationReq() 是用来停止再次通知的.因为消息处理命令是有限的，你必须确保那些你程序用不着的消息处理已经删除掉了。你可以通过AdsNotificationAttrib结构的描述来获取更多的信息。

以下程序在PLC中在双字0的标志上启动消息处理，每次PLC变量的改变就会引起回调函数的调用。回调函数接收AdsNotificationHeader()类型的变量作为它的擦鞋女书。这个结构包含所有的必需的信息（值，时间，标志。。。。）。

#include <iostream.h>
#include <windows.h>
#include <conio.h>
#include <winbase.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void Callback(AmsAddr*, AdsNotificationHeader*, unsigned long);

void main()
{
  long                   nErr, nPort;
  AmsAddr                Addr;
  PAmsAddr               pAddr = &Addr;
  ULONG                  hNotification, hUser;
  AdsNotificationAttrib  adsNotificationAttrib;

  // Open communication port for local PLC (Run-time system 1)
  nPort = AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = AMSPORT_R0_PLC_RTS1;

  // Specify attributes of the notification
  adsNotificationAttrib.cbLength = 4;
  adsNotificationAttrib.nTransMode = ADSTRANS_SERVERONCHA;
  adsNotificationAttrib.nMaxDelay = 20000000; // 2sec
  adsNotificationAttrib.nCycleTime = 10000000; // 1sec

  // 32-bit variable (including pointers) that are passed to the callback function
  hUser = 3474573467;

  // Start transmission of the PLC variables
  nErr = AdsSyncAddDeviceNotificationReq(pAddr, 0x4020, 0, &adsNotificationAttrib, Callback, hUser, &hNotification);
  if (nErr) cerr << "Error: AdsSyncAddDeviceNotificationReq: " << nErr << '\n';
  cout << "Notification: " << hNotification << "\n\n";
  cout.flush();

  // Wait for a key-press from the user
  getch();

  // Finish transmission of the PLC variables
  nErr = AdsSyncDelDeviceNotificationReq(pAddr, hNotification);
  if (nErr) cerr << "Error: AdsSyncDelDeviceNotificationReq: " << nErr << '\n';

  // Close communication port
  nErr = AdsPortClose();
  if (nErr) cerr << "Error: AdsPortClose: " << nErr << '\n';
}

// Callback function
void 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 << ". Call:\n";

  // Output value of the variable
  cout << "Value: " << *(ULONG *)pNotification->data << '\n';
  cout << "Notification: " << pNotification->hNotification << '\n';

  // Convert time stamp to SYSTEMTIME
  LargeInteger.QuadPart = pNotification->nTimeStamp;
  FileTime.dwLowDateTime = (DWORD)LargeInteger.LowPart;
  FileTime.dwHighDateTime = (DWORD)LargeInteger.HighPart;
  FileTimeToSystemTime(&FileTime, &SystemTime);

  // Calculate time in local representation
  GetTimeZoneInformation(&TimeZoneInformation);
  SystemTimeToTzSpecificLocalTime(&TimeZoneInformation, &SystemTime, &LocalTime);

  // Output time stamp
  cout << LocalTime.wHour << ":" << LocalTime.wMinute << ":" << LocalTime.wSecond << '.' << LocalTime.wMilliseconds <<
       " den: " << LocalTime.wDay << '.' << LocalTime.wMonth << '.' << LocalTime.wYear << '\n';

  // Size of buffer in bytes
  cout << "SampleSize: " << pNotification->cbSampleSize << '\n';

  // 32-bit variable (including pointers) that are set by AddNotification // (See main)
  cout << "hUser: " << hUser << '\n';

  // Output ADS address of the sender
  cout << "ServerNetId: ";
  for (nIndex = 0; nIndex < 6; nIndex++)
    cout << (int)pAddr->netId.b[nIndex] << ".";
  cout << "\nPort: " << pAddr->port << "\n\n";
  cout.flush();
}

打开示例程序'事件驱动方式读取' .


Access by variable name

TwinCAT ADS-DLL: Examples

通过变量名访问

下面的程序在没有地址的情况下访问PLC。所以访问就必须通过变量名。一旦PLC变量超过10则会被重置到0。

所有对ADS设备外部有用的数据都是通过IndexGroups和IndexOffset来组织的。一个IndexGroup可以理解成一个每个入口通过IndexOffset来写地址的表格。TwinCAT PLC的IndexGroups储存了包含输入/输出或者标志区域的量。IndexGroups对于通过系统函数赋予地址的TwinCAT PLC是有用的。

IndexGroups ADSIGRP_SYM_HNDBYNAME 和ADSIGRP_ SYM_VALBYHND 对于示例程序是非常重要的。 IndexGroup ADSIGRP_SYM_HNDBYNAME 是用来从一个通过名字识别的PLC变量请求一个句柄。该变量可以在这个句柄和IndexGroup ADSIGRP_SYM_VALBYHND帮助下进行访问。变量的句柄将通过IndexOffset来传递。

#include <iostream.h>
#include <windows.h>
#include <conio.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void main()
{
  long      nErr, nPort;
  AmsAddr   Addr;
  PAmsAddr  pAddr = &Addr;
  ULONG     lHdlVar, nData;
  char      szVar []={"MAIN.PLCVar"};

  // Open communication port for local PLC (Run-time system 1)
  nPort = AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = AMSPORT_R0_PLC_RTS1;

  // Fetch handle for an <szVar> PLC variable
  nErr = AdsSyncReadWriteReq(pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(lHdlVar), &lHdlVar, sizeof(szVar), szVar);
  if (nErr) cerr << "Error: AdsSyncReadWriteReq: " << nErr << '\n';
  do
  {
    // Read value of a PLC variable (by handle)
    nErr = AdsSyncReadReq( pAddr, ADSIGRP_SYM_VALBYHND, lHdlVar, sizeof(nData), &nData );
    if (nErr)
      cerr << "Fehler: AdsSyncReadReq: " << nErr << '\n';
    else
      cout << "Wert: " << nData << '\n';
    cout.flush();
    if (Data > 10)
    {
      // Reset the value of the PLC variable to 0
      nData = 0;
      nErr = AdsSyncWriteReq(pAddr, ADSIGRP_SYM_VALBYHND, lHdlVar, sizeof(nData), &nData);
      if (nErr) cerr << "Error: AdsSyncWriteReq: " << nErr << '\n';
    }
  }
  while ( getch() == '\r');  // nächsten Wert auslesen mit RETURN, sonst Ende

  // Close communication port
  nErr = AdsPortClose();
  if (nErr) cerr << "Error: AdsPortClose: " << nErr << '\n';
}

打开示例程序 '通过变量名访问'


Read PLC variable declaration

TwinCAT ADS-DLL: Examples

读取PLC变量声明

读取PLC中声明的变量时,包含以下信息:

  • Variable name
  • Data type
  • Length
  • Address (IndexGroup / IndexOffset)
  • Comment

所有上述列出的信息在数据流中传递 . 在这些信息被读取前 , 第一个AdsSyncReadReq()函数用来获取变量的长度. 数据本身通过第二个AdsSyncReadReq()被传递. pchSymbols 变量是一个指针, 用来指向PLC中的定义的变量. FOR-loop 将相应的数据区域拷贝至pAdsSymbolEntry结构 . pAdsSymbolEntry结构存储了PLC变量的相关信息信息. 宏命令PADSSYMBOLNAME, PADSSYMBOLTYPE 和PADSSYMBOLCOMMENT 简化了这些数据的读取过程.

#include <iostream.h>
#include <windows.h>
#include <conio.h>
#include <assert.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void main()
{
  long                  nErr, nPort;
  char                  *pchSymbols = NULL;
  UINT                  uiIndex;
  AmsAddr               Addr;
  PAmsAddr              pAddr = &Addr;
  AdsSymbolUploadInfo   tAdsSymbolUploadInfo;
  PAdsSymbolEntry       pAdsSymbolEntry;

  // Open communication port for local PLC (Run-time system 1)
  nPort = AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = AMSPORT_R0_PLC_RTS1;

  // Read the length of the variable declaration
  nErr = AdsSyncReadReq(pAddr, ADSIGRP_SYM_UPLOADINFO, 0x0, sizeof(tAdsSymbolUploadInfo), &tAdsSymbolUploadInfo);
  if (nErr) cerr << "Error: AdsSyncReadReq: " << nErr << '\n';
  pchSymbols = new char[tAdsSymbolUploadInfo.nSymSize];
  assert(pchSymbols);

  // Read information about the PLC variables
  nErr = AdsSyncReadReq(pAddr, ADSIGRP_SYM_UPLOAD, 0, tAdsSymbolUploadInfo.nSymSize, pchSymbols);
  if (nErr) cerr << "Error: AdsSyncReadReq: " << nErr << '\n';

  // Output information about the PLC variables
  pAdsSymbolEntry = (PAdsSymbolEntry)pchSymbols;
  for (uiIndex = 0; uiIndex < tAdsSymbolUploadInfo.nSymbols; uiIndex++)
  {
    cout << PADSSYMBOLNAME(pAdsSymbolEntry) << "\t\t"
         << pAdsSymbolEntry->iGroup << '\t'
         << pAdsSymbolEntry->iOffs << '\t'
         << pAdsSymbolEntry->size << '\t'
         << PADSSYMBOLTYPE(pAdsSymbolEntry) << '\t'
         << PADSSYMBOLCOMMENT(pAdsSymbolEntry) << '\n';
    pAdsSymbolEntry = PADSNEXTSYMBOLENTRY(pAdsSymbolEntry); cout.flush();
  }
  getch();

  // Close communication port
  nErr = AdsPortClose();
  if (nErr) cerr << "Fehler: AdsPortClose: " << nErr << '\n';

  // Release memory
  if (pchSymbols) delete(pchSymbols);
}

打开示例程序 '读取PLC变量声明'


Detect status change in TwinCAT router and the PLC

TwinCAT ADS-DLL: Examples

检测TwinCAT路由器和PLC的状态变化

当一个应用程序正在运行时, 实时监控TwinCAT 和/或者TwinCAT组件的状态是一件很重要的工作; 例如：PLC是否在运行状态。为了避免重复发布这个请求，在回调函数的帮助下可以非常有效的检测到状态变化。

下列程序监控PLC（run-time1）和TwinCAT路由器的状态。

通过调用AdsAmsRegisterRouterNotification()函数，当TwinCAT路由器的状态每次发生改变的情况下就调用回调函数。

AdsSyncAddDeviceNotificationReq()是用来监控PLC的状态，传输给回调函数的数据代表了当前的PLC状态。

#include <iostream.h>
#include <conio.h>
#include <windows.h>
#include <winbase.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void Callback(AmsAddr*, AdsNotificationHeader*, ULONG);
void RouterCall(LONG);

void main()
{
  LONG                    nErr, nPort;
  ULONG                   hNotification, hUser = 0;
  AmsAddr                 Addr;
  PAmsAddr                pAddr = &Addr;
  AdsNotificationAttrib   adsNotificationAttrib;

  nPort = AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = AMSPORT_R0_PLC_RTS1;

  nErr = AdsAmsRegisterRouterNotification(&RouterCall);
  if (nErr) cerr << "Error: AdsAmsRegisterRouterNotification: " << nErr << '\n';

  // Invoke notification
  adsNotificationAttrib.cbLength       = sizeof(short);
  adsNotificationAttrib.nTransMode     = ADSTRANS_SERVERONCHA;
  adsNotificationAttrib.nMaxDelay      = 0; // jede Aenderung sofort melden
  adsNotificationAttrib.dwChangeFilter = 0; //
  nErr = AdsSyncAddDeviceNotificationReq(pAddr, ADSIGRP_DEVICE_DATA, ADSIOFFS_DEVDATA_ADSSTATE, &adsNotificationAttrib, Callback, hUser, &hNotification);
  if (nErr) cerr << "Error: AdsSyncAddDeviceNotificationReq: " << nErr << "\n";
  getch();

  // The following calls return errors if TwinCAT is halted
  nErr = AdsSyncDelDeviceNotificationReq(pAddr, hNotification);
  if (nErr) cerr << "Error: AdsSyncDelDeviceNotificationReq: " << nErr << '\n';

  nErr = AdsAmsUnRegisterRouterNotification();
  if (nErr) cerr << "Error: AdsAmsUnRegisterRouterNotification: " << nErr << '\n';

  nErr = AdsPortClose();
  if (nErr) cerr << "Error: AdsPortClose: " << nErr << '\n';

  return;
}

// ADS state callback function
void Callback(AmsAddr* pAddr, AdsNotificationHeader* pNotification, ULONG hUser)
{
  INT       nIndex;
  nIndex = *(short *)pNotification->data;
  switch (nIndex)
  {
  case ADSSTATE_RUN:
    cout << "PLC run\n";
    break;
  case ADSSTATE_STOP:
    cout << "PLC stop\n";
    break;
  default :
    cout << "PLC ADS-State" << nIndex << "\n";
    break;
  }
  cout.flush ();
}

// TwinCAT router callback function
void RouterCall (long nReason)
{
  switch (nReason)
  {
  case AMSEVENT_ROUTERSTOP:
    cout << "TwinCAT-Router stop\n";
    break;
  case AMSEVENT_ROUTERSTART:
    cout << "TwinCAT-Router start\n";
    break;
  case AMSEVENT_ROUTERREMOVED:
    cout << "TwinCAT-Router removed\n";
    break;
  default:
    cout << "TwinCAT-Router AMS-Event " << nReason << "\n";
    break;
  }
  cout.flush ();
}

打开示例程序 '检测TwinCAT路由器和PLC的状态变化'


Delphi: Synchronous access to PLC variables

TwinCAT ADS-DLL: Examples

Delphi: 同步访问PLC变量

ADS DLL中最重要的函数移植到Pascal中，并且都集中到Ads. pas单元中。打开示例程序 '同步访问PLC变量'

任务The task

同步读写访问PLC变量是Windows应用程序所需要的。变量是16为的整形，并且位于PLC的run-time1系统的标志区域的偏移量520.

PLC程序

当PLC运行时PLC变量的初始化为7.

PROGRAM MAIN
VAR
VARINT16 AT%MB520 :INT:=7;
END_VAR

说明

Ads DLL函数的声明位于Pascal单元的Ads.pas。这些会通过用户条款连接到工程项目中。

unit DelphiAdsDLLSample1Unit;

interface
uses
Ads, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, ComCtrls;

Ads.pas单元

unit ADS;

interface
uses sysutils,windows;

const
ADSIGRP_SYM_HNDBYNAME =$0000F003;
ADSIGRP_SYM_VALBYHND =$0000F005;
ADSIGRP_RELEASE_SYMHND =$0000F006;

type
PWORD=^WORD;
PDWORD=^DWORD;
PLONGINT=^longint;

TAmsNetId=record
b:array[0..5] of Byte;
end;
Type PAmsNetId=^TAmsNetId;

TAmsAddr=record
netId :TAmsNetId;
port :WORD;
end;
Type PAmsAddr=^TAmsAddr;

TAdsVersion=record
version:BYTE;
revision:BYTE;
build:WORD;
end;
Type PAdsVersion=^TAdsVersion;

{AdsDLL Prototypes}
function AdsGetDllVersion:longint; cdecl; external 'AdsDll.dll' name
'AdsGetDllVersion';
function AdsPortOpen:longint; cdecl; external 'AdsDll.dll' name 'AdsPortOpen';
function AdsPortClose:longint;cdecl; external 'AdsDll.dll' name 'AdsPortClose';
function AdsGetLocalAddress( pAddr:PAmsAddr ):longint; cdecl; external
'AdsDll.dll' name 'AdsGetLocalAddress';
function AdsSyncWriteReq( pAddr:PAmsAddr; indexGroup, indexOffset, len:Longword;
pData:Pointer ):longint;cdecl; external 'AdsDll.dll' name 'AdsSyncWriteReq';
function AdsSyncReadReq( pAddr:PAmsAddr; indexGroup, indexOffset, len:Longword;
pData:Pointer ):longint;cdecl; external 'AdsDll.dll' name 'AdsSyncReadReq';
function AdsSyncReadWriteReq( pAddr:PAmsAddr; indexGroup, indexOffset,
rlen:Longword; prData:Pointer; wlen:Longword; pwData:Pointer ):longint; cdecl;
external 'AdsDll.dll' name 'AdsSyncReadWriteReq';

implementation
initialization
finalization

end.

更多的DLL函数能够以类似的方式连接。

例子运用

DLL函数AdsPortOpen是在FormCreate事件函数中调用。这个函数在成功后会返回一个新端口号，否则则返回一个空值。如果发生任何的错误，它们会通过信息箱报告给用户。当应用程序结束后则必须再次关闭端口。

DLL函数 AdsPortClose 是在 FormDestroy 事件函数中调用.如果Ads端口成功打开的话，DLL函数会进一步调用AdsGetLocalAddress。这个函数返回一个本地的TwinCAT PC 的AMS address。AMS address是用来读/写访问PLC变量素所必需 的。

var
Form1         : TForm1;
ClientPort    : longint;
LocalAddr     : TAmsAddr;
IndexGroup    : Integer;
IndexOffset   : Integer;
varHandle     : Integer;
Result        : longint;

const varName: array[0..13] of Char = 'MAIN.VARINT16'; //Symbol name

implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
ClientPort:= AdsPortOpen();
if ClientPort > 0 then {OK}
begin
Result:=AdsGetLocalAddress( @LocalAddr );
if Result = 0 Then  {OK}
begin
AdsState.Panels.Items[5].Text:= Format('%d.%d.%d.%d.%d.%d',[
LocalAddr.netId.b[0], LocalAddr.netId.b[1], LocalAddr.netId.b[2],
LocalAddr.netId.b[3], LocalAddr.netId.b[4], LocalAddr.netId.b[5]]);
AdsState.Panels.Items[1].Text:=IntToStr( ClientPort );
LocalAddr.port := 801;
//Set the Ads port number for read/write/readwrite
IndexGroup := $00004020;
{memory range}
IndexOffset := 520; {byte
offset of the PLC variable}
end
else  {error}
begin
AdsState.Panels.Items[3].Text:=IntToStr( Result );
MessageBox(NULL,'AdsGetLocalAddress()', 'Error!', MB_OK)
end;
end
else  {error}
MessageBox( NULL,'AdsPortOpen()', 'Error!', MB_OK );
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
AdsPortClose();
end;

当点击 AdsSyncRead 按钮,PLC会通过识别的地址同步的读取变量值。这包括在OnSynReadButtonClick事件函数中调用的DLL函数 AdsSyncReadReq。IndexGroup和IndexOffset参数定义为一个全局变量。当应用程序启动时他们的值也就会相应的设置好。

procedure TForm1.OnSyncReadButtonClick(Sender: TObject);
var   varValue      :
Smallint;
begin
Result := AdsSyncReadReq( @LocalAddr, IndexGroup, IndexOffset,
sizeof(varValue), @varValue );
ReadLabel.Caption :='Result:' + IntToStr(Result) +
'   Value:' + IntToStr(varValue);
end;

通过类似的方式可以写变量。在事件函数中调用DLL函数AdsSyncWriteReq，并且PLC变量的值设置到200.

procedure TForm1.OnSyncWriteButtonClick(Sender: TObject);
var   varValue      :
Smallint;
begin
varValue := 200;
Result := AdsSyncWriteReq( @LocalAddr, IndexGroup, IndexOffset,
sizeof(varValue), @varValue );
WriteLabel.Caption := 'Result:' + IntToStr(Result);
end;

同时对PLC变量进行读和写的操作可以在DLL函数AdsSyncReadWriteReq帮助下实行。例子中变量值是通过句柄的方式读取的。变量的句柄首先响应AdsSyncReadWriteReq函数。通过这个句柄，变量的值可以通过AdsSyncReadReq进行读取。句柄可以必须通过AdsSyncWriteReq函数进行释放。

procedure TForm1.OnGetValueByHandleClick(Sender: TObject);
var   varValue      :
Smallint;
begin
{get handle}
Result := AdsSyncReadWriteReq( @LocalAddr, ADSIGRP_SYM_HNDBYNAME, 0, sizeof(varHandle), @varHandle, sizeof(varName),                                                 @varName );
GetHandleLabel.Caption := 'Get handle result:' + IntToStr(Result) + '   Handle:' + IntToStr(varHandle);
{get value}
Result := AdsSyncReadReq( @LocalAddr, ADSIGRP_SYM_VALBYHND, varHandle,
sizeof(varValue), @varValue );
GetValueLabel.Caption := 'Get value result:' + IntToStr(Result) + '   Value:' + IntToStr(varValue);
{release handle}
Result := AdsSyncWriteReq( @LocalAddr, ADSIGRP_RELEASE_SYMHND, 0, sizeof(varHandle), @varHandle );
ReleaseHandleLabel.Caption := 'Release handle result:' + IntToStr(Result);
end;

如果几个PLC变量的值要经常读取的话，则当应用程序在FormCreate事件函数中启动时句柄会得到请求。当应用程序关闭的时候这些句柄才只会在FormDestroy事件函数中释放。

打开示例程序 '同步访问PLC变量'.


Event-Driven Detection of Changes to the Symbol Table

TwinCAT ADS-DLL: Examples

事件驱动方式监控PLC中符号表的改变

ADS设备支持符号名(PLC, NC, ...) . ADS将这些符号名储存于内部表中. 每一个符号都被分配了一个句柄. 符号句柄是必须存在的, 目的是为了访问变量 (也可参考 Example例子 9).

如果符号表改变了,当一个新的PLC程序会被写入控制器, 那些符号句柄也必须再一次被重新确定. 该示例举例说明了如何监控符号表的变化.

#include <iostream.h>
#include <windows.h>
#include <conio.h>
#include <winbase.h>
#include "AdsDef.h"
#include "AdsAPI.h"

void SymbolChanged(AmsAddr*, AdsNotificationHeader*, unsigned long);

void main()
{
  long                   nErr;
  AmsAddr                Addr;
  PAmsAddr               pAddr = &Addr;
  ULONG                  hNotification;
  AdsNotificationAttrib  adsNotificationAttrib;

  // Open communication port for local PLC (Run-time system 1)
  AdsPortOpen();
  nErr = AdsGetLocalAddress(pAddr);
  if (nErr) cerr << "Error: AdsGetLocalAddress: " << nErr << '\n';
  pAddr->port = AMSPORT_R0_PLC_RTS1;

  // Specify attributes of the notification
  adsNotificationAttrib.cbLength = 1;
  adsNotificationAttrib.nTransMode = ADSTRANS_SERVERONCHA;
  adsNotificationAttrib.nMaxDelay = 5000000; // 500ms
  adsNotificationAttrib.nCycleTime = 5000000; // 500ms

  // Start notification for changes to the symbol table
  nErr = AdsSyncAddDeviceNotificationReq(pAddr, ADSIGRP_SYM_VERSION, 0, &adsNotificationAttrib, SymbolChanged, NULL, &hNotification);
  if (nErr) cerr << "Error: AdsSyncAddDeviceNotificationReq: " << nErr << '\n';

  // Wait for a key-press from the user
  getch();

  // Stop notification
  nErr = AdsSyncDelDeviceNotificationReq(pAddr, hNotification);
  if (nErr) cerr << "Error: AdsSyncDelDeviceNotificationReq: " << nErr << '\n';

  // Close communication port
  nErr = AdsPortClose();
  if (nErr) cerr << "Error: AdsPortClose: " << nErr << '\n';
}

// Callback function
void SymbolChanged(AmsAddr* pAddr, AdsNotificationHeader* pNotification, ULONG hUser)
{
  cout << "Symboltabelle hat sich geändert!\n";
  cout.flush();
}

打开示例程序 '事件驱动方式检测PLC中符号表的改变'.


Delphi: Start/stop PLC

TwinCAT ADS-DLL: Beispiele

Delphi: 启动/停止PLC

一下程序启动、停止PLC中的run—time system1-4。打开示例程序'启动/停止PLC'.

示例应用程序：

ADS DLL中最重要的函数移植到了Pasca中并且收集到Ads.pas单元。

unit AdsDLLWriteControlForm;

interface

uses
  Ads, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    StatusBar1: TStatusBar;
    GroupBox1: TGroupBox;
    Start: TButton;
    Stop: TButton;
    RadioGroup1: TRadioGroup;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure StartClick(Sender: TObject);
    procedure StopClick(Sender: TObject);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private declarations }
    LocalAddr     :TAmsAddr;
    ServerAddr    :TAmsAddr;
    AdsResult     :Longint;
    ClientPort    :Word;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation
{$R *.DFM}
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormCreate(Sender: TObject);
begin
  GroupBox1.Caption := 'PLC';
  GroupBox1.Align := alClient;
  RadioGroup1.Caption := 'RTS';
  RadioGroup1.Items.Add('801');
  RadioGroup1.Items.Add('811');
  RadioGroup1.Items.Add('821');
  RadioGroup1.Items.Add('831');
  RadioGroup1.ItemIndex:=0;
  StatusBar1.SimplePanel := true;
  Label1.Caption:='Ads result:';

  ClientPort:= AdsPortOpen();
  if ClientPort = 0 then {error!}
    Application.MessageBox('AdsPortOpen() error!', 'ADS DLL', MB_OK OR MB_ICONERROR)
  else  {OK}
  begin
    AdsResult:=AdsGetLocalAddress( @LocalAddr );
    if AdsResult = 0 then {OK}
    begin
      ServerAddr.netId:=LocalAddr.netId;{connect to the PLC on the local PC}
      ServerAddr.port:=801; {select first RTS}

      StatusBar1.SimpleText:=Format('Client NetId:%d.%d.%d.%d.%d.%d  Client Port:%d',[
              LocalAddr.netId.b[0], LocalAddr.netId.b[1], LocalAddr.netId.b[2],
              LocalAddr.netId.b[3], LocalAddr.netId.b[4], LocalAddr.netId.b[5],
              ClientPort]);
    end
    else
      Application.MessageBox(PChar(Format('AdsGetLocalAddress() error:%d', [AdsResult])), 'ADS DLL', MB_OK OR MB_ICONERROR);
  end;
end;

//////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormDestroy(Sender: TObject);
begin
  AdsResult := AdsPortClose();
  if AdsResult > 0 then
    Application.MessageBox(PChar(Format('AdsPortClose() error:%d', [AdsResult])), 'ADS DLL', MB_OK OR MB_ICONERROR );
end;

//////////////////////////////////////////////////////////////////////////////
procedure TForm1.StartClick(Sender: TObject);
begin
  AdsResult:=AdsSyncWriteControlReq( @ServerAddr, ADSSTATE_RUN, 0, 0, Nil );
  Label1.Caption := Format('AdsSyncWriteControlReq result:%d', [AdsResult]);
end;

//////////////////////////////////////////////////////////////////////////////
procedure TForm1.StopClick(Sender: TObject);
begin
  AdsResult:=AdsSyncWriteControlReq( @ServerAddr, ADSSTATE_STOP, 0, 0, Nil );
  Label1.Caption := Format('AdsSyncWriteControlReq result:%d', [AdsResult]);
end;

//////////////////////////////////////////////////////////////////////////////
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  ServerAddr.port := StrToInt(RadioGroup1.Items[RadioGroup1.ItemIndex]);
end;

end.

打开程序 '启动/停止PLC'.


Delphi: Detect status changes in TwinCAT router and the PLC

TwinCAT ADS-DLL: Examples

Delphi: 检测TwinCAT路由器和PLC中的状态改变

ADS DLL中最重要的函数移植到Pascal中，并且都集中到Ads. pas单元中。这里你可以打开例子程序 。

说明

当一个应用程序正在运行时, 实时监控TwinCAT 和/或者TwinCAT组件的状态是一件很重要的工作; 例如, PLC是否处于运行状态 . 因此所需的状态信息也并不总是通过轮番检查，也有可能在TwinCAT组件中注册消息。这些在回调函数协助下注册一个客户应用程序被赋予地址的状态改变。在下面的程序监测的是PLC和本地的TwinCAT路由器的状态。应用程序只能监测本地TwinCAT路由器的状态。换句话说，远程PC不能采用这种方式进行监测。而PLC的状态通过这个函数既可以在本地电脑进行监测也可以通过远程监测。

启动应用程序的时候TwinCAT路由器就和本地PC建立好了连接。消息处理在按合适的按键时注册到TwinCAT路由器或者PLC run-time system1上。到达的消息处理会被添加到两个列表中，当继续按键时消息处理会在各自的TwinCAT设备上取消。在应用程序的结尾，TwinCAT路由器连接会关闭。在启动/停止TwinCAT系统下通过任务栏，消息处理路由会被传送。在已经注册的设备上，消息处理通过TwinCAT自身的组件执行。正因如此，设备消息处理在TwinCAT系统被停止的时候会再次注册，因此当TwinCAT系统停止的时候这个组件就从内存中删除。

Ads.pas单元

在这个单元中，最重要的DLL函数已经移植到Pascal。这可以作为客户自己的出发点。

......

{notification callback prototype}
TAdsNotificationFunc = Procedure(	pAddr:PAmsAddr; pNotification:PAdsNotificationHeader;	hUser:Longword ); cdecl;
PAdsNotificationFunc=^TAdsNotificationFunc;

{router notification callback prototype}
TAmsRouterNotificationFunc = Procedure( nEvent:Longint ); cdecl;
PAmsRouterNotificationFunc = ^TAmsRouterNotificationFunc;

{AdsDLL Prototypes}
function AdsGetDllVersion:longint; cdecl; external 'AdsDll.dll' name 'AdsGetDllVersion';
function AdsPortOpen:longint; cdecl; external 'AdsDll.dll' name 'AdsPortOpen';
function AdsPortClose:longint;cdecl; external 'AdsDll.dll' name 'AdsPortClose';
function AdsGetLocalAddress( pAddr:PAmsAddr ):longint; cdecl; external 'AdsDll.dll' name 'AdsGetLocalAddress';
function AdsSyncWriteReq( pAddr:PAmsAddr; indexGroup, indexOffset, len:Longword; pData:Pointer ):longint;cdecl; external 'AdsDll.dll' name  'AdsSyncWriteReq';
function AdsSyncReadReq( pAddr:PAmsAddr; indexGroup, indexOffset, len:Longword; pData:Pointer ):longint;cdecl; external 'AdsDll.dll' name 'AdsSyncReadReq';
function AdsSyncReadWriteReq( pAddr:PAmsAddr; indexGroup, indexOffset, rlen:Longword; prData:Pointer; wlen:Longword; pwData:Pointer ):longint; cdecl; external 'AdsDll.dll' name 'AdsSyncReadWriteReq';
function AdsSyncReadDeviceInfoReq( pAddr:PAmsAddr; pDevName:PChar; pVersion:PAdsVersion ):longint;cdecl; external 'AdsDll.dll' name  'AdsSyncReadDeviceInfoReq';
function AdsSyncWriteControlReq( pAddr:PAmsAddr; nAdsState, nDeviceState:Word; nLength:Longword; pData:Pointer ):longint; cdecl; external 'AdsDll.dll' name 'AdsSyncWriteControlReq';
function AdsSyncReadStateReq( pAddr:PAmsAddr; pAdsState, pDeviceState:PWORD ):longint; cdecl; external 'AdsDll.dll' name 'AdsSyncReadStateReq';
function AdsSyncSetTimeout( nMs:longint ):longint; cdecl; external 'AdsDll.dll' name 'AdsSyncSetTimeout';

{device notifications}
function AdsSyncAddDeviceNotificationReq( pAddr:PAmsAddr; indexGroup, indexOffset:Longword; pNoteAttrib:PAdsNotificationAttrib;
                    pNoteFunc:PAdsNotificationFunc;	hUser:Longword; pNotification:PDWORD ):longint; cdecl; external 'AdsDll.dll' name 'AdsSyncAddDeviceNotificationReq';
function AdsSyncDelDeviceNotificationReq( pAddr:PAmsAddr; hNotification:Longword ):longint; cdecl; external 'AdsDll.dll' name 'AdsSyncDelDeviceNotificationReq';

{router notifications}
function AdsAmsRegisterRouterNotification(  pNoteFunc: PAmsRouterNotificationFunc ):Longint;  cdecl; external 'AdsDll.dll' name 'AdsAmsRegisterRouterNotification';
function AdsAmsUnRegisterRouterNotification ():longint; cdecl; external 'AdsDll.dll' name 'AdsAmsUnRegisterRouterNotification';

.....

示例应用程序

DLL函数AdsPortOpen在FormCreate事件函数中调用。当调用成功，这个函数返回一个端口号，否则返回0.如果发生任何错误，则会通过消息框通知用户。如果Ads 端口成功打开，则会进一步调用函数AdsGetLocalAddress。这个函数返回TwinCAT PC的AMS address。为了给第一个运行系统注册消息处理另一个AMS Address就会产生。这就会占用和我们应用程序一样的网络标识，所以我们希望给PLC赋予和TwinCAT PC一样的地址。通过分配端口号801则第一个RTS就被选择好了。这个端口必须在应用程序结束的时候关闭。DLL函数AdsPortClose在FormDestroy事件函数中调用。

unit AdsDLLEventSampleForm;

interface

uses
  Ads, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Math,
  StdCtrls, ComCtrls;

const WM_ADSROUTERNOTIFICATION = WM_APP + 400;
const WM_ADSDEVICENOTIFICATION = WM_APP + 401;

type
  TForm1 = class(TForm)
    StatusBar1: TStatusBar;
    GroupBox1: TGroupBox;
    RegRouterNotify: TButton;
    UnregRouterNotify: TButton;
    ListBox1: TListBox;
    GroupBox2: TGroupBox;
    AddDevNotify: TButton;
    DelDevNotify: TButton;
    ListBox2: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure RegRouterNotifyClick(Sender: TObject);
    procedure UnregRouterNotifyClick(Sender: TObject);
    procedure AddDevNotifyClick(Sender: TObject);
    procedure DelDevNotifyClick(Sender: TObject);

    procedure WMAdsRouterNotification( var Message: TMessage ); message WM_ADSROUTERNOTIFICATION;
    procedure WMAdsDeviceNotification( var Message: TMessage ); message WM_ADSDEVICENOTIFICATION;
  private
    { Private declarations }
    LocalAddr     :TAmsAddr;
    ServerAddr    :TAmsAddr;
    hNotification :DWORD;

  public
    { Public declarations }
  end;

{$ALIGN OFF}
type TThreadListItem = record
     netId           : TAmsNetId;
     port            : Word;
       hNotify         : Longword;
        stamp           : _FILETIME;  {int64}
       cbSize          : Longword;
     hUser           : Longword;
     data            : Byte;    //Array[1..ANYSIZE_ARRAY] of Byte;
     end;
PThreadListItem = ^TThreadListItem;
{$ALIGN ON}

var
  Form1: TForm1;
  wndHandle : HWND;
  DevThreadList : TThreadList;

implementation
{$R *.DFM}
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormCreate(Sender: TObject);
var AdsResult:Longint;
    ClientPort:Word;
begin
  GroupBox1.Caption := 'Router notifications:';
  GroupBox2.Caption := 'Device notifications:';
  StatusBar1.SimplePanel := true;
  wndHandle := Handle;
  DevThreadList := TThreadList.Create();

  ClientPort:= AdsPortOpen();
  if ClientPort = 0 then {error!}
    Application.MessageBox('AdsPortOpen() error!', 'ADS DLL', MB_OK OR MB_ICONERROR)
  else  {OK}
  begin
    AdsResult:=AdsGetLocalAddress( @LocalAddr );
    if AdsResult = 0 then {OK}
    begin
      ServerAddr.netId:=LocalAddr.netId;{connect to the PLC on the local PC}
      ServerAddr.port:=801; {first RTS}

      StatusBar1.SimpleText:=Format('Client NetId:%d.%d.%d.%d.%d.%d  Client Port:%d,  Server Port:%d',[
              LocalAddr.netId.b[0], LocalAddr.netId.b[1], LocalAddr.netId.b[2],
              LocalAddr.netId.b[3], LocalAddr.netId.b[4], LocalAddr.netId.b[5],
              ClientPort, ServerAddr.port]);
    end
    else
      Application.MessageBox(PChar(Format('AdsGetLocalAddress() error:%d', [AdsResult])), 'ADS DLL', MB_OK OR MB_ICONERROR);
  end;
end;
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormDestroy(Sender: TObject);
var AdsResult:longint;
    X : integer;
begin
  UnregRouterNotifyClick( Sender );
  DelDevNotifyClick( Sender );

  With DevThreadList.LockList do
  try
     for X := 0 to Count-1 do
         FreeMem( Items[X], sizeof(TThreadListItem) + PThreadListItem(Items[X])^.cbSize - 1 );
     Clear();
  finally
     DevThreadList.UnlockList;
     DevThreadList.Destroy;
  end;

  AdsResult := AdsPortClose();
  if AdsResult > 0 then
    Application.MessageBox(PChar(Format('AdsPortClose() error:%d', [AdsResult])), 'ADS DLL', MB_OK OR MB_ICONERROR );
end;

注册/取消路由器消息处理

调用函数AdsAmsRegisterRouterNotification()就可以为路由器注册一个消息处理。路由器消息处理只能够被本地的TwinCAT PC注册。作为一个单独的函数参数，函数指针会传送到我们的回调函数。通过调用AdsAmsUnRegisterRouterNotification()则可以取消TwinCAT路由器的消息处理。

procedure TForm1.RegRouterNotifyClick(Sender: TObject);
var AdsResult:longint;
begin
  AdsResult:= AdsAmsRegisterRouterNotification(@AdsRouterCallback);
  if AdsResult > 0 then
    ListBox1.Items.Insert(0, Format('AdsAmsRegisterRouterNotification() error:%d', [AdsResult]));
end;
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.UnregRouterNotifyClick(Sender: TObject);
var AdsResult:longint;
begin
  AdsResult:=AdsAmsUnRegisterRouterNotification();
  if AdsResult > 0 then
    ListBox1.Items.Insert(0, Format('AdsAmsUnRegisterRouterNotification() error:%d', [AdsResult]));
end;

注册/取消设备的消息处理

设备的消息处理只被TwinCAT组件本身所执行。设备的消息处理能够通过远程PC进行注册，目标设备通过AdsAms地址进行选择。消息处理通过调用函数AdsSyncAddDeviceNotificationReq()进行注册。消息的属性必须事先确定好。如果成功，函数会提供一个没错误的消息处理句柄。这个句柄在调用函数AdsSyncDelDeviceNotificationReq()去取消消息处理时会用到。一个应用程序可以同时注册几个消息处理。每个消息处理可以通过消息处理句柄或者用户句柄来识别。这些参数通过回调函数回传给应用程序。

procedure TForm1.AddDevNotifyClick(Sender: TObject);
var AdsResult:Longint;
    huser :Longword;
    adsNotificationAttrib :TadsNotificationAttrib;
begin

  adsNotificationAttrib.cbLength       := sizeof(DWORD);
  adsNotificationAttrib.nTransMode     := ADSTRANS_SERVERONCHA;
  adsNotificationAttrib.nMaxDelay      := 0; // jede Aenderung sofort melden
  adsNotificationAttrib.nCycleTime     := 0; //

  hUser := 7;

  AdsResult:=AdsSyncAddDeviceNotificationReq( @ServerAddr,
                    ADSIGRP_DEVICE_DATA,
                    ADSIOFFS_DEVDATA_ADSSTATE,
                    @adsNotificationAttrib,
                    @AdsDeviceCallback, hUser, @hNotification  );
  if AdsResult > 0 then
    ListBox2.Items.Insert(0, Format('AdsSyncAddDeviceNotificationReq() error:%d', [AdsResult]));
end;

//////////////////////////////////////////////////////////////////////////////
procedure TForm1.DelDevNotifyClick(Sender: TObject);
var AdsResult:Longint;
begin
  AdsResult := AdsSyncDelDeviceNotificationReq( @ServerAddr, hNotification );
  if AdsResult > 0 then
    ListBox2.Items.Insert(0, Format('AdsSyncDelDeviceNotificationReq() error:%d', [AdsResult]));
end;

回调函数

路由器回调和设备消息处理回调的回调函数已经作为程序定义好了。它们同样可以作为函数定义。由于然而C++函数不提供返回参数并且Pascal中一个函数必须提供一个参数，程序被选为回调函数。该函数调用的DLL应该在API函数的帮助下分开。换句话说，它们不能再次在一个回调函数中调用DLL函数。这可能会促发进一步的回调和其他一些情况。T

Procedure AdsDeviceCallback(	pAddr:PAmsAddr; pNotification:PAdsNotificationHeader;	hUser:Longword ); cdecl;
var pItem : PThreadListItem;
begin
    pItem := Nil;
    try
       GetMem( pItem, sizeof(TThreadListItem) + pNotification^.cbSampleSize - 1 );
       pItem^.netId := pAddr^.netId;
       pItem^.port := pAddr^.port;
       pItem^.hNotify := pNotification^.hNotification;
       pItem^.stamp := pNotification^.nTimeStamp;
       pItem^.cbSize := pNotification^.cbSampleSize;
       pItem^.hUser := hUser;
       //copy the notification data
       Move( pNotification^.data, pItem^.data, pNotification^.cbSampleSize);
    finally
        with DevThreadList.LockList do
        try
           Add( pItem );
        finally
           DevThreadList.UnlockList;
           PostMessage(wndHandle, WM_ADSDEVICENOTIFICATION, 0, 0);
        end;
    end;
end;
//////////////////////////////////////////////////////////////////////////////
procedure AdsRouterCallback( nEvent:Longint ); cdecl;
begin
  PostMessage(wndHandle, WM_ADSROUTERNOTIFICATION, nEvent, 0);
end;

procedure TForm1.WMAdsRouterNotification( var Message: TMessage );
var tmpString:String;
begin
  case Message.WParam of
    AMSEVENT_ROUTERSTOP:
      tmpString:='Router STOP!';
    AMSEVENT_ROUTERSTART:
      tmpString:='Router START!';
    AMSEVENT_ROUTERREMOVED:
      tmpString:='Router REMOVED!';
  else
      tmpString:=Format('Unknown state:%d', [Message.WParam]);
  end;
  ListBox1.Items.Insert(0, Format('%s %s', [TimeToStr(time), tmpString]));
  inherited;
end;
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.WMAdsDeviceNotification( var Message: TMessage );
var pItem                   : PThreadListItem;
    X                       : integer;
    FileTime                : _FILETIME;
    SystemTime, LocalTime   : _SYSTEMTIME;
    TimeZoneInformation     : _TIME_ZONE_INFORMATION;
    DateTime                : TDateTime;
    adsState                : Smallint;
    sState                  : String;
    cbData                  : Longword;
begin
    with DevThreadList.LockList do
    try
       for X := 0 to Count-1 do
       begin
           pItem := Items[X];
           {convert file time to local system time}
           FileTime:=pItem^.stamp;
           FileTimeToSystemTime(FileTime, SystemTime);
           GetTimeZoneInformation(TimeZoneInformation);
           SystemTimeToTzSpecificLocalTime(@TimeZoneInformation, SystemTime, LocalTime);
           DateTime:=SystemTimeToDateTime(LocalTime);

           cbData :=Min(pItem^.cbSize, sizeof(adsState));
           System.Move( pItem^.data, adsState, cbData );
           case adsState of
                ADSSTATE_STOP:  sState := 'STOP';
                ADSSTATE_RUN:  sState := 'RUN';
           else
                sState := Format('Other: %d', [adsState]);
           end;

           ListBox2.Items.Add(Format( '%s %d.%d.%d.%d.%d.%d[%d], hNot:0x%x, size:%d, hUser:0x%x, State:%s',
                                     [TimeToStr(DateTime), pItem^.netId.b[0], pItem^.netId.b[1], pItem^.netId.b[2],
                                     pItem^.netId.b[3], pItem^.netId.b[4], pItem^.netId.b[5], pItem^.port,
                                     pItem^.hNotify, pItem^.cbSize, pItem^.hUser,
                                     sState ]));
           FreeMem( pItem, sizeof(TThreadListItem) + pItem^.cbSize - 1 ); //free item memory
       end;
       Clear();
    finally
       DevThreadList.UnlockList;
    end;
    inherited;
end;

打开示例程序 sources relating to the example program.


Borland C++ Builder: Read DLL version

TwinCAT ADS-DLL: Beispiele

Borland C++ Builder: 读取DLL版本

这个程序却定了DLL文件的版本。打开示例程序 'BCB: Read DLL version'。.

应用程序：

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "AdsDllSampleUnit.h"
#include "c:\TwinCAT\ADS Api\AdsDEF.h"
#include "c:\TwinCAT\ADS Api\AdsAPI.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::GetVersionButtonClick(TObject *Sender)
{
    long        nTemp;
    AdsVersion* pDLLVersion;

    nTemp = AdsGetDllVersion();
    pDLLVersion = (AdsVersion *)&nTemp;

    Label1->Caption=Format("Version:%d, revision:%d, build:%d",
    ARRAYOFCONST(((int)pDLLVersion->version, (int)pDLLVersion->revision, (int)pDLLVersion->build)));
}
//---------------------------------------------------------------------------

打开 'BCB: Read DLL version'