/////////Netbios单元/////////// unit netbios; interface uses windows,messages,Forms,SysUtils; type {$X+}{$A+} file://声明一个NCB记录指针。 PNCB=^NCB; file://声明一个后处理例程的过程类型。 POST=procedure(var ncbR:PNCB); file://以下是NCB记录,教训1:将上面的编译选项置为{$A+}以取消数据对齐。如果在广播中有浮点数的话,数据对齐会让你大吃苦头!我已经有过惨痛教训!:( NCB=record ncb_command:UCHAR; ncb_retcode:UCHAR; ncb_lsn:UCHAR; ncb_num:UCHAR; ncb_buffer:PCHAR; ncb_length:WORD; ncb_callname:array [1..16] of UCHAR; ncb_name:array [1..16] of UCHAR; ncb_rto:UCHAR; ncb_sto:UCHAR; ncb_post:POST; ncb_lana_num:UCHAR; ncb_cmd_cplt:UCHAR; ncb_reserve:array [1..10] of UCHAR; ncb_event:HANDLE; end; file://声明自己的Netbios函数。教训2:一定要使用pascal调用规范,否则,嘿嘿!! function NetbiosSR(ncbX:PNCB):UCHAR;pascal; file://初始化NCB。 procedure InitNCB(var ncbY:PNCB); file://后处理例程,注意使用远指针。 procedure postrout(var ncbR:PNCB);stdcall;far; var char_buffer:array[0..511]of UCHAR; int_buffer:array[1..512]of Byte; implementation file://调用系统的Netbios。dll中的Netbios函数标号是6。Delphi搜索外部文件的顺序是当前目录→系统目录→其他目录,别忘了保证存在Netbios.dll。 function NetbiosSR(ncbX:PNCB):UCHAR;external ‘netbios'' index 6; procedure InitNCB(var ncbY:PNCB); var x:integer; begin ncbY.ncb_command:=0; ncbY.ncb_retcode:=0; ncbY.ncb_lsn:=0; ncbY.ncb_num:=0; ncbY.ncb_length:=512; file://数据缓冲长度,最大512B。 for x:=1 to 16 do begin ncbY.ncb_callname[x]:=0; ncbY.ncb_name[x]:=0; end; ncbY.ncb_rto:=0; ncbY.ncb_sto:=0; ncbY.ncb_lana_num:=0; ncbY.ncb_cmd_cplt:=0; for x:=1 to 10 do ncbY.ncb_reserve[x]:=0; ncbY.ncb_event:=0; end; file://后处理例程的作用是当接收到广播消息时,立即向相应窗口发送消息。我在这里偷了点懒,以广播方式发送一个定时器消息。如果你愿意可以向指定窗口发送自定义消息,这样要复杂一些。 首先,要把指定窗口的句柄传递给后台处理例程。通常这是做不到的,但可以利用一些技巧做到。在NCB记录后面紧挨着声明一个句柄类型,然后把指定窗口的句柄赋值给它的实例变量;这样句柄变量的地址与NCB是连续的。在后处理中通过指针或汇编语句将ncbR的地址移到最后一个字节+1,就是窗口句柄的起始地址。明白吗?至于自定义消息,需要重新编译连接库,限于篇幅我就不罗嗦了,有兴趣的可以自己尝试。
procedure postrout(var ncbR:PNCB); begin sendMessage(wnd_BROADCAST,WM_TIMER,0,0); end; end. ////////窗口单元////////// unit broadcast; interface uses Windows,Messages,SysUtils,Classes,Graphics,Controls,Forms,Dialogs,netbios; type Tmain=class(TForm) private {Private declarations} file://消息处理过程,注意消息宏要与后处理中的一致。 procedure post_main(var Message:TMessage);message WM_TIMER; public {Public declarations} end; var main: Tmain; ncbname:UCHAR; ncbRock:PNCB; post_add:POST; implementation {$R *.DFM}{$A-}{$I-} /////////主窗口建立过程///////// procedure Tmain.FormCreate(Sender: TObject); var ret:UCHAR; i,x,y:integer; p:single; begin new(ncbRock); randomize();i:=0; FillChar(char_buffer,sizeof(char_buffer),0); post_add:=@postrout; file://取后处理例程的地址。 ncbRock.ncb_buffer:=@char_buffer; file://取数据缓冲区的地址。 InitNCB(ncbRock); ret:=9; ncbname:=random(100); ncbRock.ncb_name[1]:=ncbname; ncbRock.ncb_command:=$30; file://加名,ret为0加名成功。 while ((i<10)and(ret<>0)) do begin ret:=netbiosSR(ncbRock); i:=i+1; end; if ret<>0 then begin for i:=1 to 20 do messagebeep(-1); MessageDlg(‘网络通信无法实现!您需要关闭程序重新运行.'',mtWarning, [mbOk],0); end else if ret=0 then begin ncbRock.ncb_post:=post_add; ncbRock.ncb_command:=$a3; file://异步接收方式字。 ncbRock.ncb_event:=0; ncbRock.ncb_length:=512; ret:=netbiosSR(ncbRock); end; end; ///////////广播消息处理过程///// procedure Tmain.post_main(var Message:TMessage); var x:integer; ret:UCHAR; begin file://取出数据缓冲区的内容 for x:=0 to 511 do int_buffer[x+1]:=char_buffer[x]; ////以下可以进行数据处理//// file://重新打开异步接受。 ncbRock.ncb_post:=post_add; ncbRock.ncb_command:=$a3; ncbRock.ncb_event:=0; ncbRock.ncb_length:=512; ret:=netbiosSR(ncbRock); end; end. 注:广播发送非常简单,不再详述。上述程序经过一年运行完全可靠。另外,经过改造可以将其改为LAN下的聊天程序。
wait no wait
名字管理 add name
add group name
delete name 30h
36h
31h b0h
b6h
b1h 增加本地惟一名
增加本地小组名
删除本地名字
数据报服务 send datagram
send broadcast
receive datagram
receive broadcast 20h
22h
21h
23h a0h
a2h
a2h
a3h 发送数据报
发送广播数据报
接收数据报
接受广播数据报
会话服务 call
listen
send
china send
send no-ack
chain send no-ack
receive
receive any
hang up 10h
11h
14h
17h
71h
72h
15h
16h
12h 90h
91h
94h
97h
f1h
f2h
95h
96h
92h 呼叫建立会话
侦听建立会话
按会话号发送数据
按会话号发送双缓冲数据
按会话号发送数据,不应答
发送双缓冲数据,不应答
按会话号接受数据
从任意会话号上接收数据
拆除当前会话
一般命令 repeat
adapter status
session status
cancel
unlink 32h
33h
34h
35h
70h b3h
b4h 初始化网络适配器
读取网络适配器状态
按名字读取当前会话状态
撤消一个netbios命令
断开远程连接
01h : 无效的缓冲区
03h : 无效的命令
05h : 命令超时
06h : 不完整地接收消息
07h : 本地No-Ack命令失败
08h : 无效的本地会话
09h : 没有可使用的资源
0Ah : 会话已关闭
0Bh : 命令已撤消
0Dh : 本地NetBIOS命名表中名字重复
0Eh : NetBIOS命名表满
0Fh : 名字具有活动会话,现被撤消登记
11h : NetBIOS 本地会话表满了
12h : 没有挂起的Listen 命令,所有拒绝断开会话
13h : 非法名字编号
14h : 不能找到被调用名字或无回答
15h : 找不到命令,或不能把*号或00h指定ncb_name的首字节,或名字已被撤消而不能再使用
17h : 名字已被删除18h : 会话非正常结束
19h : 检测到名字冲突
1Ah : 不兼容的远程设备
21h : 接口忙
22h : 挂起的命令太多
23h : 在ncb_lana_num域中无效的编号
24h : 产生取消时,命令已完成
25h : 字节组名命令指定了保留名字
26h : 命令不能被撤消
30h : 被另一个进程定义了名字
34h : NetBIOS环境未被定义
35h : 所用的操作系统资源用尽
36h : 超出最大应用个数
37h : NetBIOS无可以使用的SAP
38h : 无可以使用的请求资源
40h : 系统错误
41h : 检测到远程适配器的热载波
42h : 检测到本地适配器的热载波
43h : 未检测到载波
4Eh : 状态位12、14、或15被置位的时间超过 1 min
4Fh : 状态位8--11中的一个或多个被置位
50h--F6h: 适配器发生故障
F7h : 隐式DIR-INITIALIZE错误
F8h : 隐式DIR-OPEN-ADAPTER 错误
F9h : IBM LAN支持程序内部错误
FAh : 适配器检查
FBh : NetBIOS 程序未被装入PC
FCh : DIR-OPEN-ADAPTER 或 DIR-OPEN-SAP失败
FDh : 不期望关闭适配器
FFh : 命令挂起状态
0000 00001a37 NetbiosAddthd
0001 000019eb NetbiosDelete
0002 00001a96 NetbiosDelthd
0003 000019b1 NetbiosInitialize
0004 0000186b PostRoutineCaller
0005 0000102e _Netbios
注意到那个0005号_Netbios导出函数了吗?那就是我需要的!经过紧张的试验调试,证明它和WIN32API手册上的Netbios完全一样。剩下的工作就比较简单了,定义一个NCB(Netbios控制块)记录,将NCB数据结构封装在里面;声明一个后处理例程以及消息处理过程,以完成广播数据的接收和发送。有关NCB数据结构的详细内容以及NetBIOS广播的原理,限于篇幅我就省略了。需要的朋友可以查看BC或VC的Help或相关书籍。下面是有关的Delphi源代码。
uses windows,messages,Forms,SysUtils; type {$X+}{$A+} file://声明一个NCB记录指针。
PNCB=^NCB; file://声明一个后处理例程的过程类型。 POST=procedure(var ncbR:PNCB); file://以下是NCB记录,教训1:将上面的编译选项置为{$A+}以取消数据对齐。如果在广播中有浮点数的话,数据对齐会让你大吃苦头!我已经有过惨痛教训!:( NCB=record ncb_command:UCHAR; ncb_retcode:UCHAR; ncb_lsn:UCHAR; ncb_num:UCHAR; ncb_buffer:PCHAR; ncb_length:WORD; ncb_callname:array [1..16] of UCHAR; ncb_name:array [1..16] of UCHAR; ncb_rto:UCHAR; ncb_sto:UCHAR; ncb_post:POST; ncb_lana_num:UCHAR; ncb_cmd_cplt:UCHAR; ncb_reserve:array [1..10] of UCHAR; ncb_event:HANDLE; end; file://声明自己的Netbios函数。教训2:一定要使用pascal调用规范,否则,嘿嘿!! function NetbiosSR(ncbX:PNCB):UCHAR;pascal; file://初始化NCB。 procedure InitNCB(var ncbY:PNCB); file://后处理例程,注意使用远指针。 procedure postrout(var ncbR:PNCB);stdcall;far; var char_buffer:array[0..511]of UCHAR; int_buffer:array[1..512]of Byte; implementation file://调用系统的Netbios。dll中的Netbios函数标号是6。Delphi搜索外部文件的顺序是当前目录→系统目录→其他目录,别忘了保证存在Netbios.dll。 function NetbiosSR(ncbX:PNCB):UCHAR;external ‘netbios'' index 6; procedure InitNCB(var ncbY:PNCB); var x:integer; begin ncbY.ncb_command:=0; ncbY.ncb_retcode:=0; ncbY.ncb_lsn:=0; ncbY.ncb_num:=0;
ncbY.ncb_length:=512; file://数据缓冲长度,最大512B。 for x:=1 to 16 do begin ncbY.ncb_callname[x]:=0; ncbY.ncb_name[x]:=0; end; ncbY.ncb_rto:=0; ncbY.ncb_sto:=0;
ncbY.ncb_lana_num:=0;
ncbY.ncb_cmd_cplt:=0; for x:=1 to 10 do ncbY.ncb_reserve[x]:=0; ncbY.ncb_event:=0; end; file://后处理例程的作用是当接收到广播消息时,立即向相应窗口发送消息。我在这里偷了点懒,以广播方式发送一个定时器消息。如果你愿意可以向指定窗口发送自定义消息,这样要复杂一些。 首先,要把指定窗口的句柄传递给后台处理例程。通常这是做不到的,但可以利用一些技巧做到。在NCB记录后面紧挨着声明一个句柄类型,然后把指定窗口的句柄赋值给它的实例变量;这样句柄变量的地址与NCB是连续的。在后处理中通过指针或汇编语句将ncbR的地址移到最后一个字节+1,就是窗口句柄的起始地址。明白吗?至于自定义消息,需要重新编译连接库,限于篇幅我就不罗嗦了,有兴趣的可以自己尝试。