- 浏览: 33665 次
- 性别:
- 来自: 古城西安
最新评论
-
悬空90:
cassandra 1.2.5
2台linux集群
100 ...
试用Cassandra,其写效率太差 -
eyesmore:
这个版本的没用过。但从原理上说,你这边配置:<Commi ...
试用Cassandra,其写效率太差 -
pangyi:
zKF43510 写道创意不错,抽个时间写给个apk满足你。
...
有个想法和大家交流下 -
zKF43510:
创意不错,抽个时间写给个apk满足你。
有个想法和大家交流下 -
linliangyi2007:
楼主杯具了,cassandra被你用成残废了。你的配置中see ...
试用Cassandra,其写效率太差
最近采用JNI来实现访问PI和eDNA的组件。
PI和EDNA都是实时数据库,提供C++的API,遂采用JNI来调用这些函数。开发中发现,通过JNI封装的API,无法并发访问实时数据库,必须在api上加上同步。这样导致访问性能很低。
像Oracle等数据库的JDBC驱动,在Oracle服务端是不是也是采用JNI来实现的?
查了大量的资料,有用的实在寥寥无几。
以下对PI的API封装类源码:
有些方法不知道在如何实现,遂采用JNative代理库实现的。
与大伙一起探讨探讨。
这些资料,咱也看过。
但具体操作起来,一头雾水啊。
可否给出代码例示。
PI和EDNA都是实时数据库,提供C++的API,遂采用JNI来调用这些函数。开发中发现,通过JNI封装的API,无法并发访问实时数据库,必须在api上加上同步。这样导致访问性能很低。
像Oracle等数据库的JDBC驱动,在Oracle服务端是不是也是采用JNI来实现的?
查了大量的资料,有用的实在寥寥无几。
以下对PI的API封装类源码:
import java.util.Date; import org.xvolks.jnative.JNative; import org.xvolks.jnative.Type; import org.xvolks.jnative.exceptions.NativeException; import org.xvolks.jnative.pointers.Pointer; import org.xvolks.jnative.pointers.memory.HeapMemoryBlock; import com.hrnt.rdbc.exception.RealDBException; import com.hrnt.util.DateUtil; /** * 实现PI数据库的部分API * * @author bruce * */ public class PIAPI { /** * */ private PIAPI() { } private static PIAPI _instance = new PIAPI(); public static PIAPI getInstance() { return _instance; } public static final int ARCCOUNT = 149000; public static final int ARCTOTAL = 0;// 累计值 public static final int ARCMINNUM = 1;// 最小值 public static final int ARCMAXNUM = 2;// 最大值 public static final int ARCCSTDEV = 3;// public static final int ARCRANGE = 4;// 随机范围 public static final int ARCAVERAGE = 5;// 时间加权平均值 public static final int ARCMEAN = 6;// 算术平均值 // 获取瞬时值的mode public static final int ARCVALUEBEFORE = 1; public static final int ARCVALUEAFTER = 2; public static final int ARCVALUEINTERP = 3; public static final int ARCVALUECODE = 4; // 操作状态 public static final long STAT_SUC = 0;// 成功 public static final long STAT_120 = -120; public static final long STAT_1 = -1; public static final long STAT_105 = -105; public static final long STAT_106 = -106; // 获得digStart的错误标示 public static final int DIG_START_ERR = -10; // 数据百分比 public static final float PACGOOD = 0.85f; // 系统错误状态最小值 public static final int PI_SYSERR_MINNUM = -314; public static final int PI_SYSERR_MAXNUM = -1; /** * 设置服务器地址 * * @author bruce * @param 服务器地址或者名称 * @return 返回操作状态,0为成功 */ public synchronized native int piut_setservernode(String servername); /** * 关闭连接 * * @author bruce * @return 是否关闭成功,0为成功 */ public synchronized native int piut_disconnect(); /** * 登录PI数据库 * * @author bruce * @param 用户 * @param 密码 * @param 登录模式 * 1为只读,2为可读可写 * @return 登录是否成功,0为登录成功 */ public synchronized native int piut_login(String username, String passwd, int[] valid); /** * 获得标签的pointId * * @author bruce * @param 标签名称 * @param 标签Id(输入输出参数) * @return 是否成功,0为成功状态 */ public synchronized native int pipt_findpoint(String tagname, int[] tagpoint); /** * @author brucepang * @param sum * 参数对象 * @return 0 成功,非0失败 */ public synchronized native int piar_summary(PIAR_Summary sum); /** * 获得标签的类型 * * @author bruce * @param 测点在实时数据库的Id * @param 测点类型(返回R是模拟量 * I是开关量 D是数字状态 ) * @return 如果空 * @throws RealDBException */ public String pipt_pointtype(int pt) throws RealDBException { JNative j = null; try { j = new JNative("piapi32.dll", "pipt_pointtype"); j.setRetVal(Type.INT); j.setParameter(0, pt); Pointer p = new Pointer(new HeapMemoryBlock(1024)); j.setParameter(1, p); j.invoke(); int isSuc = j.getRetValAsInt(); if (isSuc == 0) { return p.getAsString(); } else if (isSuc == -1) { throw new RealDBException("Point does not exist"); } else { throw new RealDBException("System error:" + isSuc); } } catch (NativeException e) { throw new RealDBException(e); } catch (IllegalAccessException e) { throw new RealDBException(e); } } /** * 获得某个标签的实时数据 * * @author bruce * @param 标签Id * @param 值数 * @param 状态 * @param 时间 * @return 0为成功,-1是标签不存在,>0是系统错误 */ public synchronized native int pisn_getsnapshot(PISN_Getsnapshot snap); /** * 获得某个标签的在一段时间的历史数据 * * @author bruce * @param 标签Id * @param 要获得的数量 * @param 时间段 * 在传入时times[0]为开始时间,times[count-1]是结束时间。在返回时是返回的rvals[]值对应的时间数组 * @param 值数组 * @param 状态数组 * @param 0代表向前搜索,非0代表向后搜索 * @return 0为成功,>0是系统错误,-1是错误的pt,-101是开始时间小于结束时间,-103是该测点在这段时间没有数据,-105是时间格式错误 * -121是错误的count参数,-996是消息负载超出PINET协议,-998是内存地址出错 */ public synchronized native int piar_compvalues(PIAR_Compvalues v); /** * 根据digcode来获取digstate * * @author bruce * @param digcode * 系统状态标示 * @param digstate * 系统状态字符串(输入输出参数) * @param len * 返回的digstate字符串长度 * @return 0为成功,>0是系统错误,-11是digcode out of range * @throws RealDBException */ // public synchronized native int pipt_digstate(PIPT_Digstate state); public String pipt_digstate(int digCode) throws RealDBException { JNative j = null; try { j = new JNative("piapi32.dll", "pipt_digstate"); j.setRetVal(Type.INT); j.setParameter(0, digCode); Pointer p = new Pointer(new HeapMemoryBlock(1024)); j.setParameter(1, p); j.setParameter(2, 12); j.invoke(); int isSuc = j.getRetValAsInt(); if (isSuc == 0) { return p.getAsString(); } else if (isSuc == -11) { throw new RealDBException("Digital state code out of range"); } else { throw new RealDBException("System error:" + isSuc); } } catch (NativeException e) { throw new RealDBException(e); } catch (IllegalAccessException e) { throw new RealDBException(e); } } /** * 获取测点的瞬时值 * * @author bruce * @param 标签 * @param 时间戳(输入时是要获得数据的时间)(输出时是数据库中该数据的实际时间) * @param * 获取数据的模式(1是数据时间在要求时间的之前)(2是数据时间在要求时间之后)(3是精确时间如果没有则内插值)(4是综合1、2、3的结果) * @param 值 * @param 状态 * @return 0是成功,>0是系统错误,-1是错误的pt,-101是非在线时间,-103是没有数据,-105是错误的时间格式 */ public synchronized native int piar_value(PIAR_Value pv); /** * 将PI时间转化为常规时间数组 timearray[0] month (1-12) timearray[1] day (1-31) * timearray[2] year (four digit) timearray[3] hour (0-23) timearray[4] min * (0-59) timearray[5] sec (0-59) */ public synchronized native void pitm_secint(Pitmsecint tm); /** * 将时间数组转化为PI识别的时间格式 timearray [0] month (1-12) timearray [1] day (1-31) * timearray [2] year (four digit) timearray [3] hour (0-23) timearray [4] * minute (0-59) timearray [5] second (0-59) * * @return 返回經轉化的時間數值 t */ public synchronized native void pitm_intsec(PITM_Intsec pic); /** * 获取系统错误的状态码信息 * * @throws RealDBException */ public String piut_strerror(int stat) throws RealDBException { JNative j = null; try { j = new JNative("piapi32.dll", "piut_strerror"); j.setRetVal(Type.INT); j.setParameter(0, stat); Pointer p = new Pointer(new HeapMemoryBlock(1024)); j.setParameter(1, p); Pointer p2 = new Pointer(new HeapMemoryBlock(1024)); p2.setIntAt(0, 100); j.setParameter(2, p2); Pointer p3 = new Pointer(new HeapMemoryBlock(1024)); j.setParameter(3, p3); j.invoke(); int isSuc = j.getRetValAsInt(); if (isSuc == 0) { return p.getAsString(); } else if (isSuc == 100) { throw new RealDBException( "No more messages for this errornumber.(PI_NOMOREVALUES)"); } else if (isSuc == -411) { throw new RealDBException("String truncated"); } else if (isSuc == -993) { throw new RealDBException( "Length specified for buffer is too small"); } else if (isSuc == -10007) { throw new RealDBException("Null pointer passed for arguments"); } else { throw new RealDBException("System error:" + isSuc); } } catch (NativeException e) { throw new RealDBException(e); } catch (IllegalAccessException e) { throw new RealDBException(e); } } /** * 将普通时间转化为PI的时间 将这里改造成不需要PI来解析的方式 */ public int getPITimestamp(Date date) { long time = (date.getTime() + 8 * 3600 * 1000) / 1000; return (int) time; } /** * 计算开关量状态的起点 * * @author bruce * @param pt * 测点Id * @param digcode * 状态 * @param 该开关量的digcode个数 * @return 0代表成功,-1代表pt不存在, */ public synchronized native int pipt_digpointers(PIPT_Digpointers dg); /** * 得到时间函数 * * @param reltime * @return */ public Date getTimedate(int time) { long t = time; t = t * 1000 - (8 * 3600 * 1000); return new Date(t); } static { System.loadLibrary("pijni"); } /** * 写入实时值或历史值 * * @author brucepang * @param pt * 测点Id * @param rval * 数值 * @param istat * 状态 * @param timedate * PI的时间量 * @return 成功返回0,失败则返回非0 */ public synchronized native int pisn_putsnapshot(PISN_Putsnapshot para); /** * 组合写入数据。该函数是组合函数。使用请注意参数说明 可以写入开关量、整型量、实型量以及字符串等格式的数据。 * * @author brucepang * @param ptnum * 测点Id * @param drval * 浮点型数值,如果要写入该数组,bval必须为NULL * @param ival * 整型数值,如果要写入该数组,drval和bval必须为NULL * @param bval * 字节数组,如果为NON-NULL,则写入PI数据库 * @param bsize * 字节数组长度。可以不设,PI会调用C的strlen来判断每个字符串的长度 * @param istat * 开关量的状态值 * @param flags * 数据质量标识数组 * @param timestamp * 时间 */ public synchronized native int pisn_putsnapshotx(int ptnum, int year, int month, int day, int hour, int minute, int second, byte[] str); public int pisn_putsnapshotx2(PISN_Putsnapshotx para) throws RealDBException { JNative j = null; try { j = new JNative("piapi32.dll", "pisn_putsnapshotx"); j.setRetVal(Type.INT); j.setParameter(0, para.getPtnum()); Pointer p = new Pointer(new HeapMemoryBlock(1024)); p.setFloatAt(0, para.getDrval() == null ? 0 : para.getDrval()[0]); j.setParameter(1, p); Pointer p2 = new Pointer(new HeapMemoryBlock(1024)); p.setIntAt(0, para.getIval() == null ? 0 : para.getIval()[0]); j.setParameter(2, p2); Pointer p3 = new Pointer(new HeapMemoryBlock(1024)); p.setByteAt(0, para.getBval() == null ? null : para.getBval()[0]); j.setParameter(3, p3); Pointer p4 = new Pointer(new HeapMemoryBlock(1024)); p .setShortAt(0, para.getBsize() == null ? null : para .getBsize()[0]); j.setParameter(4, p4); Pointer p5 = new Pointer(new HeapMemoryBlock(1024)); p.setIntAt(0, para.getIstat() == null ? 0 : para.getIstat()[0]); j.setParameter(5, p5); Pointer p6 = new Pointer(new HeapMemoryBlock(1024)); p2.setIntAt(0, para.getFlags() == null ? null : para.getFlags()[0]); j.setParameter(6, p6); Pointer p7 = new Pointer(new HeapMemoryBlock(1024)); p2.setIntAt(0, para.getTimestamp() == null ? null : para .getTimestamp()[0]); j.setParameter(7, p7); j.invoke(); int ret = j.getRetValAsInt(); System.out.println("返回状态:" + ret); return ret; } catch (NativeException e) { throw new RealDBException(e); } catch (IllegalAccessException e) { throw new RealDBException(e); } } /** * 批量写入实时值或历史值 * * @author brucepang * @param pt * 测点数组 * @param rval * 数值数组 * @param istat * 状态数组 * @param timedate * PI的时间量数组 * @param error * 错误码状态集 * @param count * 写入的测点数据个数 * @return 0代表成功,非0失败 */ public synchronized native int pisn_putsnapshots(int[] pt, float[] rval, int[] istat, int[] timedate, int[] error, int count); public static void main(String args[]) { PIAPI api = new PIAPI(); Date date = new Date(); int time = api.getPITimestamp(date); long time2 = (date.getTime() + 8 * 3600 * 1000) / 1000; System.out.println("time:" + time + " time2:" + time2); int isSuc = api.piut_setservernode("172.16.109.248"); System.out.println("isSuc:" + isSuc); int[] vald = { 2 }; isSuc = api.piut_login("piadmin", "", vald); System.out.println("isSuc:" + isSuc); isSuc = api.pipt_findpoint("CDM158", vald); System.out.println("isSuc:" + isSuc + " pt:" + vald[0]); PIAR_Summary sum = new PIAR_Summary(); sum.setCode(PIAPI.ARCMAXNUM); sum.setEndTime(api.getPITimestamp(DateUtil .parseDate("2008-10-15 10:00:00"))); sum.setStartTime(api.getPITimestamp(DateUtil .parseDate("2008-10-15 00:00:00"))); sum.setPt(vald[0]); isSuc = api.piar_summary(sum); System.out.println("isSuc:" + isSuc + " value:" + sum.getRval() + " pct:" + sum.getPctgood()); String t; try { t = api.pipt_pointtype(vald[0]); System.out.println("isSuc:" + isSuc + " type:" + t); } catch (RealDBException e1) { e1.printStackTrace(); } PISN_Getsnapshot shot = new PISN_Getsnapshot(); shot.setPt(vald[0]); isSuc = api.pisn_getsnapshot(shot); System.out.println("isSuc:" + isSuc + " value:" + shot.getRval() + " time:" + DateUtil.format(api.getTimedate(shot.getTimedate()), DateUtil.DTRANS_EXTEND_FORMAT) + " istat:" + shot.getIstat()); PIAR_Compvalues c = new PIAR_Compvalues(); c.setCount(PIAPI.ARCCOUNT); c.setPt(vald[0]); int[] times = new int[c.getCount()]; times[0] = api .getPITimestamp(DateUtil.parseDate("2008-10-15 00:00:00")); times[times.length - 1] = api.getPITimestamp(DateUtil .parseDate("2008-10-15 10:00:00")); c.setTimes(times); float[] rvals = new float[c.getCount()]; int[] istats = new int[c.getCount()]; isSuc = api.piar_compvalues(c); System.out.println("count:" + c.getCount() + " isSuc:" + isSuc); String state; try { state = api.pipt_digstate(248); System.out.println("isSuc:" + isSuc + " desc:" + state); } catch (RealDBException e) { e.printStackTrace(); } PIAR_Value pv = new PIAR_Value(); pv.setMode(3); pv.setPt(vald[0]); pv.setTimedate(api.getPITimestamp(DateUtil .parseDate("2008-10-15 13:00:00"))); isSuc = api.piar_value(pv); System.out.println("isSuc:" + isSuc + " value:" + pv.getRval() + " istat:" + pv.getIstat()); PIPT_Digpointers dg = new PIPT_Digpointers(); dg.setPt(vald[0]); isSuc = api.pipt_digpointers(dg); System.out.println("isSuc:" + isSuc + " code:" + dg.getDigcode() + " number:" + dg.getDignumb()); try { String err = api.piut_strerror(5); System.out.println("err:" + err); } catch (RealDBException e) { e.printStackTrace(); } }
有些方法不知道在如何实现,遂采用JNative代理库实现的。
与大伙一起探讨探讨。
评论
3 楼
pangyi
2009-04-29
tinywind 写道
jni方法就和一般的java方法一样,直接在头上加synchoronized性能当然快不了。jni本身提供了monitor的操作方法,可以作更精细的同步控制。http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/functions.html#wp23124
这些资料,咱也看过。
但具体操作起来,一头雾水啊。
可否给出代码例示。
2 楼
tinywind
2009-04-29
jni方法就和一般的java方法一样,直接在头上加synchoronized性能当然快不了。jni本身提供了monitor的操作方法,可以作更精细的同步控制。http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/functions.html#wp23124
1 楼
pangyi
2009-04-28
实时数据库数据量很大,采用JNI访问,有时会造成JVM崩溃。
是不是Java在异构系统的集成上,是不是很不理想啊?
是不是Java在异构系统的集成上,是不是很不理想啊?
发表评论
-
几道面试题
2010-08-11 10:02 881从CSDN上看到几道有趣的面试题,记录下来。抽空逐个分析解答。 ... -
试用Cassandra,其写效率太差
2010-03-16 17:24 4687Cassandra是由Facebook贡献的开源分布式数据库。 ... -
温习工厂模式
2010-03-10 14:50 0http://www.jdon.com/designpatte ... -
Java连续乘法遇到的问题
2009-07-12 17:05 2376采用 JDK 1.6_13版本,发现一个奇怪的问题。 ... -
逆波兰表达式
2009-07-04 17:00 1881逆波兰表达式 逆波 ... -
Python如何操作Oracle的Blob字段
2009-06-30 19:53 4562最近在学习使用Python,操作Oracle数据库采用的是cx ... -
Java远程通讯可选技术及原理(四)
2009-04-28 18:05 1425Burlap Burlap也是有ca ... -
Java远程通讯可选技术及原理(三)
2009-04-28 18:03 1752JMS JMS 呢,是实现java领域远程通信的一种手段 ... -
Java远程通讯可选技术及原理(二)
2009-04-28 18:00 1606RMI RMI 是个典型的为java定制的远程通信协议, ... -
Java远程通讯可选技术及原理(一)
2009-04-28 17:56 1098在分布式服务框架 ... -
求:采用DWR异步访问,频繁报“Failed to read input”的错误
2008-09-17 17:45 2361使用场景: 在报表展现时,需要计算处理数据,由于处 ... -
请教关于继承和引用引申的类设计问题
2008-09-11 12:37 903假设有两个类:DataTag 与 DataTagValue。D ... -
ORA-00600错误记录
2007-05-31 14:14 1474...
相关推荐
在Eclipse中,我们可以创建一个Java项目,并添加JNI支持。编写C/C++代码并使用javah工具生成头文件,然后实现头文件中声明的函数。最后,通过Eclipse的C/C++插件或外部编译器编译原生代码,并将其链接到Java项目。 ...
JNI本身并不直接支持创建线程,但我们可以使用C/C++原生的线程API(如POSIX的`pthread_create`或Windows的`CreateThread`)来实现。以下是一个简单的示例: ```c++ #include void* jniThreadFunction(void* ...
在实际应用中,开发人员需要考虑兼容性问题,如不同操作系统下的动态库加载方式、32位与64位系统的差异,以及可能的多线程安全性问题。此外,为了保证代码的安全性和效率,需要对敏感数据进行妥善处理,遵循最佳实践...
总之,实现"android JNI串口驱动支持多串口同时收发"涉及创建一个C/C++库来处理串口操作,编写JNI接口以在Java和C/C++之间进行通信,以及可能的多线程编程来确保并发处理。通过这种方式,Android应用可以高效地管理...
JNI在很多场景下非常有用,比如当需要利用已有的C或C++库,或者提升性能时,我们可以通过JNI将Java代码与本地代码集成。 在描述中提到的`jni.h`和`jni_md.h`是JNI编程中必不可少的头文件。`jni.h`包含了JNI接口的...
此外,还要注意线程安全问题,因为Java是多线程的,而Delphi的代码可能需要同步控制以避免竞态条件。 总结来说,`jni.pas`和`JNI_MD.INC`是Delphi开发者使用JNI的关键组件,它们使得Delphi程序能够无缝地与Java世界...
7. **线程支持**:JNI支持多线程环境,可以创建和管理Java线程,以及同步原语。 8. **字符串处理**:JNI提供了Java字符串和本地字符串之间的转换,以及字符串操作的功能。 9. **文件I/O**:虽然Java本身提供了丰富...
Android JNI线程的创建是Android原生开发中的一个核心概念,尤其在涉及到多线程并行执行任务时,其重要性不言而喻。 首先,理解Android系统对线程的支持。Android系统基于Linux内核,因此其线程模型遵循了POSIX线程...
JNI提供了线程本地存储来支持在多线程环境中安全地使用本地代码。 ### 知识点七:版权和知识产权 书中提到了Sun Microsystems公司对Java技术实现相关的知识产权拥有权。在实施此规范时,Sun提供了一种有限的、非...
9. **多线程支持**:JNI支持多线程编程,每个线程都有自己的`JNIEnv`指针。需要注意的是,线程安全问题,特别是在访问Java对象时,可能需要同步措施。 10. **性能优化**:JNI允许开发者使用原生代码来处理计算密集...
【标题】基于tesseract的多线程OCR服务器的JAVA实现 在信息技术领域,光学字符识别(OCR)技术被广泛应用于将图像中的文本转换为可编辑的电子格式。Tesseract是一款开源OCR引擎,由Google维护,它具有高精度和强大...
7. **多线程支持**:JNI支持在多线程环境中使用,但需要注意线程同步和资源管理,以避免竞态条件和内存泄漏。 8. **性能优化**:虽然JNI提供了强大的功能,但其调用开销比纯Java代码大。因此,通常只在性能关键的...
JNI支持多线程环境,每个Java线程都有其对应的`JNIEnv`指针。在多线程编程中,需要注意线程安全和同步问题。 8. **JNI的应用场景**: JNI常用于游戏开发,与硬件设备通信,调用系统API,或者优化性能关键部分的...
9. **线程支持**:JNI支持多线程环境,但需要注意线程安全问题。每个Java线程都有自己的JNI环境,本地方法必须在相应的线程环境中执行。 10. **内存管理**:JNI中使用`NewObject`、`NewString`等创建的Java对象需要...
`xxJNI_ch04_aa_Advanced_ok.pdf`和`xxJNI_ch04_bb_Advanced_ok.pdf`可能包含了JNI的高级话题,比如多线程(`xxJNI_ch05_Thread_ok.pdf`)支持。在Android中,JNI的线程管理是复杂的,因为Java和C/C++线程模型不同。...
8. **多线程支持**:JNI支持多线程环境,每个Java线程都有一个对应的本地线程,需要正确管理线程状态和同步。 通过学习和实践这个资料包中的内容,开发者可以掌握JNI的基本使用,从而实现Java与本地代码的无缝集成...
9. **线程支持**:JNI支持多线程环境,每个Java线程都有自己的JNIEnv指针。在本地代码中创建线程时,需要注意线程安全问题。 10. **动态链接库加载**:在JNI程序中,可以使用`LoadLibrary`函数加载动态链接库,并...
如果本地代码涉及到多线程,需要注意Java的线程安全问题。JNI提供了`MonitorEnter`和`MonitorExit`函数,用于实现Java对象的同步。 9. **数据类型转换**: JNI提供了从Java数据类型到C/C++数据类型的转换函数,如...
8. **线程支持**:JNI支持多线程环境,每个进入Java虚拟机的本地线程都会被分配一个`JNIEnv`指针,用于线程间的通信。 9. **垃圾回收协调**:本地代码需要配合Java的垃圾回收机制,例如,通过`NewGlobalRef`创建...
8. **多线程支持**:JNI在多线程环境下的注意事项,如何在本地方法中安全地访问Java对象。 9. **性能优化**:探讨使用JNI可能带来的性能影响,以及如何通过优化本地代码来提高效率。 10. **实例分析**:提供实际的...