2011年2月10日星期四

Facebook開發工具比較(for .Net)

 
Facebook開發工具比較(for .Net)
 
臉書整合服務寫於 2010年12月28日 6:53
 
Facebook官方提供的Api SDK,如果依程式語言來區分的話一共有以下幾種:
 
  • JavaScript SDK
  • PHP SDK
  • Python SDK
  • iOS SDK for iPhone, iPod touch, and iPad
  • Android SDK 
 
明 顯地,Facebook官方並沒有提供 for .Net的SDK,所以如果要使用.Net來開發Facebook的應用服務的話,就必須再借助第三方廠商所開發的SDK工具。我沒有實際去找出目前網路 上所有 for .Net的SDK,但就我爬文的結果,被使用最廣泛的主要有三套,如下:
 
 
 
(1) Facebook Developer Toolkit
 
(2) csharp-sdk
 
(3) Facebook C# SDK
 
 
 
來跟大家分享一下我比較的一些結果。
 
 
 
Facebook Developer Toolkit 
 
‧下載的網址:http://facebooktoolkit.codeplex.com/  (for .Net 2.0版本)
 
是 微軟官方所出版的Facebook for .Net SDK,沒錯!就是微軟官方所出的。一開始我看到好像找到寶一樣的開心,文件和範例都蠻齊全的。但是,我再詳細看這個SDK的支援還有最新更新的日期,才 發現它不支援Facebook新版的Graph API,也就是不支援OAuth 2.0 規格的登入授權方式。
 
舊版的 Facebook API登入是使用session_key以及動態的secret,新版的 Facebook API登入則是使用access_token(請參考:i am Jason: Facebook Open Graph的登入授權方式)。雖然現在Facebook API新舊並存,如果是用舊版的Api寫的程是目前都還可以跑,但不曉得哪Facebook要給它停掉,所以還是決定不採用。而且, 後來微軟官方自己也都沒有再更新這個套件了,我再去看這個工具的討論版,蠻多抱怨的文章 ,所以...
 
 
 
csharp-sdk
 
 
for .Net 4.0版本,而且要用Visual Studio 2010才能開啟專案檔,記得!還要自己將套件編譯過才能使用(給的很沒誠意!) 
 
本套件是Facebook官方開發人員所釋出的 for .Net SDK,但是從2010年7月17日公開到現在,一直都在Alpha版,沒錯!好像都沒再更新了。所以, Facebook也沒有將這個工具放在官方正式SDK清單裡面。
 
我看了[after dusk, before dawn]部落格針對該套件寫了幾篇文章,寫的很不錯,有興趣研究的可以連到[after dusk, before dawn: [Facebook] 臉書的C Sharp SDK]研究。
 
我 自己使用的感覺, csharp-sdk將比較繁瑣的Facebook訊息傳遞包裝成簡單的方法,並且提供便利的Jason物件解析方式(Facebook很多都是利用 Jason傳遞資料),基本上是一個麻雀雖小五臟俱全的工具。如果你要比較複雜的功能,或是後來Facebook又釋出的新功能,這個工具可能就達不到, 而要自己再開發。
 
Facebook使用者登入授權的方式主要有兩種,官方說明,一種是利用網頁傳地參數的方式,另一種是利用JS SDK取得Cookie來認證 ,csharp-sdk基本上是使用第一種方式,
 
利用網頁傳地參數的方式會有一些不便之處,後續再分享我的解決方式。
 
 
 
Facebook C# SDK
 
 ‧下載的網址:http://facebooksdk.codeplex.com/
 
for .Net 3.5 & 4.0版本,而且要用Visual Studio 2010才能開啟專案檔。一開始我以為是微軟官方的Facebook for .Net SDK,結果錯!不是微軟官方所出的,是另外的團隊所開發的,我看到的更新版本日期是
 
 
 
Current 4.1.0
 
Date    Fri Dec 17 2010 at 4:00 PM
 
 
 
真是好開心!有再更新耶~(標準越來越低了,有更新就好~),連微軟官方都公開直接支持這個 SDK(這是微軟的風格嗎?) ,範例也都有了,而且相當的齊全,連 Api文件都有了耶~佛心來著~
 
 
 
範例
 
 
 
馬上來測試看看,我是測試CSASPNETFacebookApp專案,但照上面的步驟來編譯,一直會出現Facebook授權失敗的錯誤訊息,我到專案的討論區上,也看到不少人有跟我同樣的問題,現在只能期待這個問題在下一個版本可以被修正。
 
 
 
【後續建議】
 
如果你的專案不大,我直接推薦csharp-sdk,快速上手又輕巧。
 
基本上我是蠻期待Facebook C# SDK這一套的工具的,但是Facebook常常在變動規則,要一直跟著 Facebook的隨興,又不會給搞死的第三方SDK套件,真的必須俱備有兩把刷子!
 
連微軟都索性不再更新官方的Facebook for .Net SDK了。Facebook C# SDK,加油! 
 
 
 
Best Regards
Miao
 
 

2011年1月24日星期一

USB设备描述符

 
USB中,USB HOST是通过各种描述符来识别设备的,有设备描述符,
配置描述符,接口描述符,端点描述符,字符串描述符,报告描述符等等。
USB报告描述符(Report Descriptor)HID设备中的一个描述符,它是比较
复杂的一个描述符。
USB HID设备是通过报告来给传送数据的,报告有输入报告和输出报告。
输入报告是
USB设备发送给主机的,例如USB鼠标将鼠标移动和鼠标点击等
信息返回给电脑,键盘将按键数据数据返回给电脑等;输出报告是主机发送
USB设备的,例如键盘上的数字键盘锁定灯和大写字母锁定灯等。报告是
一个数据包,里面包含的是所要传送的数据。输入报告是通过中断输入端点
输入的,而输出报告有点区别,当没有中断输出端点时,可以通过控制输出
端点
0发送,当有中断输出端点时,通过中断输出端点发出。
而报告描述符,是描述一个报告以及报告里面的数据是用来干什么用的。
通过它,USB HOST可以分析出报告里面的数据所表示的意思。它通过控制输入
端点0返回,主机使用获取报告描述符命令来获取报告描述符,注意这个请求
是发送到接口的,而不是到设备。一个报告描述符可以描述多个报告,不同的
报告通过报告ID来识别,报告ID在报告最前面,即第一个字节。当报告描述符中
没有规定报告ID时,报告中就没有ID字段,开始就是数据。更详细的说明请参看
USB HID协议,该协议可从Http://www.usb.org下载。
 
一个USB设备有一个设备描述符,设备描述符里面决定了该设备有多
少种配置,每种配置描述符对应着配置描述符;而在配置描述符中又定义
了该配置里面有多少个接口,每个接口有对应的接口描述符;在接口描
述符里面又定义了该接口有多少个端点,每个端点对应一个端点描述符;
端点描述符定义了端点的大小,类型等等。由此我们可以看出,USB
描述符之间的关系是一层一层的,最上一层是设备描述符,下面是配置
描述符,再下面是接口描述符,再下面是端点描述符。在获取描述符时,
先获取设备描述符,然后再获取配置描述符,根据配置描述符中的配置
集合长度,一次将配置描述符、接口描述符、端点描述符一起一次读回。
其中可能还会有获取设备序列号,厂商字符串,产品字符串等。
USB主机在检测到USB设备插入后,就要对设备进行枚举了。为什么要枚举呢?枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序。调试USB设备,很重要的一点就是USB的枚举过程,只要枚举成功了,那么就已经成功大半了。
在说枚举之前,先大概说说
USB的一种传输模式――控制传输。这种传输在USB中是非常重要的,它要保证数据的正确性,在设备的枚举过程中都是使用控制传输的。控制传输分为三个阶段:①建立阶段。②数据阶段。③确认阶段。建立(setup)阶段都是由USB主机发起,它是一个setup数据包,里面包含一些数据请求的命令以及一些数据。如果建立阶段是输入请求,那么数据阶段就要输入数据;如果建立阶段是输出请求,那么数据阶段就要输出数据。如果在数据阶段,即便不需要传送数据,也要发一个0长度的数据包。数据阶段过后就是确认阶段。确认阶段刚好跟数据阶段相反,如果是输入请求,则它是一个输出数据包;如果是输出请求,则它是一个输入数据包。确认阶段用来确认数据的正确传输。
好了,下面我们来看看枚举的详细过程。
首先,
USB主机检测到USB设备插入后,就会先对设备复位。设备复位后,USB主机就会对地址为0的设备发送获取设备描述符的标准请求。所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信。主机在建立阶段发出获取设备描述符的输入请求,设备收到该请求后,在数据阶段将设备描述符返回给主机。主机在成功获取到一个数据包的设备描述符后并且确认没有什么错误后(注意:有些USB设备的端点0大小不足18字节(但至少具有8字节),而标准的设备描述有18字节,在这种情况下,USB设备只能暂时按最大包将部分设备描述符返回,而主机在成功获取到前面一部分描述符后,就不会再请求剩下的设备描述符部分,而是进入设置地址阶段),就会返回一个0长度的确认数据包给设备。
然后主机再对设备复位一下,接下来就会进入到设置地址阶段。这时
USB主机发出一个设置地址的请求,并在后面跟着一个0长度的数据输出包。地址包含在建立包中,具体的地址USB主机会负责管理,它会分配一个唯一的地址给新的设备。USB设备在收到地址后,返回0长度的应答包,设备在收到这个0长度应答包的ACK之后,就可以起用新的地址了。这样设备就分配到了一个唯一的设备地址,以后主机就通过它来进行访问该设备。
然后主机再次获取设备描述符,这次跟第一次可能有点不一样,这次需要获取完全部的
18个字节的设备描述符。当然,如果你的端点0缓冲大于18字节的话,那就跟第一次的情形一样了。
接下来,主机就会获取配置描述符。配置描述符总共为
9字节。主机在获取到配置描述符后,根据里面的配置集合总长度,再获取配置集合。配置集合包括配置描述符,接口描述符,端点描符等等。
如果有字符串描述符的话,还要获取字符串描述符。另外
HID设备还有HID描述符等。使用BUS HOUND以及通过串口返回信息,很容易看到具体的过程。总之是主机请求什么,你的程序就响应什么。
下面这些数据是使用BUS HOUND抓的,这个是在WIN2000下抓到的,如果在WINXP下,就看不到设置地址之前的数据。
写了注释下面的部分就是主机和设备之间的数据通信,而其它的则是主机跟根集线器之间的通信数据。
USB是个通用的总线,端口都是统一的。但是USB设备却各种各样,
例如
USB鼠标,USB键盘,U盘等等,那么USB主机是如何识别出不同的
设备的呢?这就要依赖于描述符了。
USB的描述符主要有设备描述符,配置描述符,接口描述符,
端点描述符,字符串描述符,
HID描述符,报告描述符等等。
关于报告描述符,请看我以前写的:《
USB HID报告及报告描述符简介
http://group.ednchina.com/93/198.aspx
一个USB设备有一个设备描述符,设备描述符里面决定了该设备有多
少种配置,每种配置描述符对应着配置描述符;而在配置描述符中又定义
了该配置里面有多少个接口,每个接口有对应的接口描述符;在接口描
述符里面又定义了该接口有多少个端点,每个端点对应一个端点描述符;
端点描述符定义了端点的大小,类型等等。由此我们可以看出,
USB
描述符之间的关系是一层一层的,最上一层是设备描述符,下面是配置
描述符,再下面是接口描述符,再下面是端点描述符。在获取描述符时,
先获取设备描述符,然后再获取配置描述符,根据配置描述符中的配置
集合长度,一次将配置描述符、接口描述符、端点描述符一起一次读回。
其中可能还会有获取设备序列号,厂商字符串,产品字符串等。

每种描述符都有自己独立的编号,如下:
#define DEVICE_DESCRIPTOR 0x01 //设备描述符
#define CONFIGURATION_DESCRIPTOR 0x02 //配置描述符
#define STRING_DESCRIPTOR 0x03 //字符串描述符
#define INTERFACE_DESCRIPTOR 0x04 //接口描述符
#define ENDPOINT_DESCRIPTOR 0x05 //端点描述符

下面分别详细介绍一下各描述符。
1.设备描述符
//定义标准的设备描述符结构
typedef struct _DEVICE_DCESCRIPTOR_STRUCT
{
BYTE blength; //设备描述符的字节数大小
BYTE bDescriptorType; //设备描述符类型编号
WORD bcdUSB; //USB版本号
BYTE bDeviceClass; //USB分配的设备类代码
BYTE bDeviceSubClass; //USB分配的子类代码
BYTE bDeviceProtocol; //USB分配的设备协议代码
BYTE bMaxPacketSize0; //端点0的最大包大小
WORD idVendor; //厂商编号
WORD idProduct; //产品编号
WORD bcdDevice; //设备出厂编号
BYTE iManufacturer; //设备厂商字符串的索引
BYTE iProduct; //描述产品字符串的索引
BYTE iSerialNumber; //描述设备序列号字符串的索引
BYTE bNumConfigurations; //可能的配置数量
}
DEVICE_DESCRIPTOR_STRUCT, * pDEVICE_DESCRIPTOR_STRUCT;
//实际的设备描述符示例
code DEVICE_DESCRIPTOR_STRUCT device_descriptor= //设备描述符
{
sizeof(DEVICE_DESCRIPTOR_STRUCT), //设备描述符的字节数大小,这里是18字节
DEVICE_DESCRIPTOR, //设备描述符类型编号,设备描述符是01
0x1001, //USB版本号,这里是USB01.10,即USB1.1。由于51是大端模式,所以高低字节交换
0x00, //USB分配的设备类代码,0表示类型在接口描述符中定义
0x00, //USB分配的子类代码,上面一项为0时,本项也要设置为0
0x00, //USB分配的设备协议代码,上面一项为0时,本项也要设置为0
0x10, //端点0的最大包大小,这里为16字节
0x7104, //厂商编号,这个是需要跟USB组织申请的ID号,表示厂商代号。
0xf0ff, //该产品的编号,跟厂商编号一起配合使用,让主机注册该设备并加载相应的驱动程序
0x0100, //设备出厂编号
0x01, //设备厂商字符串的索引,在获取字符串描述符时,使用该索引号来识别不同的字符串
0x02, //描述产品字符串的索引,同上
0x03, //描述设备序列号字符串的索引,同上
0x01 //可能的配置数为1,即该设备只有一个配置
};
2.配置描述符
//定义标准的配置描述符结构
typedef struct _CONFIGURATION_DESCRIPTOR_STRUCT
{
BYTE bLength; //配置描述符的字节数大小
BYTE bDescriptorType; //配置描述符类型编号
WORD wTotalLength; //此配置返回的所有数据大小
BYTE bNumInterfaces; //此配置所支持的接口数量
BYTE bConfigurationValue; //Set_Configuration命令所需要的参数值
BYTE iConfiguration; //描述该配置的字符串的索引值
BYTE bmAttributes; //供电模式的选择
BYTE MaxPower; //设备从总线提取的最大电流
}
CONFIGURATION_DESCRIPTOR_STRUCT, * pCONFIGURATION_DESCRIPTOR_STRUCT;
2.接口描述符
//定义标准的接口描述符结构
typedef struct _INTERFACE_DESCRIPTOR_STRUCT
{
BYTE bLength; //接口描述符的字节数大小
BYTE bDescriptorType; //接口描述符的类型编号
BYTE bInterfaceNumber; //该接口的编号
BYTE bAlternateSetting; //备用的接口描述符编号
BYTE bNumEndpoints; //该接口使用的端点数,不包括端点0
BYTE bInterfaceClass; //接口类型
BYTE bInterfaceSubClass; //接口子类型
BYTE bInterfaceProtocol; //接口遵循的协议
BYTE iInterface; //描述该接口的字符串索引值
}
INTERFACE_DESCRIPTOR_STRUCT, * pINTERFACE_DESCRIPTOR_STRUCT;
4.端点描述符
//定义标准的端点描述符结构
typedef struct _ENDPOINT_DESCRIPTOR_STRUCT
{
BYTE bLegth; //端点描述符字节数大小
BYTE bDescriptorType; //端点描述符类型编号
BYTE bEndpointAddress; //端点地址及输入输出属性
BYTE bmAttributes; //端点的传输类型属性
WORD wMaxPacketSize; //端点收、发的最大包大小
BYTE bInterval; //主机查询端点的时间间隔
}
ENDPOINT_DESCRIPTOR_STRUCT, * pENDPOINT_DESCRIPTOR_STRUCT;
下面是一个配置描述符集合的定义
typedef struct _CON_INT_ENDP_DESCRIPTOR_STRUCT
{
CONFIGURATION_DESCRIPTOR_STRUCT configuration_descriptor;
INTERFACE_DESCRIPTOR_STRUCT interface_descritor;
ENDPOINT_DESCRIPTOR_STRUCT endpoint_descriptor[ENDPOINT_NUMBER];
}CON_INT_ENDP_DESCRIPTOR_STRUCT;
配置描述符集合的示例

code CON_INT_ENDP_DESCRIPTOR_STRUCT con_int_endp_descriptor= //配置描述符集合
{
//configuration_descriptor //
配置描述符
{
sizeof(CONFIGURATION_DESCRIPTOR_STRUCT), //
配置描述符的字节数大小,这里为9
CONFIGURATION_DESCRIPTOR, //
配置描述符类型编号,配置描述符为2
(sizeof(CONFIGURATION_DESCRIPTOR_STRUCT)+
sizeof(INTERFACE_DESCRIPTOR_STRUCT)+
sizeof(ENDPOINT_DESCRIPTOR_STRUCT)*ENDPOINT_NUMBER)*256+
(sizeof(CONFIGURATION_DESCRIPTOR_STRUCT)+
sizeof(INTERFACE_DESCRIPTOR_STRUCT)+
sizeof(ENDPOINT_DESCRIPTOR_STRUCT)*ENDPOINT_NUMBER)/256, //
配置描述符集合的总大小
0x01, //只包含一个接口
0x01, //该配置的编号
0x00, //iConfiguration字段
0x80, //采用总线供电,不支持远程唤醒
0xC8 //从总线获取最大电流400mA
},
//interface_descritor //
接口描述符
{
sizeof(INTERFACE_DESCRIPTOR_STRUCT), //
接口描述符的字节数大小,这里为9
INTERFACE_DESCRIPTOR, //
接口描述符类型编号,接口描述符为3
0x00, //
接口编号为4
0x00, //
该接口描述符的编号为0
ENDPOINT_NUMBER, //
0端点数量为2,只使用端点主端点输入和输出
0x08, //定义为USB大容量存储设备
0x06, //使用的子类,为简化块命令
0x50, //使用的协议,这里使用单批量传输协议
0x00 //接口描述符字符串索引,为0,表示没有字符串
},
//endpoint_descriptor[]
{
{ //主端点输入描述
sizeof(ENDPOINT_DESCRIPTOR_STRUCT), //端点描述符的字节数大小,这里为7
ENDPOINT_DESCRIPTOR, //端点描述符类型编号,端点描述符为5
MAIN_POINT_IN, //端点号,主输入端点
ENDPOINT_TYPE_BULK, //使用的传输类型,批量传输
0x4000, //该端点支持的最大包尺寸,64字节
0x00 //中断扫描时间,对批量传输无效
},
{ //主端点输出描述
sizeof(ENDPOINT_DESCRIPTOR_STRUCT), //端点描述符的字节数大小,这里为7
ENDPOINT_DESCRIPTOR, //端点描述符类型编号,端点描述符为5
MAIN_POINT_OUT, //端点号,主输出端点
ENDPOINT_TYPE_BULK, //使用的传输类型,批量传输
0x4000, //该端点支持的最大包尺寸,64字节
0x00 //中断扫描时间,对批量传输无效
}
}
};
其中关于端点的类型定义如下
//定义的端点类型
#define ENDPOINT_TYPE_CONTROL 0x00 //控制传输
#define ENDPOINT_TYPE_ISOCHRONOUS 0x01 //同步传输
#define ENDPOINT_TYPE_BULK 0x02 //批量传输
#define ENDPOINT_TYPE_INTERRUPT 0x03 //中断传输
端点号的定义如下
#define MAIN_POINT_OUT 0x02 //2号输出端点
#define MAIN_POINT_IN 0x82 //2号输入端点
 
 
 
 

2011年1月21日星期五

FW: [嘻嘻哈哈] USB接口的红外电脑遥控

USB接口的红外电脑遥控

USB接口的红外电脑遥控

摘要:本设计采用2262,2272一对无线控制芯片来实现红外的发射和硬件解码。而在USB控制模块则采用51+PDIUSBD12来实现USB设备的枚举和数据传送。

本次设计的要点在于USB键盘的实现,因为本次设计最终与主机进行通信是将USB设备枚举成USB键盘,从而获得操作系统的支持,实现免驱动安装的目的。

(一)方案的选择

1:红外的软件解码

此方案是通过软件的复杂化来简化硬件结构,此方案不仅要求对红外协议的熟悉,而且软件解码耗时较大,不适合进行实时性数据的传送。

2:红外的硬件解码

此方案通过一对无线发射和接受芯片2262,2272来实现红外的解码。其特点是红外传送的实时性好,简化软件编写量,且外围硬件电路并不复杂,故在本次设计中采用此方案。

3:采用LPC2148来实现USB的控制

此方案中,LPC2148是一款内置USB模块的微控制器。用其来实现USB的控制和传送可以最大化的简化外围电路和程序的稳定性,但其缺点是芯片价格高,需要另外学习ARM7系列的编程特点。



4:采用51+PDIUSBD12来实现USB的控制

PDIUSBD12是一款性价比很高的USB 器件它通常用作微控制器系统中实现与微控制器进行通信的高速通用并行接口它还支持本地的DMA 传输,支持USB1.1协议。虽然采用51+PDIUSBD12形式使硬件复杂,但由此换来的软件编写环境的熟悉和价格上的优势,故采用此方案。

(二)USB协议和HID键盘的实现

本设计是通过将此USB设备枚举成USB键盘来实现电脑的控制,所以必定设计到USB设备的枚举,故在此先对USB的枚举进行简单的论述。

枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序。 在说枚举之前,先大概说说USB的一种传输模式――控制传输。这种传输在USB中是非常重要的,它要保证数据的正确性,在设备的枚举过程中都是使用控制传输的。控制传输分为三个阶段:①建立阶段。②数据阶段。③确认阶段。建立(setup)阶段都是由USB主机发起,它是一个setup数据包,里面包含一些数据请求的命令以及一些数据。如果建立阶段是输入请求,那么数据阶段就要输入数据;如果建立阶段是输出请求,那么数据阶段就要输出数据。如果在数据阶段,即便不需要传送数据,也要发一个0长度的数据包。数据阶段过后就是确认阶段。确认阶段刚好跟数据阶段相反,如果是输入请求,则它是一个输出数据包;如果是输出请求,则它是一个输入数据包。确认阶段用来确认数据的正确传输。



1:枚举过程
首先,USB主机检测到USB设备插入后,就会先对设备复位。设备复位后,USB主机就会对地址为0的设备发送获取设备描述符的标准请求。所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信。主机在建立阶段发出获取设备描述符的输入请求,设备收到该请求后,在数据阶段将设备描述符返回给主机。主机在成功获取到一个数据包的设备描述符后并且确认没有什么错误后(注意:有些USB设备的端点0大小不足18字节(但至少具有8字节),而标准的设备描述有18字节,在这种情况下,USB设备只能暂时按最大包将部分设备描述符返回,而主机在成功获取到前面一部分描述符后,就不会再请求剩下的设备描述符部分,而是进入设置地址阶段),就会返回一个0长度的确认数据包给设备。
然后主机再对设备复位一下,接下来就会进入到设置地址阶段。这时USB主机发出一个设置地址的请求,并在后面跟着一个0长度的数据输出包。地址包含在建立包中,具体的地址USB主机会负责管理,它会分配一个唯一的地址给新的设备。USB设备在收到地址后,返回0长度的应答包,设备在收到这个0长度应答包的ACK之后,就可以起用新的地址了。这样设备就分配到了一个唯一的设备地址,以后主机就通过它来进行访问该设备。
然后主机再次获取设备描述符,这次跟第一次可能有点不一样,这次需要获取完全部的18个字节的设备描述符。当然,如果你的端点0缓冲大于18字节的话,那就跟第一次的情形一样了。
接下来,主机就会获取配置描述符。配置描述符总共为9字节。主机在获取到配置描述符后,根据里面的配置集合总长度,再获取配置集合。配置集合包括配置描述符,接口描述符,端点描符等等。

如果在设备描述符中定义了字符串描述符,而主机还需要获取字符串描述符。字符串描述符的长度不固定,可由程序员自行确定。
而在此设计中,由于键盘是一种HID设备,故主机还需要获取HID设备类描述符。HID设备类描述符包括HID描述符、报告描述符、物理描述符三种,其中报告描述符是必须的,而另外两种则为可选描述符。

主机得到各种正确的描述符,则为枚举成功,主机加载合适的驱动,即可进行通讯。

2:各种描述符

一个USB设备有一个设备描述符,设备描述符里面决定了该设备有多
少种配置,每种配置描述符对应着配置描述符;而在配置描述符中又定义
了该配置里面有多少个接口,每个接口有对应的接口描述符;在接口描
述符里面又定义了该接口有多少个端点,每个端点对应一个端点描述符;
端点描述符定义了端点的大小,类型等等。由此我们可以看出,USB的
描述符之间的关系是一层一层的,最上一层是设备描述符,下面是配置
描述符,再下面是接口描述符,再下面是端点描述符。在获取描述符时,
先获取设备描述符,然后再获取配置描述符,根据配置描述符中的配置
集合长度,一次将配置描述符、接口描述符、端点描述符一起一次读回。
其中可能还会有获取设备序列号,厂商字符串,产品字符串等。


每种描述符都有自己独立的编号,如下:
DEVICE_DESCRIPTOR 0x01 //设备描述符
CONFIGURATION_DESCRIPTOR 0x02 //配置描述符
STRING_DESCRIPTOR 0x03 //字符串描述符
INTERFACE_DESCRIPTOR 0x04 //接口描述符
ENDPOINT_DESCRIPTOR 0x05 //端点描述符

设备描述符



BYTE blength; //设备描述符的字节数大小
BYTE bDescriptorType; //设备描述符类型编号
WORD bcdUSB; //USB版本号
BYTE bDeviceClass; //USB分配的设备类代码
BYTE bDeviceSubClass; //USB分配的子类代码
BYTE bDeviceProtocol; //USB分配的设备协议代码
BYTE bMaxPacketSize0; //端点0的最大包大小
WORD idVendor; //厂商编号
WORD idProduct; //产品编号
WORD bcdDevice; //设备出厂编号
BYTE iManufacturer; //设备厂商字符串的索引
BYTE iProduct; //描述产品字符串的索引
BYTE iSerialNumber; //描述设备序列号字符串的索引
BYTE bNumConfigurations; //可能的配置数量

配置描述符

BYTE bLength; //配置描述符的字节数大小
BYTE bDescriptorType; //配置描述符类型编号
WORD wTotalLength; //此配置返回的所有数据大小
BYTE bNumInterfaces; //此配置所支持的接口数量
BYTE bConfigurationValue; //配置值
BYTE iConfiguration; //描述该配置的字符串索引值
BYTE bmAttributes; //供电模式的选择
BYTE MaxPower; //设备从总线提取的最大电流



接口描述符
BYTE bLength; //接口描述符的字节数大小
BYTE bDescriptorType; //接口描述符的类型编号
BYTE bInterfaceNumber; //该接口的编号
BYTE bAlternateSetting; //备用的接口描述符编号
BYTE bNumEndpoints; //接口使用的端点数不包括端点0
BYTE bInterfaceClass; //接口类型
BYTE bInterfaceSubClass; //接口子类型
BYTE bInterfaceProtocol; //接口遵循的协议
BYTE iInterface; //描述该接口的字符串索引值



端点描述符
BYTE bLegth; //端点描述符字节数大小
BYTE bDescriptorType; //端点描述符类型编号
BYTE bEndpointAddress; //端点地址及输入输出属性
BYTE bmAttributes; //端点的传输类型属性
WORD wMaxPacketSize; //端点收、发的最大包大小
BYTE bInterval; //主机查询端点的时间间隔



字符串描述符



字符串描述符包括设备厂家描述符、产品描述符、设备序列号描述符。其作用主要是打上自己的商标,防止与其它厂家或其它产品一样,或者被其它厂家倒版,这一块有自己定义,长度也是由自己设定。

基本格式为:

BYTE bLegth; //字符串描述符字节数大小(N+2)
BYTE USB_STRING_DESCRIPTOR_TYPE //字符串类型编码

下面的数据有自己定义 //自己定义,长度为N


HID设备类描述符



通过上面的描述,主机可得到USB设备的大概信息,但如果仅只有以上那些的话,USB设备是不能和主机进行通信的。因为在上面的描述符中根本就没有定义USB设备的具体功能和传递信息的数据格式,而这就需要类描述符。

而因为本设计中的USB键盘所属的为人机接口类(HID),所以我们主要就叙述HID设备类描述符。



HID设备类描述符中自行定义了3种类描述符,分别为HID描述符、报告描述符和物理描述符,其中HID描述符和物理描述符不是必须的。

而报告描述符,是描述一个报告以及报告里面的数据是用来干什么用的。
通过它,USB HOST可以分析出报告里面的数据所表示的意思。它通过控制输入端点0返回,主机使用获取报告描述符命令来获取报告描述符,注意这个请求是发送到接口的,而不是到设备。一个报告描述符可以描述多个报告,不同的报告通过报告ID来识别,报告ID在报告最前面,即第一个字节。当报告描述符中没有规定报告ID时,报告中就没有ID字段,开始就是数据。

以下为普通USB键盘的报告描述符(不支持多媒体功能)
0x05, 0x01, //USAGE_PAGE (Generic Desktop)用途页为通用桌面设备 0x09, 0x06, //USAGE (Keyboard) 用途为键盘
0xa1, 0x01, //COLLECTION (Application)集合开始 0x05, 0x07, //USAGE_PAGE (Keyboard) 用途页为按键
0x19, 0xe0, //USAGE_MINIMUM (Keyboard LeftControl)用途最小值
0x29, 0xe7, //USAGE_MAXIMUM (Keyboard Right GUI)用途最大值
0x15, 0x00, //LOGICAL_MINIMUM (0) 逻辑最小值为0
0x25, 0x01, //LOGICAL_MAXIMUM (1) 逻辑最大值为1
0x75, 0x01, //REPORT_SIZE (1) 报告大小(即这个字段的宽度)为1bit
0x95, 0x08, //REPORT_COUNT (8) 报告的个数为8,即总共有8个bits
//输入用,变量,值,绝对值。像键盘这类一般报告绝对值
0x81, 0x02, // INPUT (Data,Var,Abs)
/*上面这这几项描述了一个输入用的字段,总共为8个bits,每个bit表示一个按键,分别从左ctrl键到右GUI键。这8个bits刚好构成一个字节,它位于报告的第一个字节。它的最低位,即bit-0对应着左ctrl键,如果返回的数据该位为1,则表示左ctrl键被按下,否则,左ctrl键没有按下。最高位,即bit-7表示右GUI键的按下情况。中间的几个位,需要根据HID协议中规定的用途页表(HID Usage Tables)来确定。这里通常用来表示特殊键,例如ctrl,shift,del键等 */
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // INPUT (Cnst,Var,Abs)

0x95, 0x05, // REPORT_COUNT (5)
0x75, 0x01, // REPORT_SIZE (1)
0x05, 0x08, // USAGE_PAGE (LEDs) 控制键盘上的LED用的
0x19, 0x01, // USAGE_MINIMUM (Num Lock) 数字键锁定灯
0x29, 0x05, // USAGE_MAXIMUM (Kana)
//输出,用来控制LED。变量,值,绝对值。1表示灯亮,0表示灯灭
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
//由于要按字节对齐,而前面控制LED的只用了5个bit,
//所以后面需要附加3个不用bit,设置为常量
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0xFF, // LOGICAL_MAXIMUM (255)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Ary,Abs)
/*以上定义了6个8bit宽的数组,每个8bit(即一个字节)用来表示一个 按键,所以可以同时有6个按键按下。没有按键按下时,全部返回0。如果按下的键太多,导致键盘扫描系统无法区分按键时,则全部返回0x01,即6个0x01。如果有一个键按下,则这6个字节中的第一个字节为相应的键值(具体的值参看HID Usage Tables),如果两个键按下,则第1、2两个字节分别为相应的键值,以次类推。*/

USB HID设备传送数据有两种方式:

一是通过报告来给传送数据,报告有输入报告和输出报告。输入报告是USB设备发送给主机的,例如USB鼠标将鼠标移动和鼠标点击等信息返回给电脑,键盘将按键数据数据返回给电脑等;输出报告是主机发送给USB设备的,例如键盘上的数字键盘锁定灯和大写字母锁定灯等。报告是一个数据包,里面包含的是所要传送的数据。输入报告是通过中断输入端点输入的,而输出报告有点区别,当没有中断输出端点时,可以通过控制输出端点0发送,当有中断输出端点时,通过中断输出端点发出。在USB2.0协议中提出不提倡通过这种方式来进行传送数据。

二是通过端点的中断传送进行传递数据,在本设计中就是通过端点2的中断来进行与主机的数据交换。

(三)无线电脑遥控实现

本设计是通过2262配合红外发射头来进行发送数据,用与之相匹配的2267(地址,频率匹配)和红外接收头来接收数据。在单片机每次的定时器0中断的时候进行读取数据,通过每次的端点2的中断将数据传递至主机,从而实现对主机的相应控制。

具体实现为:在每一次2262的有按键按下的同时使2262工作,将数据传送至2272,而2272将从2262解码出数据,等待单片机的读取。若2262没有按键按下,则2272的数据为0。在定时器0中断的中断服务程序中,读出2272解码出来的其中的按键信息,将其变换成控制相应按键的USB键盘数据流,通过PDIUSBD12的端点2将数据传递至主机,从而实现一次相应的控制。因为端点2的中断和定时器0的中断是循环不断的进行,所以也就能实时地读取到键盘的信息,完成无线电脑遥控的目的。



05 01 // USAGE_PAGE (Generic Desktop)
09 03 //USAGE
a1 01 //collection

09 01 //USAGE
15 00 //最小值
26 ff 00

75 08 ,//REPORT_SIZE (8)
95 08 //报告的个数为8
81 00 //输入
15 00 //最小值
25 01 //最大值


95 40 //报告长度64
75 01 ,//每个段大小为3bits
05 08 //USAGE_PAGE (Generic Desktop)
19 01 //用途最小值
29 40 //用途最大值
91 02 //报告相对值
c0

HID报告描述符_百度文库
http://blog.sina.com.cn/s/blog_4cd5d2bb0100f72p.html

--
Best Regards



--
Kimi 于 1/21/2011 05:01:00 上午 发布在 嘻嘻哈哈 <http://joke-xixihaha.blogspot.com/2011/01/usb.html> 上

2010年6月3日星期四

什么是KDJ指标

http://www.shenmeshi.com/Business/Business_20070211204322.html


什么是KDJ指标

作者:未知 来源:网 络 点击数:1355904 日期:2007-2-11
文章导读
什么是KDJ指标?KDJ指标是什么意思?KDJ指标又叫随机指标,是由乔治-蓝恩博士(George Lane)最早提出的,是一种相当新 颖、实用的技术分析指标,它起先用于期货市场的分析,后被广泛用于股市的中短期趋势分析,是期货和股票市场上最常用的技术分析工具。…

KDJ指标又叫随机指标,是由乔治-蓝恩博士(George Lane)最早提出的,是一种相当新颖、实用的技术分 析指标,它起先用于期货市场的分析,后被广泛用于股市的中短期趋势分析,是期货和股票市场上最常用的技术分析工具。

KDJ指标的原理和计算方法

一、KDJ指标的原理

随机指标KDJ一般是根据统计学的原理,通过一个特定的周期(常为9日、9周等)内出现过的最高价、最低价及最后一个计算周期的收盘价及这三者之间的比例关系,来计算最后一个计算周期的未成熟随机值RSV,然后根据平滑移动平均线的方法来计算K值、D值与J值,并绘成曲线图来研判股票走势。

随机指标KDJ是以最高价、最低价及收盘价为基本数据进行计算,得出的K值、D值和J值分别在指标的坐标上形成的一个点,连接无数个这样的点位,就形成一个完整的、能反映价格波动趋势的KDJ指标。它主要是利用 价格波动的真实波幅来反映价格走势的强弱和超买超卖现象,在价格尚未上升或下降之前发出买卖信号的一种技术工具。它在设计过程中主要是研究最高价、最低价 和收盘价之间的关系,同时也融合了动量观念、强弱指标和移动平均线的一些优点,因此,能够比较迅速、快捷、直观地研判行情。

随机指标KDJ最早是以KD指标的形式出现,而KD指标是在威廉指标的基础上发展起来的。不过威廉指标只判断股票的超买超卖的现象,在 KDJ指标中则融合了移动平均线速度上的观念,形成比较准确的买卖信号依据。在实践中,K线与D线配合J线组成KDJ指标来使用。由于KDJ线本质上是一 个随机波动的观念,故其对于掌握中短期行情走势比较准确。

二、KDJ指标的计算方法

指标KDJ的计算比较复杂,首先要计算周期(n日、n周等)的RSV值,即未成熟随机指标值,然后再计算K值、D值、J值等。以日KDJ数值的计算 为例,其计算公式为

n日RSV=(Cn-Ln)÷(Hn-Ln)×100

公式中,Cn为第n日收盘价;Ln为n日内的最低价;Hn为n日内的最高价。RSV值始终在1—100间波动。

其次,计算K值与D值:

当日K值=2/3×前一日K值+1/3×当日RSV

当日D值=2/3×前一日D值+1/3×当日K值

若无前一日K 值与D值,则可分别用50来代替。

以9日为周期的KD线为例。首先须计算出最近9日的RSV值,即未成熟随机值,计算公式为

9日RSV=(C-L9)÷(H9-L9)×100

式中,C为第9日的收盘价;L9为9日内的最低价;H9为9日内的最高价。

K值=2/3×前一日 K值+1/3×当日RSV

D值=2/3×前一日K值+1/3×当日RSV

若无前一日K值与D值,则可以分别用50代替。

需要说明的是,式中的平滑因子1/3和2/3是可以人为选定的,不过目前已经约定俗成,固定为1/3和2/3。在大多数股市分析软件中,平滑因子已 经被设定为1/3和2/3,不需要作改动。另外,一般在介绍KD时,往往还附带一个J指标。

J指标的计算公式为:

J=3D—2K

实际上,J的实质是反映K值和D值的乖离程度,从而领先KD值找出头部或底部。J值范围可超过100。

J指标是个辅助指标,最早的KDJ指标只有两条线,即K线和D线,指标也被称为KD指标,随着股市分析技术的发展,KD指标逐渐演变成KDJ指标, 从而提高了KDJ指标分析行情的能力。另外,在一些股市重要的分析软件上,KDJ指标的K、D、J参数已经被简化成仅仅一个,即周期数(如日、周、月 等),而且,随着股市软件分析技术的发展,投资者只需掌握KDJ形成的基本原理和计算方法,无须去计算K、D、J的值,更为重要的是利用KDJ指标去分 析、研判股票行情。

和其他指标的计算一样,由于选用的计算周期的不同,KDJ指标也包括日KDJ指标、周KDJ指标、月KDJ指标年KDJ指标以及分钟KDJ指标等各 种类型。经常被用于股市研判的是日KDJ指标和周KDJ指标。虽然它们的计算时的取值有所不同,但基本的计算方法一样。

KDJ指标的一般研判标准

随机指标KDJ主要是通过K、D和J这三条曲线的所构成的图形关系来分析股市上的超买超卖,走势背离及K线、D线和J线相互交叉突破等现象,从而预测股价中、短期及长期趋势。KDJ是市场上绝大多数投资者熟知的分析工具,但具体运用时,投资者可能会发现 KDJ的分析结果和实际走势存在着特别大的差别,有时还会得出相反的结论,这其中原因主要是绝大多数投资者只知道KDJ的一般分析原理和方法,而对KDJ 分析指标的一些内涵和特定的分析技巧知之甚少。本节在介绍股市分析中市场上流行的KDJ的一般研判技巧和分析方法上,重点挖掘 KDJ指标的内在规律,详细分析KDJ的一些特殊研判功能。

KDJ指标是三条曲线,在应用时KDJ指标的一般研判标准主要是从KDJ三个参数的取值、KDJ曲线的形态、KDJ曲线的交叉、KDJ曲线的背离和 K线、D线、J线的运行状态以及KDJ曲线同股价曲线的配合等六个方面来考虑。

一、KDJ的取值

1、取值范围

KDJ指标中,K值和D值的取值范围都是0—100,而J值的取值范围可以超过100和低于0,但在分析软件上KDJ的研判范围都是0—100。通 常就敏感性而言,J值最强,K值次之,D值最慢,而就安全性而言,J值最差,K值次之,D值最稳。

2、超买超卖信号

根据KDJ的取值,可将其划分为几个区域,即超买区、超卖区和徘徊区。按一般划分标准,K、D、J这三值在20以下为超卖区,是买入信号;K、D、 J这三值在80以上为超买区,是卖出信号;K、D、J这三值在20—80之间为徘徊区,宜观望。

3、 多空力量对比

一般而言,当K、D、J三值在50附近时,表示多空双方力量均衡;当K、D、J三值都大于50时,表示多方力量占优;当K、D、J三值都小于50 时,表示空方力量占优。

二、KDJ曲线的形态

KDJ指标的研判还可以从KDJ曲线的形态来分析。当KDJ指标曲线图形形成头肩顶底形态、双重顶底形态(即M头W底)及三重顶底等形态时,也可以按照形态理论的研判方法加以分析。KDJ曲线出现的各种形态是判断行情走势、决定买卖 时机的一种分析方法。另外,KDJ指标曲线还可以划趋势线、压力线和支撑线等。

1、当KDJ曲线在50上方的高位时,如果KDJ曲线的走势形成M头或三重顶等顶部反转形态,可能预示着股价由强势转为弱势,股价即将大跌,应及时 卖出股票。如果股价的曲线也出现同样形态则更可确认,其跌幅可以用M头或三重顶等形态理论来研判。

2、当KDJ曲线在50下方的低位时,如果KDJ曲线的走势出现W底或三重底等底部反转形态,可能预示着股价由弱势转为强势,股价即将反弹向上,可以逢低少量吸纳股票。如果股价曲线 也出现同样形态更可确认,其涨幅可以用W底或三重底形态理论来研判。

3、KDJ曲线的形态中M头和三重顶形态的准确性要大于W底和三重底。

三、KDJ曲线的交叉

KDJ曲线的交叉分为黄金交叉和死亡交叉两种形式

一般而言,在一个股票的完整的升势和跌势过程中,KDJ指标中的K、D、J线会出现两次或以上的“黄金交叉”和“死亡交叉”情况。

1、当股价经过一段很长时间的低位盘整行情,并且K、D、J三线都处于50线以下时,一旦J线和K线几乎同时向上突破D线时,表明股市即将转强, 股价跌势已经结束,将止跌朝上,可以开始买进股票,进行中长线建仓。这是KDJ指标“黄金交叉”的一种形式。

2、当股价经过一段时间的上升过程中的盘整行情,并且K、D、J线都处于50线附近徘徊时,一旦J线和K线几乎同时再次向上突破D线,成交量再度放出时,表明股市处于一种强势之中,股价将再次上涨,可以加码买进股票或持股待涨,这就是KDJ指标 “黄金交叉”的一种形式。

3、当股价经过前期一段很长时间的上升行情后,股价涨幅已经很大的情况下,一旦J线和K线在高位(80以上)几乎同时向下突破D线时,表明股市即将 由强势转为弱势,股价将大跌,这时应卖出大部分股票而不能买股票,这就是KDJ指标的“死亡交叉”的一种形式。

4、当股价经过一段时间的下跌后,而股价向上反弹的动力缺乏,各种均线对股价形成较强的压力时,KDJ曲线在经过短暂的反弹到80线附近,但未能重 返80线以上时,一旦J线和K线再次向下突破D线时,表明股市将再次进入极度弱市中,股价还将下跌,可以再 卖出股票或观望,这是KDJ指标“死亡交叉”的另一种形式。

四、KDJ曲线的背离

KDJ曲线的背离就是指当KDJ指标的曲线图的走势方向正好和K线图的走势方向正好相反。KDJ指标的背离有顶背离和底背离两种。

当股价K线图上的股票走势一峰比一峰高,股价在一直向上涨,而KDJ曲线图上的KDJ指标的走势是在高位一峰比一峰低,这叫顶背离现象。顶背离现象 一般是股价将高位反转的信号,表明股价中短期内即将下跌,是卖出的信号。

当股价K线图上的股票走势一峰比一峰低,股价在向下跌,而KDJ曲线图上的KDJ指标的走势是在低位一底比一底高,这叫低背离现象。底背离现象一般 是股价将低位反转的信号,表明股价中短期内即将上涨,是买入的信号。

与其他技术指标的背离现象研判一样,KDJ的背离中,顶背离的研判准确性要高于底背离。当股价在高位,KDJ在80以上出现顶背离时,可以认为股价 即将反转向下,投资者可以及时卖出股票;而股价在低位,KDJ也在低位(50以下)出现底背离时,一般要反复出现几次底背离才能确认,并且投资者只能做战 略建仓或做短期投资

五、K、D、J曲线运行的状态

1、当J曲线开始在底部(50以下)向上突破K曲线时,说明股价的弱势整理格局可能被打破,股价短期将向上运动,投资者可以考虑少量长线建仓。

2、当J曲线向上突破K曲线并迅速向上运动,同时曲线也向上突破D曲线,说明股价的中长期上涨行情已经开始,投资者可以加大买入股票的力度。

3、当K、D、J曲线开始摆脱前期窄幅盘整的区间并同时向上快速运动时,说明股价已经进入短线强势拉升行情,投资者应坚决持股待涨。

4、当J曲线经过一段快速向上运动的过程后开始在高位(80以上)向下掉头时,说明股价短期上涨过快,将开始短线调整,投资者可以短线卖出股票。

5、当D曲线也开始在高位向下掉头时,说明股价的短期上涨行情可能结束,投资者应中线卖出股票。

6、当K曲线也开始在高位向下掉头时,说明股价的中短期上涨行情已经结束,投资者应全部清仓离场。

7、当K、D、J曲线从高位同时向下运动时,说明股价的下跌趋势已经形成,投资者应坚决持币观望。

六、KDJ曲线与股价曲线的配合使用

1、当KDJ曲线与股价曲线从低位(KDJ值均在50以下)同步上升,表明股价中长期趋势向好、短期内股价有望继续上涨趋势,投资者应继续持股或逢低买入。

2、当KDJ曲线与股价曲线从高位(KDJ值均在50以上)同步下降,表明短期内股价将继续下跌趋势,投资者应继续持币观望或逢高卖出。

3、当KDJ曲线从高位回落,经过一段时间强势盘整后再度向上并创出新高,而股价曲线也在高位强势盘整后再度上升创出新高,表明股价的上涨动力依然较强,投资者可继续持股待涨。

4、当KDJ曲线从高位回落,经过一段时间盘整后再度向上,但到了前期高点附近时却掉头向下、未能创出新高时,而股价曲线还在缓慢上升并创出新 高,KDJ曲线和股价曲线在高位形成了相反的走势,这可能就意味着股价上涨的动力开始减弱,KDJ指标出现了顶背离现象。此时投资者应千万小心,一旦股价 从下,应果断及时地离场。

5、当KDJ曲线在长期弱势下跌过程中,经过一段时间弱势反弹后再度向下并创出新低,而股价曲线也在弱势盘整后再度向下创出新低,表明股价的下跌动 能依然较强,投资者可继续持币观望。

6、当KDJ曲线从低位向上反弹到一定高位、再度向下回落,但回调到前期低点附近时止跌企稳、未能创出新低时,而股价曲线还在缓慢下降并创出新低,KDJ曲线和股价曲线在低位形成相反的走势,这 可能就意味着股价下跌的动能开始衰弱,KDJ指标出现了底背离现象。此时投资者也应密切关注股价动向,一旦股价向上就可以短线买入,等待反弹的出现。

KDJ的特殊分析方法

一、KDJ的分析周期

日、周、月、分钟(主要是60分钟)

10日以下为分析参数的KDJ的研判适用周期为3天左右(从金叉到死叉为3天时间)

50日以下为分析参数的KDJ的研判使用周期为10天左右

50日以上为分析参数的KDJ的研判适用周期为20天左右

二、 均线先行原则

股价一旦被长期均线压制,KDJ再怎么样金叉一般也只能做短线操作,切莫做中长线投资。这是KDJ使用的前提。在长期均线下,且远离均线,KDJ金 叉时,股价有超跌反弹的可能可做短线操作

三、 涨势的大体周期

日KDJ是短中期 最多维持15天——1个月

周KDJ是中期,维持时间为1个月——3个月(一旦金叉,一个月内基本会涨,但涨幅不能确定)

月KDJ是长期,维持时间一般为3个月——5个月

四、除权后,KDJ指标没有研判意义,起码要三个月以后才能重新研判

五、KDJ的参数的修改

随着技术分析的广泛应用,技术指标的重要性已是不言而喻。 但由于目前技术指标的运算已大为简化,这就造成技术指标大面积的雷同,并失去其指导作用。因此为尽量保持精确性、敏感性和时效性的和谐与统一,有必要对某 些指标重置参数,并注意以下通则:

第一、根据时间循环周期设定参数。无论大盘还是个股,时间周期对其运行都会产生较大的影响,而周期本身往往就是一个非常重要的参数,这一点在均线、 强弱指标等方面表现得尤为突出。至于周期的测量, 通常可由两个重要低点的时间跨度 来确定。同时,由于存在长、中、短线的区别,投资者必须根据具体情况设定个性化 且自己熟知的参数,而5(日)或其倍数、以及费波南兹数列在这方面都是不错的选择。

第二、保持不同时间标准的一致性。如果将适用于日线的参数放到分时里, 特别是震荡类指标就会过于敏感,用于周线或月线则会明显滞后,因此,在应用过程中, 这一原则必须适当变通后方可使用,这绝不是一个简单的计算问题,而是换算之后需要进行微调,尽管个别指标绝对保持一致仍能取得较好的效果。

第三、注意大盘与个股间的异同点。由于大盘与个股是一般与特殊的关系, 适合于大盘的同一指标参数,通常可直接或略作调整后用于大多数个股,但两者之间的差异毕竟存在,大盘的平均结果往往也会掩盖许多真相或忽略了个性化,即处 于超强或超弱状态的个股不同于大盘,其参数的设定理应另开小灶。

第四、随市场变化不断调整并优化。强势股和弱势股与大盘情形迥然不同, 平衡市与趋势运行也有所不同。面对不断变化的市场, 一旦发现原参数不太适合当时的市势,大家就需要对该参数进行修正以便优化指标,这应是一项长期的工作, 中短期参数更是如此。

六、不同周期参数的KDJ指标的研判

(一)34日KDJ(注:这是钱龙软件上的KDJ参数)的使用方法

对于大部分股市分析软件,投资者都可以根据不同的股票、股票不同时期的走势以及投资者的兴趣偏好来修改指标,从而得出不同的分析结论。把日KDJ的数值修改为34,下面在这里简单介绍34日KDJ使 用方法。

1、首先,把KDJ运行的区间分为20以下极弱势区,20——50之间为弱势区,50——80之间强势区和80以上极强势区,数值50是强弱的分界 线。

34日KDJ指标中的50线对于从弱势区起来的股价来说是一个强大的短线阻力位,只要KDJ中的D值没有有效冲上50位值,就不能说股价已经走出弱 势区,KD两值在此点位无功而返是非常正常的事,所以必须十分重视此阻力位的作用,很多股票的反弹都在此点位就结束。

34日KDJ指标中的50线对于从强势区跌下来的股价而言,虽有些支撑,却较为轻易击破,显得并没有重要参考意义。这与股价上涨要成交量配合,而下 跌没有量也能做到是同样的道理。

2、KDJ三值在极弱势区20以下运行时,一般不作买入计划,持币观望为主,特别是股价刚刚经历过一轮较长时间的下跌。除非是股价经过大跌并在低位 盘整了很长时间后,如果有反转意义的低位放量大阳线出现,或有反转意义K线组合形态明确告诉投资者进场。

3、KDJ三值在极强势区80以上运行时,一般不作卖出计划,以持股待涨为主。除非有反转意义的高位放量大阴线乌云盖顶,穿头破脚)出现,或有反转意义的K线组合形态明确告诉投资者离场。

4、KDJ在20以下极弱势区运行的时间越长,在极弱势区内发出的金叉信号越应该重视,特别是那些向上角度较大、能迅速脱离20线的金叉,K线组合 形态也在明确告诉我们有反转的可能,更应该重视此金叉的意义,这很有可能是中短期底部形成的第一个信号。

5、KDJ在80以上极强势区运行的时间越长,在极强势区内发出的死叉信号越应该重视,特别是那些向下的角度较大能迅速跌破80线的死叉,K线形态 组合也在明确告诉我们有反转的可能,更应该重视此死叉的意义,这很有可能是顶部形成的第一个信号。

6、KDJ在20以上(弱势区)和50以上(强势区)的每次KDJ金叉都是介入机会,金叉的位置越高市场意义越大,同时还必须关注KDJ金叉角度大 小,角度越大越好。另外,还要注意K线形态组合和成交量配合情况。如我们在高位KDJ金叉(80线以上)介入之后不久却又看见KDJ死叉,投资者要立即止损离场,防止扩大损失。但20以下的金叉如无KD两值迅速脱离20极弱势区或K线组合形态确认反转,无多大市 场意义。

7、KDJ在80以下(强势区)和50以下(弱势区)的每次KDJ死叉都是必须离场的信号,死叉的位值越高下跌的风险也越大。但KDJ在20以下的极弱势区内的死叉,如果KD两值没有继续走低则无多大参考意义。

8、股价短期暴涨,会引起KDJ滞后的连续上升,这时我们要保持冷静,切忌盲目追涨,此时股价走势可能正与KDJ的走势相反,耐心等待KDJ走势与 股价走势的同步运行。然后再寻找介入机会,同样股价的短期暴跌,也会引起KDJ的滞后下降,股价也会出现暴跌后的反弹走势是与KDJ走势相反的情况。当 KDJ走势与股价走势同步时,正是我们反弹离场之时。

9、在下跌的市道中,反弹时产生的KDJ金叉位置也会越来越低,从而引起KDJ的死叉位置也会越来越低,在上涨的市道中,回调时产生的KDJ死叉位 置越来越高,从而引起KDJ的金叉位置也会越来越高。

10、KDJ在冲出极弱势区20以后,KDJ会出现报复性的上窜,即股价并没有上涨很多,KDJ就很快进入强势区,这时我们要注意KDJ的回调整 理,要注意KDJ在强势区的第二次金叉,因为第二次金叉的介入意义会比每一次更大。更有上涨空间。

11、KDJ的使用只是作为一种辅助手段而存在,投资者应参考股价的K线组合,量价关系,趋势形态与KDJ指标一起研判,当它们发出的买卖信号趋于 一致之时,这样的买卖信号比较可靠。

(二)89日KDJ(注:这是钱龙软件上的KDJ参数)参数的使用方法

如果把34日KDJ指标作为一个比较有实际研判意义的KDJ短线指标,那么89日KDJ指标就是一个比较重要的KDJ中线研判指标,它和34日 KDJ指标在研判上虽有很多相似的地方,但也有不少不同步骤,具体研判如下:

1、和34日KDJ指标的研判相同的是,89日KDJ运行区间也分为20以下极弱势区,20——50之间为弱势区,50——80之间强势区和80以 上极强势区,数值50是强弱的分界线。

89日KDJ指标中的50线对于从弱势区起来的股价来说是一个强大的短线阻力位,只要KDJ中的D值没有有效冲上50位值,就不能说股价已经走出弱 势区,KD两值在此点位无功而返是非常正常的事,所以必须十分重视此阻力位的作用,很多股票的反弹都在此点位就结束。

89日KDJ指标中的50线对于从强势区跌下来的股价而言,虽有些支撑,却较为轻易击破,显得并没有重要参考意义。这与股价上涨要成交量配合,而下 跌没有量也能做到是同样的道理。

2、要关注0—20区间的KDJ,越靠近底部股价越有上升力度。和34日KDJ指标一样, 89日KDJ在20以下极弱势区运行的时间越长,在极弱势区内发出的金义越应该重视,特别是那些向上角度较大、能迅速脱离20线的金叉,K线组合形态也在 明确告诉我们有反转的可能,更应该重视此金叉的意义,这很有可能是中长期底部形成的第一个信号。

3、89日KDJ指标中的80这条强弱分界线对于中线卖出股票有着重要的指导意义。只要89KDJ曲线没有向下突破80区间,就可以一路持股,直到 该指标中的J线、K线、D线都向下跌破80这条多方防线以后,就可以中线卖出全部或大部分股票。

4、89日KDJ中的K、D、J这三条曲线在80以上极强势区运行的时间越长,那么它们在极强势区内发出的死叉越应该重视,特别是那些向下的角度较 大能迅速跌破80线的死叉,K线形态组合也在明确告诉我们有反转的可能,更应该重视此死叉的意义,这很有可能是中长期顶部形成的第一个信号。

5、当89日KDJ指标在80以上的高位死叉、并开始朝下发散时,应立刻卖出余下的股票更不要轻易买进股票(死叉向下发散应观望)

6、一旦89日KDJ指标在20以下金叉并开始向上发散,不要轻易卖出股票,直到确定它高位死叉。

(三)55周KDJ(注:这是钱龙软件上的KDJ参数)使用方法

相比较34日和89日KDJ指标,55周KDJ指标就是KDJ指标研判中的一种长期指标,它对于研判一个股票未来半年以上的走势具有很强的参考意 义。

1、周K线(55周)KDJ的使用方法与日K线(34天)KDJ的使用方法大同小异。

首先,和日KDJ指标研判一样,把周KDJ运行的区间分为20以下极弱势区,20——50之间的为弱势区,50——80之间的为强势区和80以上极 强势区,50线是强弱的分界线。

周KDJ指标中的50线对于从弱势区涨起来的股价来说是一个强大的阻力位,只要KDJ中的D值没有有效冲上50数值,就不能说股价已经走出弱势 区,KD两值在此点位无功而返是非常正常的事,很多股票的反弹都在此点位就结束。

周KDJ指标中的50线对于从强势区跌下来的股价而言,是一个较强的支撑位。如果周KDJ指标在50线附近获得支撑,股价的中长线还是看好,而一旦 KDJ曲线再次跌破50线,则表明股价的中长线依然看坏。

2、周(55周)KDJ在80以下任何区域的金叉和在K线形态量价支持下的J值强力拐头都是介入持仓信号。

金叉的数值越低市场意义越大,同时还必须关注KDJ金叉角度大小,角度越大越好,另外还要注意K线形态组合和成交量配合情况。如我们在KDJ金叉介 入之后KDJ又出现死叉的,要立即止损离场。

3、周K线(55周)KDJ在50以上区域的死叉和在K线形态破位时的J值强力拐头都是离场止损信号。死叉的位值越高下跌的风险也越大。

4、周K线(55周)KDJ三值在极弱势区20以下运行时,一般不作买入计划,持币观望是上上策。除非有反转意义的低位放量大阳线(一阳指)出现, 或有反转意义K线组合形态明确告诉我们进场。

5、周K线(55周)KDJ三值在极强势区80以上运行时,一般不作卖出计划,持股待涨是上上策。除非有反转意义的高位放量大阴线(乌云盖顶,穿头 破脚)出现,或有反转意义的K线组合形态明确告诉我们退场。

6、股价短期暴涨,会引起KDJ滞后的连续上升,这一点比日(34)KDJ更明显,这时我们要保持冷静,切忌盲目追涨,此时股价走势可能正与KDJ 的走势相反,耐心等待KDJ走势与股价走势的同步运行。然后再寻找介入机会,同样股价的短期暴跌,也会引起KDJ的滞后下降,股价也会出现暴跌后的反弹走 势是与KDJ走势相反的情况。当KDJ走势与股价走势同步时,正是我们反弹离场之时。

7、KDJ死叉的位值越高,对股价的风险就越大,同样KDJ金叉的位值越低,对股价的上涨就越有保障。在下跌的市道中,反弹时产生的KDJ金叉位值 也会越来越低,从而引起KDJ的死叉位值也会越来越低,在上涨的市道中,回调时产生的KDJ死叉位值越来越高,从而引起KDJ的金叉位值也会越来越高。

8、KDJ在冲出极弱势区20以后,KDJ会出现报复性的上窜,即股价并没有上涨很多,KDJ就很快进入强势区,这时我们要注意KDJ的回调整理, 要注意KDJ在强势区的第二次金叉,因为第二次金叉的介入意义会比每一次更大、更有上涨空间。

9、KDJ的使用只是作为一种辅助手段而存在,特别对于周K线KDJ来说,滞后效应更明显,决不能盲目根据周K线KDJ来选择进出点,但周K线 KDJ也有它的优点,那就是它的稳定性,中长期趋势的指向性比较明确。

2009年12月8日星期二

C++模板使用介绍

1. 模板的概念。

我们已经学过重载(Overloading),对重载函数而言,C++的检查机制能通过函数参数的不同及所属类的不同。正确的调用重载函数。例如,为求两个数的最大值,我们定义MAX()函数需要对不同的数据类型分别定义不同重载(Overload)版本。

//函数1.

int max(int x,int y);
{return(x>y)?x:y ;}

//函数2.
float max( float x,float y){
return (x>y)? x:y ;}

//函数3.
double max(double x,double y)
{return (c>y)? x:y ;}

但如果在主函数中,我们分别定义了 char a,b; 那么在执行max(a,b);时 程序就会出错,因为我们没有定义char类型的重载版本。

现在,我们再重新审视上述的max()函数,它们都具有同样的功能,即求两个数的最大值,能否只写一套代码解决这个问题呢?这样就会避免因重载函数定义不 全面而带来的调用错误。为解决上述问题C++引入模板机制,模板定义:模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。

2.   函数模板的写法

函数模板的一般形式如下:

Template <class或者也可以用typename T>

返回类型 函数名(形参表)
{//
函数定义体 }

说明: template是一个声明模板的关键字,表示声明一个模板关键字class不能省略,如果类型形参多余一个 ,每个形参前都要加class <类型 形参表>可以包含基本数据类型可以包含类类型.

请看以下程序:

//Test.cpp

#include <iostream>

using std::cout;

using std::endl;

//声明一个函数模版,用来比较输入的两个相同数据类型的参数的大小,class也可以被typename代替,

//T可以被任何字母或者数字代替。

template <class T>

T min(T x,T y)

{ return(x<y)?x:y;}

void main( )

{

     int n1=2,n2=10;

     double d1=1.5,d2=5.6;

     cout<< "较小整数:"<<min(n1,n2)<<endl;

     cout<< "较小实数:"<<min(d1,d2)<<endl;

     system("PAUSE");

}

程序运行结果: 

 

程序分析:main()函数中定义了两个整型变量n1 , n2 两个双精度类型变量d1 , d2然后调用min( n1, n2); 即实例化函数模板T min(T x, T y)其中T为int型,求出n1,n2中的最小值.同理调用min(d1,d2)时,求出d1,d2中的最小值.

3. 类模板的写法

定义一个类模板:

Template < class或者也可以用typename T >
class
类名{
//类定义......
};

说明:其中,template是声明各模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个。

例如:定义一个类模板:

// ClassTemplate.h
#ifndef ClassTemplate_HH

#define ClassTemplate_HH

template<typename T1,typename T2>

class myClass{

private:

     T1 I;

     T2 J;

public:

     myClass(T1 a, T2 b);//Constructor

     void show();

};

//这是构造函数

//注意这些格式

template <typename T1,typename T2>

myClass<T1,T2>::myClass(T1 a,T2 b):I(a),J(b){}

//这是void show();

template <typename T1,typename T2>

void myClass<T1,T2>::show()

{

     cout<<"I="<<I<<", J="<<J<<endl;

}

类模板与模板类

原创  类模板与模板类

1.类模板与模板类的概念

什么是类模板 一个类模板(也称为类属类或类生成类)允许用户为类定义一种模式,使得类中的某些数据成员、默写成员函数的参数、某些成员函数的返回值,能够取任意类型(包括系统预定义的和用户自定义的)。

  如果一个类中数据成员的数据类型不能确定,或者是某个成员函数的参数或返回值的类型不能确定,就必须将此类声明为模板,它的存在不是代表一个具体的、实际的类,而是代表着一类类。

类模板定义 定义一个类模板,一般有两方面的内容:

A.       首先要定义类,其格式为:

template <class T>

class foo

{

……

}

foo 为类名,在类定义体中,如采用通用数据类型的成员,函数参数的前面需加上T,其中通用类型T可以作为普通成员变量的类型,还可以作为conststatic成员变量以及成员函数的参数和返回类型之用。例如:

template<class T>

class Test{

private:

    T n;

    const T i;

    static T cnt;

public:

    Test():i(0){}

    Test(T k);

    ~Test(){}

    void print();

    T operator+(T x);

};

B.       在类定义体外定义成员函数时,若此成员函数中有模板参数存在,则除了需要和一般类的体外定义成员函数一样的定义外,还需在函数体外进行模板声明

例如

template<class T>

void Test<T>::print(){

    std::cout<<"n="<<n<<std::endl;

    std::cout<<"i="<<i<<std::endl;

    std::cout<<"cnt="<<cnt<<std::endl;

 

}

如果函数是以通用类型为返回类型,则要在函数名前的类名后缀上"<T>"。例如:

template<class T>

Test<T>::Test(T k):i(k){n=k;cnt++;}

template<class T>

T Test<T>::operator+(T x){

               return n + x;

               }

C.       在类定义体外初始化const成员和static成员变量的做法和普通类体外初始化const成员和static成员变量的做法基本上是一样的,唯一的区别是需在对模板进行声明,例如

template<class T>

int Test<T>::cnt=0;

template<class T>

Test<T>::Test(T k):i(k){n=k;cnt++;}

类模板的使用 类模板的使用实际上是将类模板实例化成一个具体的类,它的格式为:类名<实际的类型>

  模板类是类模板实例化后的一个产物。说个形象点的例子吧。我把类模板比作一个做饼干同的模子,而模板类就是用这个模子做出来的饼干,至于这个饼干是什么味道的就要看你自己在实例化时用的是什么材料了,你可以做巧克力饼干,也可以做豆沙饼干,这些饼干的除了材料不一样外,其他的东西都是一样的了。

2.类模板的派生

  可以从类模板派生出新的类,既可以派生类模板,也可以派生非模板类。派生方法:

⑴ 从类模板派生类模板可以从类模板派生出新的类模板,它的派生格式如下例所示:

template <class T>

class base

{

……

};

 

template <class T>

class derive:public base<T>

{

……

};

与一般的类派生定义相似,只是在指出它的基类时要缀上模板参数,即base<T>

⑵ 从类模板派生非模板类  可以从类模板派生出非模板类,在派生中,作为非模板类的基类,必须是类模板实例化后的模板类,并且在定义派生类前不需要模板声明语句:template<class>。例如:

template <class T>

class base

{

……

};

 

class derive:public base<int>

{

……

};

在定义derive类时,base已实例化成了int型的模板类。

 

2009年6月27日星期六

FLEXLM9.5该洗洗睡了吧---200水帖纪念

FLEXLM9.5该洗洗睡了吧---200水帖纪念

【郑重声明】:本文纯属技术交流,无其他目的。 请勿用于商业或其他非法用途,否则后果自负。转载需征得看雪论坛同意。
【软件名称】:Imaris 4.16版本,官方网站有DOWN。
【下载页面】:http://www1.skycn.com/soft/20059.html
【软件简介】:Flexlm9.5保护,医学用(汗,我不懂-:))
【调试环境】:XP、OLLYICE、PEID0.93、calcseed.exe、lmcryptgui.exe、SIG文件
不会写破文,发了199个水帖,一直潜水不敢再发。据说200帖就可以成为中级了,我汗---我这么菜水平的都成中级?细想一下,我想看雪上的级别资格更多的是给那些有所奉献的朋友吧,不完全代表水平,于是菜鸟我心释然。
论坛上有很多值得我尊敬的人:看雪、fly、一二三水(哈哈)、laoqian等,无私奉献技术,指导菜鸟提高。当然,我在学习FLEXLM的时候,请教时也曾遇到有人索要RMB的问题(至今心里忿然BS)。学CRACK仅仅是我的兴趣而非职业,是想了解别人的思想,因此绝不会靠CRACK去谋取一分钱,这也不是我学习的目的。
受大水写了第500帖纪念的启发,于是发第200水帖纪念,希望大锅们不要扁我哈。论坛上有很多水牛,发了那么多帖子,按说水平也该很高了,希望大家也能在整百帖的时候发帖纪念一下,总结一下自己的经验,共同提高!
转入正题:本文是学习CRACKZ的文章《FLEXlm latest information by CrackZ》后总结的。同样的软件,版本不同。

一、信息收集

1、-8,著名标志,许可证密码错误(0xFFFFFFF8 如果爆破的话,有用)
2、-5,著名标志,FEATURE名称错误
3、-2,许可证语法错误
4、验证逻辑:
如果许可证里面给的种子不对,做出的许可证即使过期了,也会提示-8错误,表示许可证密码无效。这说明是先进行种子参与的密码运算,接着进行密码验证;而后,才是进行日期检查。
如果给出的种子对了,而日期过期了,则会提示-10,表示FEATUER到期;也说明先进行密码检查,后进行日期检查。哈哈。
5、使用假的许可证,启动后出现提示-8提示,表示密码不正确
6、通过分析,比较容易发现VENDOR DAEMON是哪个可执行程序。
7、对于多进程情况,直接调试主干程序;如果调试创建进程的程序,则可能不中断,因为新进程在附加前已经完成了许可证检查
8、认真读看雪大锅的书,学习FLEXLM的基本知识。另外CRACKZ上也有不少好文章。

二、制作一个假的许可证FAKE.LIC

FEATURE feature_name vendor_name 1.000 permanent uncounted 123456654321\
HOSTID=ANY SIGN=123456789123
格式一定要正确。如果用lmcryptgui.exe检查许可证时提示格式错误,它会自动校正;将前面的错误信息删除,再检查一次,则会成功。

三、确定VEDNDOR NAME

目的:确定VENDOR NAME
方法:搜索字符串"DEMO",其前面的je跳转处会出现VENDOR NAME名称。(其实程序中多处出现,该步可略去)

006CE285 |. 05 0C030000 add eax, 30C
006CE28A |. 85C0 test eax, eax
006CE28C |. 74 2B je short 006CE2B9
确定VENDOR=bitplane
006CE28E |. B9 B8A28E00 mov ecx, 008EA2B8 ;
ASCII "demo"
006CE293 |. 85C9 test ecx, ecx
006CE295 |. 74 22 je short 006CE2B9
006CE297 |. 68 C0A28E00 push 008EA2C0 ;
/s2 = "demo"
006CE29C |. 8B95 6CFDFFFF mov edx, [ebp-294] ; |
006CE2A2 |. 81C2 0C030000 add edx, 30C ; |

四、定位_l_init()函数

函数目的:定位_l_init()函数可确定_l_sg函数,确定VENDOR NAME
方法:查找默认的种子0x12345678 & 0x87654321 可以确定_l_init() 函数

该函数调用时堆栈出现正确的VENDOR NAME;
006CE309 |. 51 push ecx ; |Arg2
006CE30A |. 8B95 6CFDFFFF mov edx, [ebp-294] ; |
006CE310 |. 52 push edx ;
|lc_init函数
006CE311 |. E8 453DFFFF call 006C205B ;
\调用_l_sg函数,F7跟进
006CE316 |. 83C4 0C add esp, 0C
006CE319 |. 81BD 84FDFFFF>cmp dword ptr [ebp-27C], 87654321 ;
默认的种子1,定位标志1
006CE323 |. 74 0C je short 006CE331
006CE325 |. 81BD 88FDFFFF>cmp dword ptr [ebp-278], 12345678 ;
默认的种子2,定位标志2
006CE32F |. 75 5D jnz short 006CE38E
006CE331 |> 8B85 6CFDFFFF mov eax, [ebp-294]
注意调用_l_sg函数完毕后不会填入正确的种子,因为在_l_sg函数中会提前跳转

五、定位_lc_checkout()函数以及对其的调用处

目的:确定FEATURE和产品版本号(不是FLEXLM的版本号)
方法:在字符串参考中查找'lm_ckout.c';总共也就出现几次,比较容易确定。一般是挨着的两个才是;另外有四个挨着的不是。
定位_lc_checkout()函数比较容易,可以执行到其返回,再F8,就可以确定主程序中对它的调用处。(直接查找可能有多处,运行一次找到)006BE4C0
/$ 55 push ebp
1、定位_lc_checkout函数
006BE4E8 |. 8990 FC030000 mov [eax+3FC], edx
006BE4EE |. 68 95000000 push 95 ;
/Arg3 = 00000095
006BE4F3 |. 68 8C9B8E00 push 008E9B8C ; |Arg2
= 008E9B8C ASCII "lm_ckout.c",确定标志1,
//堆栈和寄存器出现FEATURE
006BE4F8 |. 8B4D 08 mov ecx, [ebp+8] ; |
。。。。。
006BE543 |. E8 B3000000 call 006BE5FB ;
\堆栈出现产品版本和FEATUER名称
。。。。。
006BE5A6 |. E8 50000000 call 006BE5FB ;
\如果许可证密码错误,这里会间接调用_l_sg函数
(跟进发现有对l_valid_version 验证版本;lc_check_key 调用_l_sg函数等函数的调用)
(!!!注意:在这里初次不会调用该CALL。慢慢来,呵呵。我们在该函数里能得到FEATURE和版本号就足够了!)
。。。。。
006BE5DE |. 68 A6000000 push 0A6 ;
/Arg3 = 000000A6
006BE5E3 |. 68 989B8E00 push 008E9B98 ;
|Arg2 = 008E9B98 ASCII "lm_ckout.c 确定标志2
006BE5E8 |. 8B55 08 mov edx, [ebp+8] ; |
2、定位对其的调用
0041E785 . 51 push ecx
0041E786 . 53 push ebx
0041E787 . 52 push edx
0041E788 . E8 33FD2900 call 006BE4C0 ;
调用_l_checkout
0041E78D . 8BE8 mov ebp, eax ; |
0041E78F . 8B46 2C mov eax, [esi+2C] ; |
0041E792 . 53 push ebx ; |Arg2
0041E793 . 50 push eax ; |Arg1
0041E794 . E8 BDEC2900 call <_lc_hostid 009E:_l_hostid> ;
\堆栈出现产品版本号与FEATURE
0041E799 . 8B4E 08 mov ecx, [esi+8]
0041E79C . 83C4 24 add esp, 24
记录前面的版本号和FEATURE(有的软件可能有几个产品版本号)

六、修改假的许可证

为什么我们在前面不能再次调用_l_sg函数?这是因为FLEXLM的验证机制,如果FEATUER和产品号验证不正确,眼看要煮熟的鸭子_l_sg就直接飞了,就不会再断下来!
修改许可证,_l_sg函数就可以再次停下来了,这就相当于帅哥有钱,MM停下来;看来造假还是不好,来接近于真的:
FEATURE ImarisBase bitplane 4.1 permanent uncounted 123456654321\
HOSTID=ANY SIGN=123456789123

七、定位_l_sg函数及其返回

函数目的:_l_sg的意思是"签名vendor_key5",呵呵。其返回处是恢复加密种子的关键!
方法:搜索常量SEEDVAL,9.2版本的是6F7330B8;注意,有两个出现,其中一个是混淆函数_l_svk()里面的。可以在?_l_init函数里面通过F7跟进确定,也结合后面的固定结构
V7 glseed?=?0x788F71D2 seedval?=?0x7648B98E
V8 glseed?=?0x3CDE3EBF seedval?=?0x6F73330B8
V9 glseed?=?0x72346B53 seedval?=?0x6F7330B8
v10 glseed?=?0x5332322F seedval?=?0x6F7330B8
(seedval:用于确定_l_sg函数;glseed:可以用来确定vendor name,堆栈出现)
(这些值最早出现于WOODOOM网站,laoqian大锅的总结一文也提及了-惠及众生,爽)
可以执行到返回,然后用dd命令
1、定位函数
006C205B /$ 55 push ebp ; _l_sg函数
。。。。。
006C206B |. 8845 EF mov [ebp-11], al
006C206E |. C745 F4 B8307>mov dword ptr [ebp-C], 6F7330B8 ;
seedval 寻找L_SG,注意混淆函数
006C2075 |. C745 FC 00000>mov dword ptr [ebp-4], 0
。。。。。
006C21D7 |. 5D pop ebp ;
_l_sg函数返回

2、定位函数的返回:
006C02FF |. 8B45 08 mov eax, [ebp+8] ; |
006C0302 |. 50 push eax ; |Arg1
006C0303 |. E8 531D0000 call 006C205B ;
\调用_l_sg函数
006C0308 |. 83C4 0C add esp, 0C ;
返回到这里,该处下断,用dd [esp]和dd [esp+8]命令
006C030B |. 8B4D 08 mov ecx, [ebp+8]

八、追踪加密种子和许可证

方法:在调用_l_sg函数的返回处(后面一般是一个add…命令,就可以在add命令处下断,使用dd命令),用dd [esp] dd
[esp+8]命令查看内存中的内容。
dd [esp] 得到的是00000066。。。job[]结构内容
dd [esp+8] 得到的是00000004。。。data[]内容
如果是正确的许可证,与调用_l_sg函数的次数关系不大,除了_l_init函数不填充正确的job结构外,每一个FEATUER都要调用_l_sg函数一次;
其返回值将是正确的job和data结构数据;也就是说,每个FEATUER对应的job和data值不相等,但用它们算出的加密种子却一样。
1、追踪加密种子
在_l_sg函数的返回处,使用dd命令
006C0308 |. 83C4 0C add esp, 0C ;
返回到这里,该处下断,用dd [esp]和dd [esp+8]命令

dd [esp]
03A7C350 00000066
03A7C354 00000000
03A7C358 ******** job+08
03A7C35C ******** job+0c
03A7C360 ******** job+10
03A7C364 00000000

dd [esp+8]
0012F5CC 00000004
0012F5D0 ******** data[0]
0012F5D4 ******** data[1]
0012F5D8 18A011ED
0012F5DC 274BE197
0012F5E0 E26A21A8
0012F5E4 52580441
0012F5E8 00020009

用calcseed.exe计算加密种子
vendor=bitplane
enseed1=******* enseed2=******** 与VENDOR NAME的关系是仅仅与其第一个字母有关

vendor=Bitplane

追出的VENDOR NAME是小写:bitplane,算出的LIC正确;
而网上有DOWN的许可证,第一个字母是大写的VENDOR
NAME:Bitplane也正确。这里将VENDOR换成大、小写都没有关系,但用小写VENDOR计算的加密SEEDS。
2、做出许可证
用lmcryptgui.exe。具体方法参见laoqian大锅的文章。
用SDK也可以制作,只不过在lm_code.h中要添加一些信息,麻烦哦。我懒,如果你做好了,别忘了送我一份啊:)

后记:该文的方法对于7.2、8.0、9.2的版本都适用。7之前的版本应该也可以用该方法,因为FLEXLM的某些关键结构(哈哈)一直没有改变!这是通用的方法。
本想做个循序渐进册子,突然发现该方法其实对老版本的_l_init,_l_new_job等方法都包容了。
可以这样说,对于没有使用ECC等技术(需要PATCH,SIGN超过12位则使用了该技术,以后讨论)的情况,FLEXLM就像一张纸。
关键点:定位对_l_sg函数的调用返回处,逐步修改假许可证(格式正确)中的内容,就会在_l_sg函数处停下来恢复加密的种子。

感谢

CrackZ
Nolan Blender
tulipfan[CCG]
laoqian
看雪论坛上的朋友们!

附:一些常见问题的处理,希望大家补充:
1、FLEXLM追出了正确加密种子,7.2等SDK做不出正确的许可证?
如果能正确启动genlic32.exe,恭喜你找对了加密的种子,试试添加选项"advanced->Add Compatible License Key"
2、_l?_sg函数函数只调用一次,不来第二次?
CRACKZ的文章也曾提及,但没有说明原因和处理办法。常见处理:
A:许可证格式要正确
B:设置正确的环境变量,可在"我的电脑->属性"里面或者autoexec.bat里面设置
C:许可证里面逐步填入正确的VENDOR,FEATUER,产品版本号等
CRACKZ的另外一篇文章还提及了另外一种处理办法:终止DAEMON进程,在许可证里面加入UER_...信息,但我没有用它试验成功过。
3、如何让后台程序安息?
运行程序,会发现至少有三样不少:lmgrd.exe是一陪,vendor daemon是二陪,Cracker是"三陪",哈哈。
将许可证做成无任何限制的格式,前面两陪直接上床睡觉(进程不启动),晕吧。如果不知道什么是无任何限制的格式,赶快买看雪大锅的书看看(书店一般脱销,我托人才买到)。
4、有了正确的许可证,怎么找加密种子?
实在恭喜你,省事啊。VENDOR,FEATURE等等都不用找了。
一般情况了,有了正确的许可证,_l_sg函数会在每个FEATURE的验证后返回正确的值,用通用的种子恢复技术就可以了。
5、相互交流,才能共同提高!仅作技术交流,恳请不要用于非法用途!

2009年6月25日星期四

翻译FLEXlm9.2的破解教学六

  对于4个Vendor密钥,很明显它们是初始化时在l_n36_buf()中被模糊的,在l_xorname()中进行模糊逆向,l_xorname()未做别的仅进行了一些与或操作。我们都知道与或的特征:x ^ x = 0, x ^ 0 = x, x ^ 1 = ~x,因此进行两次相同的与或操作将相互取消。这个好特性将与或适用于编码和译码。如果我们的猜测对的话,那么l_n36_buf()将采用与 l_good_lic_key()相同的方式调用l_xorname()。因为l_n36_buf()驻留于lm_new.c,它由 lmnewgen.exe产生,因此直接检查lmnewgen.c更好。


#include "l_strkey.h"
... ...
int main(int argc, char **argv)
{
      ... ...
      strcpy(outfile, "lm_new.c");
      ... ...
/* 初始化工作,能报告错误并能退出 */
if (lc_init(0, vendor_name, &vendorkeys[0], &job))
      {  
fprintf(stderr, "lc_init failed: %s\n", lc_errstring(job));
         exit(1);
      }  
/* 通过sb_rngFIPS186Session()产生的随机数 */
l_genrand(job, lmseed1, lmseed2, lmseed3, NEWSEEDSIZ, newseeds);
      ... ... /* 把新种子转换为seed1 ? seed4,及其它*/
/* 把4个加密种子写进lmseeds.h */
if (!(ofp = fopen("lmseeds.h", "w")))
      {  
perror("Can't write lmseeds.h file, exiting");
         exit(1);
      }  
fprintf(ofp, "... ...", seed1, seed2, seed3, seed4);
      fclose(ofp);

/*现在是 lm_new.c */
if (!(ofp = fopen(outfile, "w")))
      {  
perror("Can't open output file, exiting");
         exit(1);
      }  
      ... ...
/* 确定缺省的和弱的种子被排除在外 */
if (!l_reasonable_seed(seed3) || !l_reasonable_seed(seed4) ||
         !l_reasonable_seed(lmseed1) || !l_reasonable_seed(lmseed2) ||
         !l_reasonable_seed(lmseed3))
      {  
         ... ...
         fprintf(stderr, "Existing.\n");
         exit(1);
      }  
      ... ...
      fputs("#include <time.h>\n", ofp);
      do_real(); /* 写lm_new.c 的主要内容*/
      fclose(ofp);
      return 0;
}

static void do_real()
{
      ... ... /* 产生随机的参数和函数名称 */
      while (!key5_uniqx)
      {  
key5_uniqx = our_rand(256) + (our_rand(256) << 8) +
                  (our_rand(256) << 16) + (our_rand(256) << 24);
      }  
      ... ... /* 产生随机的 key5_order[] */
for (i = 0; i < 200; i += 2)
         random_garbage();
      ... ... /* 更多的随机垃圾 */
for (counter=0; counter<keysize; counter++)/* 一般 keysize=1, 只有一个vendor */
      {  
         key5(&vendorkeys[counter]);  /* 模糊化vendorkeys->data[] */
         l_xorname(vname, &vendorkeys[counter]);/* 模糊化vendorkeys->keys[] */
/* 真正VENDORCODE的初始化 */
         do_ulong(vendorkeys[counter].data[0], "data[0]", counter);
         do_ulong(vendorkeys[counter].data[1], "data[1]", counter);
         do_ulong(vendorkeys[counter].keys[0], "keys[0]", counter);
         do_ulong(vendorkeys[counter].keys[1], "keys[1]", counter);
         do_ulong(vendorkeys[counter].keys[2], "keys[2]", counter);
         do_ulong(vendorkeys[counter].keys[3], "keys[3]", counter);
         ... ...
      }  
      l_puts_rand(ofp, fpVar, numvars); /* 以随机的顺序输出行 */
      fflush(ofp);
      uniqcode(); /* 输出lm_new.c!l_n36_buff()的源代码 */
      fflush(ofp);
      ... ...
}

static void do_ulong(unsigned long ul, char *varname, int counter)
{
      ... ...
randvarname(b1, "b1"); bnames[0] = b1; /* 产生混乱的参数名称 */
randvarname(b2, "b2"); bnames[1] = b2;
randvarname(b3, "b3"); bnames[2] = b3;
randvarname(b4, "b4"); bnames[3] = b4;

for (i = 0; i < 4; i++)
      {  
shift = i * 8;

         CLEARV; /* 把ul分成4 字节并且把它们指派为4个糊乱命名的参数 */
sprintf(vBuf, "static unsigned int %s = %d;\n",
bnames[i], (ul & (0xff << shift)) >> shift );
         fwrite(vBuf, sizeof(char), sizeof(vBuf), fpVar);

         CLEARF; /* 重新组装回4个参数,使v->varname = ul */
sprintf(fBuf, "\tif (%s == %d) v->%s += (%s << %d);\n",
               countervar, counter, varname, bnames[i], shift);
         fwrite(fBuf, sizeof(char), sizeof(fBuf), fpFunc);
      }  
}

static void uniqcode()
{
      ... ...
int idx = *vendor_name % XOR_SEEDS_ARRAY_SIZ; /* idx = V % 20 = 86 % 20 = 6 */
      XOR_SEEDS_INIT_ARRAY(xor_arr) /* l_strkey.h */

fprintf(ofp, "static void %s(job, vendor_id, key) \n\
            ... ...
unsigned long x = 0x%x; \n\
struct s_tmp {int i; char *cp; unsigned char a[12];} *t, t2; \n\
if (job) t = (struct s_tmp *)job; \n\
               else t = &t2; \n\
            ... ...", key5_fname, key5_uniqx); /* key5_fname 和key5_uniqx都是随机数*/
for(i = 0; i < SEEDS_XOR_NUM; i++) /* 解码 t->a[], i.e. job->mem_ptr2_bytes[] */
      {  
         unsigned char num;
         cpp[i] = cp;
sprintf(cp, "t->cp=(char *)(((long)t->cp) ^ (time(0) ^ ((0x%x << 16) + 0x%x)));\n\
t->a[%d] = (time(0) & 0xff) ^ 0x%x;\n", /* 运行时随机化 */
               our_rand(0xff), our_rand(0xff), i, our_rand(0xff));
cp += strlen(cp) + 1;
      }  
      l_puts_rand1(ofp, SEEDS_XOR_NUM, cpp); /* 以随机的顺序输出行*/

fprintf(ofp, "for (i = 0; i < %d; i++) \n\
            { \n\
               if (sig[i%%SIGSIZE] != vendor_id[i%%len]) \n\
                  sig[i%%SIGSIZE] ^= vendor_id[i%%len]; \n\
            } \n\
unsigned long y = ((((long)sig[0] << %d) \n\
| ((long)sig[1] << %d) \n\
   | ((long)sig[2] << %d) \n\
| ((long)sig[3] << %d)) \n\
^ ((long)(t->a[%d]) <<  0) \n\
^ ((long)(t->a[%d]) <<  8) \n\
^ ((long)(t->a[%d]) << 16) \n\
^ ((long)(t->a[%d]) << 24) \n\
^ x \n\
^ key->keys[0] \n\
^ key->keys[1]) & 0xffffffff; \n\
key->data[0] ^= y; \n\
key->data[1] ^= y; \n\
      ... ...", MAX_DAEMON_NAME, /* MAX_DAEMON_NAME = 10 在lmclient.h中定义 */
key5_order[0], key5_order[1], key5_order[2], key5_order[3],
xor_arr[idx][0], xor_arr[idx][1], xor_arr[idx][2], xor_arr[idx][3]));
}

static void key5(VENDORCODE *k) /* 模糊化加密种子, 即 k->data[] */
{
      ... ...
/* 和uniqcode()中相同的key5_uniqx, key5_order[], sig[]和MAX_DAEMON_NAME*/
for (i = 0; i < MAX_DAEMON_NAME; i++)
      {  
if (sig[i%SIGSIZE] != vname[i % len])
            sig[i%SIGSIZE] ^= vname[i % len];
      }  
y = ((((long)sig[0] << key5_order[0])
         | ((long)sig[1] << key5_order[1])
         | ((long)sig[2] << key5_order[2])
         | ((long)sig[3] << key5_order[3]))
         ^ key5_uniqx
         ^ k->keys[0]
         ^ k->keys[1]) & 0xffffffff;
      k->data[0] ^= y; /* k->data[0] = 加密种子SEED1 ^ y */
      k->data[1] ^= y; /* k->data[1] = 加密种子SEED2 ^ y */
}


  源码有点长,但是它的结构一旦被我们揭开就不再非常难懂。主要的过程在lmseeds.h中花费大量时间,并且lm_new.c主要是在 do_real()中产生的。在几个子程序的帮助下,do_real()本身是在制造l_n36_buf(),它把l_n36_buff()留给 uniqcode()。那么l_xorname()干什么用呢? 它虽然没有出现在l_n36_buf()中,但是在do_real()中出现了。因此这种说法是正确的:4个vendor key在不同的位置被同一个异或操作来进行加密和解密,它们分别是 do_real() / l_n36_buf() 和l_good_lic_key() 
  注意lm_new.c本身被高度模糊化:这里有大量完全无用的垃圾代码, 标识符是随机、无任何意义的, 真代码和垃圾代码看上去相似并且混在一起,真代码的行顺序被打乱……。很明显,它以这种方式设计的目的是用来迷惑读者,并且它成功了,因为理解C源码几乎是不可能的,更不用说反编译码了。这正是我们为什么要把注意力放在它的起源lmnewgen.c上的原因, 因为后者能给我们更多提示。例如,l_xorname()在do_real()中被调用,而不是l_n36_buf()。这意味着密钥的模糊化是在vendor site的内部进行的,并且加密后的密钥被链接到传送到终端用户的目标中,然后它们在l_good_lic_key()中被实时解码。这种方案是为了最小化真实密钥的暴露。
  但是,我们对加密种子(encryption seeds)比vendor key更感兴趣。回想一下,job->mem_ptr2_bytes[] 和code->data[] 在验证和密钥产生的过程中是不一样的。很可能 FLEXlm 也使用了异或进行种子加密,但是远不止这些,因为我们在运行时看到它是随机的。我们将逐步分析。
  恰好在do_real()->l_xorname()前面一行,这儿有一处对key5()的调用, 它在vendor那边对原有的加密种子进行模糊化 (在工作结构上没做任何事情). vendor key 的经验告诉我们它们一定在用户方的某个地方进行了反向模糊。这时从事这项工作的不再是 l_good_lic_key(), 而是l_n36_buff(). 它在lm_new.c中的源码是可读的,但我们仍然依然顷向于研究它的产生器 uniqcode()。不多久我们发现l_n36_buff()对一些和key5()中一样的参数进行了异或, 并加上额外的t->a[]。那么它又是什么东西呢?t->a[]正是job->mem_ptr2_bytes[]的另外一个名称. 怎样去解决那些额外的异或呢?让我们翻回去几页…,噢,在l_string_key()中宏L_MOVELONG()处,正好在散列(hashing)开始之前。
  现在我们看到种子加密/解密在两个步骤中完成, l_n36_buf()/l_n36_buff() 和 l_n36_buff() / l_string_key()。在相应函数中的xor代码相互镜像,从而保证清除掉所有的干扰。l_n36_buff()中的time(0)因子引入了运行时的随机性,它同时影响了我们在l_string_key()中看到的code->data[]和 job->mem_ptr2_bytes[] . 确实,与vendor keys相对而言,FLEXlm以一种更加模糊的方式隐藏了加密种子。我们现在知道了lm_new.c中两个名字神秘的函数的意义(事实上只有两个): l_n36_buf() ? 初始化VENDORCODE 、加密种子和密钥;l_n36_buff() ? 解开 l_n36_buf(),并进行第二步加密。
  我们需要强调模糊化只用于checkout过程。在keygen过程中lmcrypt.exe并不调用 l_n36_buf()、 l_n36_buff(),或者 l_good_lic_key(),并且L_MOVELONG对原始种子无任何影响,因为 job->mem_ptr2_bytes[]总是0。在checkout 的加密/解密过程中,xor操作涉及了众多的常数、变量和数组。它们常被放置在两个地方,一个用于编码,另一个用于解码。为了方便,下表对它们进行了概括。



  相关的对象  编码  解码
VENDOR_KEY  l_xorname(),
VENDORNAME,  do_real(), l_n36_buf()  l_good_lic_key()
ENCRYPTION_SEED,步骤1
  VENDORMAGIC_V7  do_real(),key5(), l_n36_buf()  uniqcode(), l_n36_buff()
ENCRYPTION_SEED,步骤2  x = key5_uniqx, 
key5_order[], 
sig[], MAX_DAEMON_NAME
idx, xor_arr  uniqcode(), l_n36_buff()  L_MOVELONG(), l_string_key()

  现在是我们从cmath.exe中找回真正的加密种子的时候了。真正的种子作为L_MOVELONG()的第一个参数被恢复。使用idx=6,和前面所述的置换表 xor_arr[],xor操作数包含 4 字节,,在job->mem_ptr2_bytes[]中被编址在7、3、5、11。经过汇编后是00A0A000.因此, ENCRYPTION_SEED1 =  code-> data[0] ^ 00A0A000 = 52ED15B8 ^ 00A0A000 = 524DB5B8,ENCRYPTION_SEED2 =  code-& gt;data[1] ^ 00A0A000 = 75CF780F ^ 00A0A000 = 756FD80F。
  在lmseeds.h中试用它们.呜呼! lmcrypt.exe产生了亲爱的6D5C01FD71C9。改变版本号为5.0,我们得到 3F23BE3056E4, 又是正确的鉴名档! 这无疑确定我们已经获得Visual Numerics的可信的加密种子 (encryption seeds)和vendor keys。 最终,我们能生成任何我们所想要的VNI license keys, 可以应用于其它的版本, 其它的features,其它的产品…只要FLEXLM没做重大的修改, 它应能够正常工作而不会有任何故障发生.

F:\flexlm>type license.dat
FEATURE CMATH VNI 5.0 permanent uncounted 0 HOSTID=ANY
FEATURE CSTAT VNI 5.0 permanent uncounted 0 HOSTID=ANY
FEATURE CMATH VNI 5.5 permanent uncounted 0 HOSTID=ANY
FEATURE CSTAT VNI 5.5 permanent uncounted 0 HOSTID=ANY
FEATURE CMATH VNI 7.1 permanent uncounted 0 HOSTID=ANY
FEATURE CSTAT VNI 8.3 permanent uncounted 0 HOSTID=ANY
FEATURE Hello VNI 2.9 permanent uncounted 0 HOSTID=ANY
FEATURE cRaCk VNI 4.0 permanent uncounted 0 HOSTID=ANY
FEATURE CMATH VNI 5.5 permanent uncounted HOSTID=ANY SIGN=0

E:\flexlm>utils\lmcrypt -i license.dat
FEATURE CMATH VNI 5.0 permanent uncounted 3F23BE3056E4 HOSTID=ANY
FEATURE CSTAT VNI 5.0 permanent uncounted 2C60CD4570B0 HOSTID=ANY
FEATURE CMATH VNI 5.5 permanent uncounted 6D5C01FD71C9 HOSTID=ANY
FEATURE CSTAT VNI 5.5 permanent uncounted 369B56AC8B35 HOSTID=ANY
FEATURE CMATH VNI 7.1 permanent uncounted F218B30D7129 HOSTID=ANY
FEATURE CSTAT VNI 8.3 permanent uncounted CC5FA3C48B85 HOSTID=ANY
FEATURE Hello VNI 2.9 permanent uncounted 505E4E243D1B HOSTID=ANY
FEATURE cRaCk VNI 4.0 permanent uncounted 93D0E20E2D20 HOSTID=ANY
FEATURE CMATH VNI 5.5 permanent uncounted HOSTID=ANY \
        SIGN=B5E1542279DC

进一步的讨论

尽管我们已经对FLEXLM保护系统进行了完全的反向工程,这里仍有许多东西值得讨论. 最显著的一个是, VENDOR_KEY5在哪里?

(待续。。。。。。)