连接线(Fromtos)、手动连接线(Manual Fromtos)、类(Classes)、规则(Ruls)、冲突(violations)、内含对象(embedded objects)等。主数据库(见图4-3)是一个扁平的数据库,由两个不同的数据结构组成,在图左边的第一个结构是单一连接列表,由单独的过孔(Vias)、元件(Components)、网络(Nets)、坐标(coordinates)、尺寸(Dimensions)、类(Classes)、规则(Ruls)、连接线(Fromtos)、和连接(Connections)组成,在图右边的第二个结构由相同种类的PCB对象的数组连接列表组成,每一个数组分别存放线(Track)、圆心弧(Arcs)、填充(Fills)、文本字符串(Text String)、焊盘(Pads)、多边形(Polygons)对象,从性能上优选第二个数据结构。

图4-3 在PCB编辑器中的主数据库
一个组对象可为一个元件、尺寸、坐标、多边形或者一个网络对象,且每一个组对象是由图元组成,每一个组对象也有它自己的小的存储图元的数据库。
第二个结构(图4-4)的数据库系统是一个空间数据库,在空间数据库中,每一个层是一个容器,容器是一个空间的或象限的树,能存储任何种类的PCB对象,第二个结构的数据库从PCB对象快速访问上来说是优选。

图4-4 空间数据库
2.2 程序系统
迭代程序提供了顺序地访问一个集合对象单元的方法,而无需暴露它的底层的表现。PCB编辑器数据库系统使用迭代程序提供了一个紧密的访问PCB对象的方法,无需跨越API来创建一个镜像数据库。迭代程序主要功能是通过数据库遍历来读取某些PCB对象。有二个迭代程序类型,对象迭代程序和空间迭代程序。

图4-5 一个在动作中迭代程序
对象迭代程序被用于来管理进行全局的查找,空间迭代程序被用于管理内部的查找。一个迭代程序内置在PCB编辑器中来遍历数据库,从外部服务器查找相似的对象。PCB编辑器会自动选择使用哪一个数据库系统,仅依赖于哪一个迭代程序方法被使用。
2.3 机器人和消息系统
32位的PCB编辑器应用程序实现了一个事件驱动构架,事件驱动构架简化很多关联软件设计和实现的问题,例如,PCB编辑器使用软件代理或机器人和消息,消息把很多任务连成一个整体并且显著地减少了源代码的编程量。
在PCB编辑器中,消息是一个到编辑器系统的内部事件通知,例如,当PCB编辑器数据库系统被修改,一个通知消息将被广播,且此PCB消息将携带有关当前内部事件的信息,一个消息携带四个不同的参数,原对象(source object)、目标(destination)、消息类型(type of message)和可选择的信息(optional information)。
一个机器人是一个软件代理,当它被PCB编辑器产生的消息激活时有指定任务要做。这些机器人在PCB编辑器的后台操作,消息被顺着一个机器人列表传递,这些机器人仅对特别的消息类型起作用或不起作用。举个实际的例子,在PCB数据库系统中执行维护连通性和完整性任务的机器人当一个进程开始时起作用,这个机器人当在一个修改被完成时也起作用,当一个PCB对象被增加或从数据库中删除时和当计算被完成时起作用,但是当一个修改被初始化后不起作用。
用户与PCB编辑器相互作用,如果用户修改环境状态,机器人将得到通知,知道但是在等待用户停止和环境的相互作用,也就是说,当PCB编辑器环境被修改后,PCB消息被发送,携带了有关环境状态的信息,一旦机器人被激活,机器人将知道将要做什么以确保环境被恢复。PCB编辑器有十七个预定义的消息和十一个内部的机器人。
PCB编辑器为构建和使用外部机器人提供一个预备措施,这个预备措施被制作成PCB的API使用。因而,开发者能开发外部机器人并且发送PCB消息到PCB编辑器,来通知已存在的机器人恢复PCB编辑器的环境。

图4-6 机器人系统
此图4-6显示了一个机器人系统,图形简单显示了在PCB编辑器中机器人的功能。这些机器人被附在环形消息连接链上,有齿轮组符号机器人A被PCB消息激活,机器人B、C、D、和E是空闲的,也就是说,它们对此PCB消息不起反应。
2.4 交互事件句柄系统
交互事件处理是PCB编辑器非常重要的部份。一个事件是一个在PCB编辑器中特殊的发生事情,并且一个代码段对那个特殊的发生事情进行响应,此响应代码是一个句柄,事件仅在交互模式下是句柄,也就是说,当一个十字光标被显示在PCB编辑器中时,交互事件句柄系统不处理用户事件,例如按下一个键来显示一个菜单等操作不被处理。
当PCB编辑器在交互模式下时,PCB事件被监视,如果一个事件发生,内置在PCB编辑器中的交互事件句柄系统起反应,代码段是对特殊事件起反应的句柄。在PCB编辑器中,对每一个不同的事件,它们有定义的句柄。

图4-7 事件句柄系统
举一个例子,使用的事件句柄是一个由PCB API开发的,附加的、交互的PCB编辑器事件句柄,在PCB编辑器为交互模式时,此附加的使用外部事件句柄的系统来追踪事件,交互的尺寸在两个参考点间增加一个尺寸对象,用户点击PCB对象的第一点参考点和第二点参考点,一个尺寸对象接着被产生并且放置在当中。
PCB事件句柄的客户化提供了用户交互的精确控制,也就是说,当在交互模式下时,API使程序开发者能够连接他们的代码到指定的PCB编辑器的事件。
3. PCB API的结构
3.1 应用程序接口
在您编写一个外部的服务器前,您必须明白PCB编辑器提供的服务。这些服务有标准的协议,您的服务器必须使用。如果您要编写一个服务器,需使用PCB编辑器的服务,您必须首先明白用于连通PCB编辑器的协议。
协议被通过动态连接库技术(DLL)简化,一个DLL一旦加载到内存中就是一个应用程序,暴露一定固定数量的称为函数指针的函数入口点,这些功能指针针向DLL内部的函数和过程。PCB API为了其它服务器方便使用,提供了一个干净的接口,来封装函数指针到函数/过程接口。
PCB编辑器DLL文件为了任何外部服务器方便使用,提供了201个函数入口指针。PCB API是封装这些PCB编辑器函数入口指针的接口,给程序设计者使用PCB编辑器的输出过程和函数提供了访问手段。

图4-8 设计资源管理器的开放架构
自定义服务器的代码依赖于PCB编辑器的服务,必须访问PCB API文件,这些PCB API单元将允许服务器来使用通过PCB编辑器DLL所暴露的函数和过程,这些单元是PCBTypes、PCBProcs、PCBClass、SchProcs和SchClass。PCB API单元被内置在CSRTL.DPL中,CSRTL.DPL是当您在开发一个服务器时,您需要引用到服务器项目中的一个运行时间包,您仍然需要增加指定的单元到您的代码的Uses子句中。例如,客户化(外部)服务器通过引用三个PCB API单元有PCB API接口(参见图4-8),此服务器与PCB编辑器一道,被以插件方式加入到设计资源管理器中,因而它有访问PCB编辑器的能力。
3.2 PCB API单元
AdvPCB.DLL文件提供了一个较大集合的过程和函数,过程和函数在运行时被激活。PCB API的任务是提供一个干净的接口,接口包裹在PCB编辑器的函数入口指针的周围。PCB API是一个层次结构,请见图4-9 PCB编辑器服务器,也就是说,AdvPCB.DLL是层次结构的顶层。下面被讨论的三个API单元构成整个PCB API,每一个单元将被详细讨论。

图4-9 PCB API单元
从图4-9中,PCB API单元PCBProcs、PCBClass和PCBTypes为PCB编辑器的PCB API提供了支持,客户化服务器通过PCB API与PCB编辑器协作,PCBTypes单元是PCB API最基本的单元,在PCB编辑器数据和一个外部的服务器数据之间提供了类型的兼容性支持。

图4-10 数据类型兼容
例如,TReal(实数)类型在PCBTypes单元中被作为一个双精度(Double)类型定义,在32位Object Pascal中定义双精度为8位大小,如果您使用Object Pascal的实数类型来定义数据集,并且试图在PCB编辑器中使用TReal变量类型,此时如果您编译工程,编译器将产生一个错误。数据类型是非常重要的,在外部服务器中的数据块是8位长,且在PCB编辑器是数据块也是8位长,变量类型的大小必须相同。PCBTypes单元提供了预先定义的类型,很容易地使用,胜于您定义类型和检查数据类型值是否被使用。
PCBPROCS单元通过使用地址指针和类型来包裹在这些函数周围,得到所有PCB编辑器服务器所暴露的过程和函数的地址。SetAllPCBProcAddresses API方法获取所有定义在PCB API中的函数和过程,所以外部服务器能提取出地址和进行类型转换,这些地址通过使用适当的类型转换作为正确的函数和过程,函数指针内存分配被API自动地完成,指针地址被设置到它的在PCB编辑器服务器的实现(implementation)处。
PCBClass单元提供了最高层水平的包裹,对象确定地址技术帮助隐藏和密封函数到对象中,因而PCBClass单元实现底层的例程。
3.3 PCB API的实现
PCB API能帮助您来构建增强PCB和PCB库编辑器功能的服务器,您能检索有关印制板参数的信息,在它们上进行操作处理,并且您能为印制板设置新的参数,所以您能容易地构建一个服务,无须担心有关Windows句柄、编写有关参数检索的过程等等方面的麻烦。
过程SetAllPCBProcAddresses获取所有来自PCB编辑器的函数和过程的指针地址。
Procedure SetAllPCBProcAddreses;
Procedure SetProcAddress(var P : TFarProc; ProcName : Pchar);
Begin
If PcbLibrary = 0 Then P := Nil
Else P := GetProcAddress(PCBLibrary,ProcName);
//GetProcAddress是一个Windows API函数,其功能是获取指定的DLL模块中,某函数或功能地址,取出的地址放在P中。具体请见下面详细说明。
End;
Begin
PCBLibrary := GetModuleHandle(‘AdvPCB.DLL’);
// 如果一个指定的模块文件(DLL)已被映射到调用进程地址空间,GetModuleHandle函数返回此模块的句柄。
...
SetProcAddress(_PcbAPI_QueryPrimitive,’PcbAPI_QueryPrimitive’);
...
End;
GetProcAddress函数返回指定的输出动态连接库(DLL)中某函数的地址,定义如下:
FARPROC GetProcAddress(
HMODULE hModule,// handle to DLL moduleDLL模块的句柄
LPCSTR lpProcName // name of function函数名称
);
参数说明如下:
hModule
包含要获取函数指针的DLL模块的标识符,LoadLibrary或GetModuleHandle函数返回此句柄。
lpProcName
指向一个包含函数名称的以空终止符(null-terminated)为结尾的字符串,或指定函数的次序值。如果此参数是一个次序值,它必须是在低次序字,高次序字必须被置为零。
返回值:
如果此函数执行成功,返回值是DLL的输出函数的地址。如果函数执行失败,返回值是NULL。为得到扩大范围的错误信息,调用GetLastError。
注意:
GetProcAddress函数被用于检索在DLL中输出函数的地址,通过lpProcName指向的函数名称的拼写和大小写必须与在源DLL的模板中定义(.DEF)的文件中输出(EXPORTS)段的名称一致。
lpProcName参数能通过指定一个关联的在DLL的EXPORTS段中标识的次序值来标识DLL的函数,GetProcAddress在范围1到在.DEF文件中输出的最高次序值间校验指定的次序值,函数接着使用次序值作为索引来从一个函数表中读取函数的地址,如果.DEF文件从1到N(N是输出函数的数值)没有此函数的次序,一个错误将发生,GetProcAddress返回一个错误,空地址(non-NULL address),即这里没有指定次序的函数。
万一此函数也许不存在,使用名称比使用次序更好。
GetProcAddress函数是一个API windows 函数,得到函数地址,地址是一个无类型的指针。在此例子中,_PcbAPI_QueryPrimitive是一个无类型的指针。第二个字符串“PcbAPI_QueryPrimitive”在SetProcAddress方法中是过程的名称。这步骤涉及了PCBClass单元中函数调用,说明如下:
TPCBObject = Class
BitField: TBitField;
BitField2 : Byte;
Index : Word;
Layer : TLayer;
Moveable: Boolean;
Net : TObjectHandle;
Component : TObjectHandle;
Constructor Create(AHandle: TObjectHandle);
Function QueryDatabase(Mode : TQueryMode) : Integer;Virtual;
Procedure GetState_BoundingRectangle(Var Lx, Ly, Hx, Hy : TCoord); Virtual;
FunctionImport_FromUser : Boolean; Virtual;
Procedure GraphicallyInvalidate;
...
End;
TPCBObject.QueryDatabase(Mode : TQueryMode)函数抽取或分配有关此对象的信息。
Function TPCBObject.QueryDatabase(Mode : TqueryMode) : TShortInt;
Begin
PcbApi_GetObjectBitField2(Mode,ObjectHandle,BitField2);
Result := PcbApi_QueryPrimitive(Mode, ObjectHandle, BitField,
Index, Layer, Net, Component, Moveable);
End;
用QueryDatabase函数,用函数PcbApi_QueryPrimitive,一个调用被安排到PCBProcs单元,内存分配和类型转换在PCBProcs单元内部完成。
函数类型声明:
TPcbApi_QueryPrimitive=
Function(Mode : TQueryMode;
ObjectHandle : TObjectHandle;
VarBitField : TBitField;
VarIndex: TWord;
VarLayer: TLayer;
VarNet: TObjectHandle;
VarComponent: TObjectHandle;
VarMoveable : TBoolean) : TshortInt;
函数指针类型定义
_PcbApi_QueryPrimitive : TFarProc;
对函数指针的内存分配被完成,并且指针的地址被设置到它的在ADVPCB.DLL中的实现(implementation)处。
Procedure SetAllPCBProcAddresses;
Procedure SetProcAddress(Var P : TFarProc; ProcName : Pchar);
Begin
If PcbLibrary = 0 Then P := Nil
Else P := GetProcAddress(PCBLibrary,ProcName);
//取PCBLibrary句柄中ProcName函数的地址到指针P。
End;
Begin
PCBLibrary := GetModuleHandle('AdvPcb.dll');//取得AdvPcb.dll句柄。
SetProcAddress(_PcbApi_QueryPrimitive, 'PcbApi_QueryPrimitive');
//取得AdvPcb.dll中输出的函数PcbApi_QueryPrimitive的地址到指针变量_PcbApi_QueryPrimitive中。
End;
函数的主体
Function PcbApi_QueryPrimitive
(Mode: TQueryMode;
ObjectHandle: TObjectHandle;
Var BitField: TBitField;
Var Index : TWord;
Var Layer : TLayer;
Var Net : TObjectHandle;
Var Component : TObjectHandle;
Var Moveable: TBoolean) : TShortInt;
Begin
Result := 0;
//如果nil检查指针
If _PcbApi_QueryPrimitive = Nil then Exit;
//如果在DLL输出的函数中没有找到PcbApi_QueryPrimitive函数,即_PcbApi_QueryPrimitive为空,则退出。
//找到后进行指针类型转换,即把指针所指向的内存地址中内存转换为函数
Result:= TPcbApi_QueryPrimitive(_PcbApi_QueryPrimitive)
(Mode, ObjectHandle, BitField, Index, Layer,
Net, Component, Moveable);
End;
3.4 数据完整性
从PCB数据库中抽取数据(就是复制相同的一组数据)需要注意“数据完整性”,请参见图4-11和4-12,数据完整性在数据库中数据和外部服务器的外部数据之间保持数据同步。
|
序号 |
PCB服务器 |
外部的服务器 |
状态 |
|
1 |
|
初始的状态 原理图服务器和外部服务器有相同的数据。 |
|
|
2 |
第二状态 在原理图服务器中数据集已修改,但数据完整性没有保护。 |
||
|
3 |
第三状态 外部服务器调用GetState模块,强制新数据集应用到外部服务器。同步数据任务空间。 |
||
|
4 |
末状态 数据完整性已保护,也就是说,在原理图服务器和外部服务器之间数据集有相同的值。 |
||
|
图4-11 PCB GetState模块 |
|||
图4-11说明内部数据库中数据发生改变,外部服务器中数据与内部服务器中数据不一致,外部服务器调用GetState模块来使用内部数据库中数据强制刷新外部服务器中数据,从而使内外部服务器中数据同步。
|
序号 |
PCB服务器 |
外部的服务器 |
状态 |
|
1 |
|
初始的状态 原理图服务器和外部服务器有相同的数据。 |
|
|
2 |
第二状态 外部数据集已经修改,但是数据完整性没有保护。 |
||
|
3 |
第三状态 外部服务器调用SetState模板,强制新的数据集被应用到原理图服务器。同步数据任务空间。 |
||
|
4 |
末状态 数据完整性已保护,也就是说,在原理图服务器和外部服务器之间数据集有相同的值。 |
||
|
图4-12 PCB SetState模块 |
|||
当外部服务器中数据发生改变后,内部服务器中数据和外部服务器中数据不一致,此时外部服务器调用SetState模块来强制用外部服务器中数据刷新内部数据库数据,从而使内外部服务器数据同步。
图4-11中,当您从PCB编辑器的数据库中抽取数据到您的外部的服务器,这样就有两个相同的数据集,一旦您在PCB编辑器修改内部数据库中的数据集,那么现在就有了两个不同表现的数据集,因而,您应调用一个带有GetState模块的QueryDatabase方法,来进行外部服务器数据更新,更新后两个数据库数据集相同。
图4-12,当您从PCB编辑器的数据库中抽取数据到您的外部服务器,这样就有两个相等的数据集,一旦您在外部数据库中修改了数据集,那么现在就有了两个不同表现的数据集,因而,您应调用一个带有SetState模块的QueryDatabase方法来强制PCB编辑器进行内部数据的更新,更新后两个数据库数据集相同。
当两个或更多数据集表现相同的信息时,其有相同的值,需要保护数据完整性。例如,如果您抽取一条线到PCB外,并在外部服务器中使用,这样这条线对象将有两个表现,一个在PCB数据库内,一个在外部服务器。
QueryDatabase进程的实际实现,是那个印制板对象必须通过使用当前印制板文档的句柄来创建,接下来,在印制板中,PCB对象就能被增加,删除或修改。
3.5 创建一个新的PCB对象
使用PCB API来创建PCB对象,需要按一列一些简单的步骤进行,以确保PCB编辑器数据库系统将能成功注册这些对象。下面是一个简单的例子,说明了如何创建一个新的PCB对象的步骤,例子代码显示出,在任何PCB对象被创建、销毁或修改前,创建一个新的印制板对象最高的任务,如果这里没有印制板对象,内置在PCB编辑器的数据库系统将不会被更新,因而,PCB文档将不受到影响,此例子显示出对象句柄是很重要的。
代码片段功能是放一个过孔对象(Via object,是一个PCB编辑器的设计对象)到PCB文档上。
请见SDK例子\SAMPLES\NO4\API\PCB\Create a via object。
{....................................................................................}
Procedure CreateAViaObject;
Var
BoardHandle : TobjectHandle; //印制板句柄。
ViaHandle : TobjectHandle; //过孔对象句柄。
PCBBoard: TPCBBoard; //印制板对象。
Via : TPCBVia; //过孔对象。
Begin
SetAllPCBProcAddresses;
//SetAllPCBProcAddresses过程初始化并且设置所有PCB编辑器调用。即取得所有AdvPcb.dll中输出的函数和过程的地址指针。
BoardHandle := PcbApi_GetCurrentBoardHandle;
//PcbApi_GetCurrentBoardHandle函数返回当前在设计资源管理器中的PCB文档的句柄,如果印制板没有找到则返回值是空(nil)。
PCBBoard := TPCBBoard.Create(BoardHandle);//创建一个印制板对象。
PCBBoard.QueryDatabase(eGetState);//同步内部服务器数据库和外部服务器数据库
ViaHandle := PcbApi_CreateObject(eViaObject);
//PcbApi_CreateObject函数使用给定的对象种类参数创建一个PCB对象,此函数返回新建的PCB对象的对象句柄。
Via := TPCBVia.Create(ViaHandle);//新建一过孔对象。
Via.QueryDatabase(eGetState);//用内部数据同步外部数据。
Via.x1:= MilsToCoord(1000);
Via.y1:= MilsToCoord(1000);
Via.Size:= MilsToCoord(50);
Via.HoleSize:= MilsToCoord(20);
Via.LowLayer:= eTopLayer;
Via.HighLayer := eBottomLayer;
//以上代码给新建的过孔对象赋值。
Via.QueryDatabase(eSetState);//用外部服务器数据强制刷新内部服务器数据。
PCBBoard.AddObject(Via.ObjectHandle);//把过孔对象增加到印制板上。
PCBBoard.Free;
Via.Free;
//以上代码清理现场。
End;
此段代码使用了下列重要的函数或过程。
|
PcbApi_GetCurrentBoardHandle |
PcbApi_CreateObject |
|
QueryDatabase |
AddObject |
这段代码做什么?
在上面代码中,先创建TPCBBoard对象,并且通过PcbApi_GetCurrentBoardHandle函数得到印制板对象的句柄,接着来创建一个新的过孔对象(Via),您需要使用带有对象ID号如eViaObject的PcbApi_CreateObject函数,您接着需要应用一个QueryDatabase(eGetState)方法来初始化此Via对象,再下一步是分配新的数据值到新建的Via对象的每一个字段,不管怎样,仅注册Via对象到PCB编辑器数据库是不够的。
为了使PCB编辑器的数据库系统知道这个新的Via对象,我们需要来增加新Via对象到印制板对象中,通过使用印制板对象的AddObject方法,AddObject方法的参数是新建Via对象的句柄,一旦AddObject方法被调用,现在,新建的Via对象在PCB编辑器工作空间已是有效对象,当您完成这些工作时,不在忘记释放PCB对象和印制板对象。
新的PCB对象被分配了唯一的对象句柄,此对象句柄是通过带有传入对象种类参数的PcbApi_CreateObject API函数来创建的,对象句柄提供了身份验证方法,并且在PCB编辑器数据库中注册新的和已存在的PCB对象,使用这些对象的句柄,您能维护存储在数据库中的PCB对象的属性。(e-works)