【转载】用外部程序启动AutoCAD方法的研究(启动闪屏制作与CreateProcess启动操控AutoCAD探索)

来源:互联网 发布:苹果手机电子书软件 编辑:程序博客网 时间:2024/05/07 16:38

第1 页
用外部程序启动AutoCAD 方法的研究
--- 启动闪屏制作与CreateProcess 启动操控AutoCAD 探索

作者:七彩云南(QQ)
QQ:420304230 E-mail:AyungerStudio@163.com

[题目] 用外部程序启动AutoCAD,显示启动屏幕,并自动加载其他相关arx 程序。
[摘要] 随着对AutoCAD 二次开发的加深,程序员需要对其开发的arx 进行打包发行(以
下称为“你的软件包”),同时想通过建立一个独立的外部启动程序(ayCAD.exe)来启动
AutoCAD,并自动加载发布的arx 程序。该启动程序需要做如下工作:
1、显示启动屏幕(用自定义的启动屏代替AutoCAD 的启动屏);
2、启动AutoCAD;
3、自动加载其他“你的软件包”相关的若干个arx 程序;
4、初始化与发行软件相匹配的AutoCAD 环境参数变量;
5、退出启动屏幕,将控制权限移交给AutoCAD。

用外部程序启动AutoCAD方法的研究(启动闪屏制作与CreateProcess启动操控AutoCAD探索) - ayungerstudio - ayungerstudio的博客

 
[正文] 这样做的好处是明显的,因为客户在使用AutoCAD 时,不仅仅是在“你的软件
包”的配置环境下使用,还可能运行其他的AutoCAD 二次开发程序(如,“他的软件包”)。
如果“你的软件包”直接永久的修改了客户计算机中的AutoCAD 配置环境,这样客户会非常
的不方便(如某CASS 软件,其对AutoCAD 环境配置修改在笔者看来是“毁容”式修改,连
AutoCAD 的菜单、快捷键命令、编辑命令都改的面目全非,而且该类软件一旦安装上之后,
你的AutoCAD 想清清爽爽的运行,那就难了!)。那么就需要建立一个与“你的软件包”相适
应的启动程序供客户使用,只有在客户启动了“你的软件包”的启动程序(ayCAD.exe)时才
去自动加载相关的arx 程序,并配置AutoCAD 环境变量及参数,一旦客户直接启动纯AutoCAD
(即直接启动了acad.exe)时,“你的软件包”不应该对客户的AutoCAD 环境保留任何的配
置残留。
好了,沿着这个想法,我开始了我的“用外部程序启动AutoCAD 方法的研究”,具体如
下:
1、启动屏幕的制作:这里就不再多说了,方法非常多:用Splash 组件类或直接写启动
屏幕非模态对话框显示启动图片方式等等。
2、启动AutoCAD 方法:用外部程序(以下简称“你的启动程序.exe”)启动AutoCAD 的
方法很多,比如:COM 方法、ShellExecute 方法、CreateProcess 方法等,详细介绍如下。
1)用COM 方式启动AutoCAD:这种启动AutoCAD 的源码在网上非常多,是依靠AutoCAD
提供的COM 函数库进行的,代码如下:
//AutoCAD COM库接口定义头文件
#include "CAcadApplication.h"
#include "CAcadDocuments.h"
#include "CAcadDocument.h"
......
::CoInitialize();//创建COM接口
CAcadApplication clsAcadApp;
if(clsAcadApp.CreateDispatch(_T("AutoCAD.Application.18"), NULL)==FALSE)
{
pSplash->Hide();//隐藏启动屏
::AfxMessageBox(_T("启动AutoCAD 失败!");
//失败后相关清理工作
::AfxGetMainWnd()->EndWaitCursor();//恢复鼠标样式
clsAcadApp.ReleaseDispatch();//释放变量
::CoUninitialize();//删除COM接口
return FALSE;
}
//设置AutoCAD状态
clsAcadApp.put_Visible(TRUE);//显示AutoCAD
clsAcadApp.put_WindowState(acMax);//最大化屏幕
......
::CoUninitialize();//删除COM接口
该方法启动由于采用对应的AutoCAD 版本COM 组件库函数,来编写启动闪屏中的关于启
动AutoCAD 的模块代码,其优点是启动程序采用COM 接口,一旦AutoCAD 启动后欲自动加载
其他arx 程序( 以下简称“ 你的arx 程序”), 由于其直接使用COM 类库中提供的
CAcadApplication::LoadArx 函数进行,无需采用向AutoCAD 命令行发送命令的方式进行,
其代码稳健性良好;其缺点是对于像AutoCAD 2008 这样的高版本软件,启动较AutoCAD 2000
要慢很多(因为软件本身的功能代码增加、图像数据库编码扩充、三维建模模型处理及材质
库等剧增, 造成了AutoCAD 启动所需时间加长)。用该方法启动, 自
CAcadApplication::CreateDispatch 函数创建并启动AutoCAD 进程开始, 至
CAcadApplication::put_Visible(true)函数调用,将AutoCAD 主窗口到前台显示之间,需
要等待漫长的时间,而且在桌面上没有任何的显示(当然,硬盘指示灯一直在闪烁,表明了
程序是在启动中)。按照COM 结构设计,其AutoCAD 进程的启动及其子窗口初始化、序列化
工作都是在后台完成的,只有等初始化完全完成后,CreateDispatch 函数才返回至下一行代
码执行(即初始化过程独占运行方式),put_Visible(true)才能生效,即显示AutoCAD 的程
序窗口于桌面上。
而直接启动acad.exe 方式(即直接用鼠标双击acad.exe 文件图标操作方式)下,AutoCAD
主窗口总是率先显示于桌面上,然后进行菜单、工具条、状态栏、命令行窗口及其它控件(或
子窗口)的初始化和显示过程。这种方式下总是“可视化”进行的,起启动全过程用户都能
够看到!而COM 方式更像是个“黑匣子”启动,给客户一种“假死”的现象。
其实该方法和单纯启动acad.exe 方法一样,至客户能够输入绘图命令为止,所用总时
间是一样的。但这个方式总给人一种莫名的担心(程序员总是心虚的,就是因为你懂的,你所
写的程序是总会有很多的Buger(俗称虫虫)存在的!笔者认为只要你的程序能够安全的在超
过四台以上的计算机上运行无异常,你就可以暂时的喝杯咖啡了^0^),其缺点二是,采用COM
方式编写启动屏幕方式和讨厌的arx 版本一样,要编写支持AutoCAD 2000~2013 所有版本
的启动程序,代码改的都要想起那个《工程师外传》里面的设计人员,还有某天在QQ 群里
面看到的关于施工图"第A 版修改"到“第Z14 版修改”的目录树一样的难过。
2)采用ShellExecute 方法:假设“你的软件包”安装于d:\123 目录下,其中“你的
arx 程序”有test1.arx, test2.arx 等程序。首先用记事本创建一个acad.rx 文件,并输入
如下内容(每个arx 程序文件名独占一行,关于acad.rx 的文件格式参见AutoCAD 的帮助文
档):
test1.arx
test2.arx
.....
编写启动AutoCAD 的函数如下:
ShellExecute(NULL,
_T("open"),
_T("C:\\Program Files\\AutoCAD 2008\\acad.exe /nologo"),//AutoCAD路径
NULL,
_T("d:\\123"), //当前工作路径
SW_SHOWNORMAL);
这样,当AutoCAD 启动时,AutoCAD 会自动寻找“当前工作路径”下的acad.rx 文档,
并自动加载里面的arx 程序。
这个方式的优点是ShellExecute 函数简洁明了,其缺点是对AutoCAD 启动之后的后续
控制上,比如:什么时间该结束你的启动闪屏,即AutoCAD 什么时候就算启动完成?要
Sleep(2*1000),还是Sleep(30*1000)。
[关于Sleep 函数的一点插曲]:Sleep 函数俗称“睡眠”,意思是让主进程暂时挂起,
直至设定的时间到了,才唤醒该进程。
对于相同版本的AutoCAD,随各个计算机的性能不同,其启动时间长短亦差异较大,故
用Sleep(nms)方式来判断“你的启动程序.exe”何时该退场了,笔者是不推荐的!当然有好
事者曾采用测试CPU 的运算峰值数据量来判断AutoCAD 是否已启动完毕(进入到等待用户输
入消息循环)!这种方法笔者也不建议采用,比如:恰巧用户启动AutoCAD 的后,马上又启
动了其他程序,如Microsoft Word, Photoshop 等大型软件,那么这种方法是不可取的。
3)采用CreateProcess 方法,该方法较ShellExecute 方法复杂,且参数较多,代码如
下:
......
lpszAcadExeFile = _T("C:\\Program Files\\AutoCAD 2008\\acad.exe");
PROCESS_INFORMATION pi;
STARTUPINFO si;
memset(&si,0,sizeof(STARTUPINFO)); //use default window feature
si.cb = sizeof(STARTUPINFO); //this field must be filled in
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWMAXIMIZED;
BOOL bRet = CreateProcess(lpszAcadExeFile,
_T(" /nologo"),
NULL,
NULL,
FALSE,
0,
NULL,
strExePath,
&si,
&pi);

if(!bRet)
{
pSplash->Hide();//隐藏启动屏
::AfxMessageBox(_T("启动AutoCAD失败!");
return FALSE;
}//end if
CreateProcess 函数的具体参数意义在这里就不在做过多解释,具体参见MSDN 的相关文
档。
其中CreateProcess 函数的最后一个参数(&pi)的返回值,是我们所需要关注的重点!
该参数返回成功启动子进程(acad.exe)实例后的进程句柄( pi.hProcess)和进程ID
(pi.dwProcessId),可供“你的启动程序.exe”的后续代码对AutoCAD 做进一步的操控,
如判断acad.exe 初始化是否结束、是否已进入到等待用户输入消息循环状态等等。
以上是三种启动AutoCAD 的常用方法(当然还有WinExe 方式等),接下来,我继续沿着
消除“假死”现象的问题继续剖析:
至今为止,我要立志抛弃了用COM 方式启动AutoCAD 的“弊端”了,而且采用ShellExecute
方法启动其他程序,其返回值仅仅能够判断你所启动的其他程序成败而已,想掌控它,就一
个字,难!这极像是大风里放风筝的感觉,只能判断风筝线断了与否,若想收回风筝就难了,
原因正如MSDN 里讲的,一头雾水!切见如下帮助:
If the function succeeds, it returns a value greater than 32. If the function fails,
it returns an error value that indicates the cause of the failure. The return value is
cast as an HINSTANCE for backward compatibility with 16-bit Windows applications. It
is not a true HINSTANCE, however. It can be cast only to an int and compared to either
32 or the following error codes below.(Bing 上的翻译:如果此函数成功,则返回一个值大
于32。如果该函数失败,则返回一个错误值,指示失败的原因。16 位的Windows 应用程序的
向后兼容的情况下,返回值转换为HINSTANCE。不过它并不真正的HINSTANCE。它可以转换为int
只和相比,32 或下面的以下错误代码。)
接着(其实你不懂我的心,原来下文还有那么多的不如意!),我就依照CreateProcess
函数的“强大”功能来进行AutoCAD 启动程序的开发了,可是总有太多太多的艰难(这正如
一旦选择了VC++,那就是踏上了一条不归的路,还自命不凡的在code(戏称“堆码”)界认
为自己是个VC 高手,看不上C#,更瞧不上VB 兄弟!其实本人最喜欢lisp,几行程序顶天
立地,不信看看本人的“[AY 工具]软件”,我都翻译成繁体版了,使用者有中国同行、还有
加拿大、台湾、新加坡的用户呢!其98%的代码采用lisp 编写。
废话多了点,你别见笑,我也累了,还要想想下面怎么写,所以和老外的作品一样,又
臭又长,让你等不及了。^0^
言归正传,采用CreateProcess 方法虽然抓住了对AutoCAD 程序(对“你的启动程序.exe”
来讲此时的AutoCAD 为子进程)启动之后的控制,可还有很多的问题需要解决,难题依然不
断:
1、如何判断AutoCAD 已经初始化完成,进入等待客户输入消息循环;
2、如何向AutoCAD 发送命令,以方便加载“你的arx 程序”
3、何时才应该进行关闭启动闪屏和退出“你的启动程序.exe”
那么我就将知难而上,当然带你进入到难题的攻坚战中,一起“Good Good Study, Day
Day Up”,并针对以上问题逐一解决如下:
1、如何判断AutoCAD 已初始化完成,进入等待用户输入阶段?
解答: 按照CreateProcess 函数的解释, 启动成功子进程(acad.exe) 后, 返回
PROCESS_INFORMATION 结构信息,即前面所定义的那个“pi”参数,其结构信息描述如下:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess; //进程的主句柄
HANDLE hThread; //进程的主线程句柄
DWORD dwProcessId; //进程ID
DWORD dwThreadId; //主线程ID
} PROCESS_INFORMATION,;
以上参数中HANDLE hProcess 就是我们想要的acad.exe 程序启动后的句柄,可是如何
将此句柄(HANDLE)转换为彼句柄(HWND)呢?那为什么一定要转换呢?这是因为:
彼句柄(HWND)才是VC++的MFC 所鼓吹的“视窗(Window)”的控制类句柄,没有彼句
柄(HWND),想控制窗口操作,根本就不可能!原因是没有几个用进程HANDLE 的API 函数可
操控于具有窗口界面(Window)的函数(当然笔者应该说很多,可MFC 封装的太深了,所以
有=没有)。想操作AutoCAD 的Window 界面,必须先获取彼句柄(HWND)!那么如何进行了?
好累啊,一个个问题接踵而来,搜了网上的资源有两天两夜之久,最后的方法还是我最
不擅长的方法(也是arx 程序员不熟悉的领域!),该方法是:遍历桌面上的所有已启动窗口
(Window)进程(“窗口”,为什么要在这里注释一下呢?因为我要找的是“窗口”,而不是
所有已启动的其他非窗口进程,因为我要的目的是找到那个彼句柄(HWND),而非窗口进程
是没有HWND 嘛,还有一点就是有了HWND 就等于有了CWnd,而CWnd 又是专门操纵窗口(Window)
对象的指针类型。CWnd 在MFC 中属于“总理”级别的,想想吧,为什么我要不厌其烦的找到
它的原因,就是为了办大事!),从中找出和刚才那个pi 变量返回的进程ID( DWORD
dwProcessId)相同进程,那就是我所想要的,也是我刚刚启动了的“那一个”AutoCAD,而
不是其他的客户已启动的AutoCAD 实例。代码如下:
//获取与指定进程ID相同的进程的主窗口句柄(HWNF)
HWND CRwetStartApp::GetHandlebyProcessID(DWORD dwProcessID)
{
if(dwProcessID==NULL) return NULL;
HWND hWndtmp = ::GetWindow(::GetDesktopWindow(), GW_CHILD);
while(hWndtmp)
{
if (::IsWindow(hWndtmp) && ::IsWindowVisible(hWndtmp))
{
DWORD dwProcessIDnew = 0;
DWORD dwThreadIDnew = ::GetWindowThreadProcessId(hWndtmp, &dwProcessIDnew);
if(dwThreadIDnew!=0)
{
if (dwProcessIDnew == dwProcessID) return hWndtmp;
}
}
hWndtmp = ::GetWindow(hWndtmp, GW_HWNDNEXT);
}//end while
return NULL;
}
这段代码就完成了逮住刚刚用CreateProcess 启动了的AutoCAD 的窗口句柄,接下来的
问题就简单了点,是吗,还不一定!至于“如何判断AutoCAD 已经初始化完成”的问题又来
了,其实还根本没解决呢!
根据关于进程和线程(这个对Arx 程序员更头疼,因为你不是开发路由器,网络接口,
大型并行计算的,简而言之,就是想画个线线,完成笔者绘图的小小要求,仅此而已)的介
绍,其中有这样一个函数:
HRESULT WaitForInputIdle(int milliseconds,BOOL *pRetVal);
单从名称上就可以一眼看出,是我所需要的(Wait For Input Idle,即等待进程进入
可输入状态)。按照MSDN 上的解释:
The WaitForInputIdle function waits until the specified process is waiting for user
input with no input pending, or until the time-out interval has elapsed.(WaitForInputIdle
函数将等待直到指定的进程正在等待用户输入具有挂起,没有输入或经过超时间隔。)
于是快速的写下如下的代码行:
//等待AutoCAD初始化完毕
if(WaitForInputIdle(pi.hProcess, INFINITE)!=0)//INFINITE=0, 为永远等待下去
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
pSplash->Hide();//关闭启动屏
::AfxMessageBox(_T("启动AutoCAD时间超限!");
return FALSE;
}
狂喜之下,按下了VS2010 的Ctrl+F5 键运行一下,想着终于可以睡觉去了!其实又错
了,程序根本不会等待AutoCAD 的启动过程到达我所想要的那种效果(AutoCAD 初始化完成,
并进入到等待用户输入的消息循环状态,然后才退出启动闪屏程序)。现在的结果却是:
AutoCAD 刚已启动,还根本就没有初始化完全结束(就是刚启动了个AutoCAD 的框架主窗口
而已),启动闪屏已经退出了,犹如在某些不适宜的场合去开会一样,报个到就闪人!
我所要的启动闪屏是应该达到这样的效果:桌面正中央显示着我的启动闪屏,后面正是
AutoCAD 启动的过程(即,先显示ACAD 主框架窗口,然后是诸如菜单条显示,工具条刷新,
命令行显示,还有那个绘图区的黑色窗口显示完成,最后就是常见的命令行上的那一句
“CommandLine”显示完毕!),等待AutoCAD 的窗口中的所有控件元素(或曰子窗口)显示
完完全全后,闪屏再将等待上500~1000 毫秒,之后划一个漂亮的弧线后像流星一样(Window
Vista 关闭窗口的方式)结束其本次行程,将客户控制权交个用户。
可是现在根本就不是我所想要的效果,也不是我所期望的,这和WinExec 有甚的区别
(WinExec 就是反正我启动了其他程序,至于启动结果怎样我不关心,想再控制它们,连门
都没有)!
接着网上有搜了大半夜,70%的网友还是负责任的告诉我一个事实:WaitForInputIdle
是确实正如MSDN 上所描述的那样,可问题是Window 自打其采用所谓的“多任务”设计理念
后,其主体思想是这样的:“多任务”是源自于“多线程”的设计思路,那么客户的计算机
中一般只有一个CPU,怎么办呢?正如就一个人怎么干三个人工作呢?其实就是“多线程”
按照其优先等级“抢用”CPU 资源原理(你闲着我忙,我闲着他忙,反正不能让CPU 闲着!),
一旦一个进程(其实Window 启动后,包括用户打开的应用程序窗口,就有上百个了进程在
前台或后台趴着,伺机运行一下,正如病毒程序一样,偷着运行)进入消息等待状态,其他
进程就可以从假寐状态进入到唤醒工作状态(因为所有的消息等待状态是全局共享的,谁都
能看到),这样就实现了“多任务”。WaitForInputIdle 也确实是忠实的办到了这一点,不能
单从我运行结果上诬告它没等待到AutoCAD 完全初始化、并显示完成。那为什么我要讲“多
任务”呢?这就牵扯到的延时显示等问题,WaitForInputIdle 确实是等待了for Input Idle,
可是Windows 采用了消息机制,消息机制如下:
//仿VB的DoEvents函数
void CayWin::DoEvents()
{
MSG msg;
while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
DispatchMessage(&msg); //分派消息到窗口的回调函数处理。
TranslateMessage(&msg);//进行消息(如键盘消息)转换。
}//end While
}
虽然消息是发出去了,但是消息执行了没有,怎么判断其消息已经被执行了(如OnPaint
完了)?这个对于“你的启动程序.exe”控制下的子进程(这里是acad.exe),怎可能深入到
acad.exe 的OnPaint()函数体中,或采用进程间(父子进程)通信的方式知晓呢?于是有翻
阅了《VisualC++2010 开发权威指南》和《把脉VC++》等多本书籍,还是没有找到我所想要
的解决方案,什么邮槽通信、匿名管道通信,共享内存通信等等,那全讲的是你自己开发的
两个进程(或称之为客户端和服务器端程序)之间的通信,我怎么好让AutoDesk 专门为我
的启动闪屏做个信息反馈值呢?那也不现实,我这个菜鸟也不敢提出这等要求给AutoDesk
公司。再加之自打Windows 采用IDLE 机制后,还有个不为大多数程序员所关注的问题就是:
消息滞后空闲相应模式,即菜单状态刷新模式。言简意赅的讲就是:我什么时候闲了,再刷
你的菜单状态,你不能要求我在规定的时间内完成刷新工作!对我来讲,判断AutoCAD 何时
刷新(如OnPaint)完了,那就是个未知数了,不知道。
仿佛几天的学习和工作白费了,还是要回到老路上,就那个鸡肋般的Sleep()函数上来
吗?答案按照网上的说法是否定的,但怎么解决呢?有些网友说了,枚举AutoCAD 主窗口中
的所有子窗口,看看是否显示出来了,如果显示完了,那就肯定了AutoCAD 真正进入到Input
Idle 了,于是我就又有了如下的探索(也是极其不情愿的啃骨头!)
方法就是采用Spy++记住每个子窗口的类名,枚举每一个子窗口、孙窗口....好累啊。
×××(次数删除250 行,竟是些关于枚举子子孙孙的窗口)。
某一天,code 的天昏地暗的时候,忽然间发现AutoCAD 同一个版本的多个启动实例中,
同一个命令窗口(CommandLine Window)居然类名(class name)不一样!比如某子窗口的
类名为:
窗口标题:Drawing1.dwg
窗口类名:Afx:00400000:b:00010003:00000006:21800A4F
Spy++另一个AutoCAD 进程实例的同样窗口,上面类名的最后一组数据(1800A4F)或两
组数据(00000006:21800A4F) 也不一样,而且不同AutoCAD 版本(笔者的计算机中安装了
AutoCAD2006、2008、2012 三个版本)的相同窗口类名也不一定一样,用获取窗口标题的方
式,对于某些想把你的AutoCAD 改的仿佛他开发的绘图软件的界面的软件来说,这个方法更
不靠谱。彻底晕了!这样下去不是和COM 做法一样多的启动程序版本,有过之而无不及?而
且有更加不详的预兆(COM 仅仅是“假死”现象,程序是健壮的,这样Spy++下去会因为我
的疏漏或无知造成大的Buger)!
苍天啊,大地啊,我就是想做个arx 程序,帮助我的同行们画画线线嘛,干嘛要我这么
的折磨呢?而且是暗无天日,车道山前没有路。于是乎便怀疑上VC++,诅咒MFC 的声音在心
底里泛起,这下真正是抓瞎了!也不由得怀念DOS 时代,怀念单线程时代了。急什么急嘛,
我干完了,你在接班吗,这多少有点要挤我下岗的感觉,而且给我一个什么时间刷新完也没
个准儿的感觉!
遍历窗口树就已经是令我非常之恼火了,后来又发现,Onpaint 即使是消息分发出去了,
执行的消息也返回了,但桌面上的窗口是否绘制完成了还没谱,全是“等我有空了马上刷新”,
又是十大谎言之一啊!这个Microsoft,连他自己都搞不定!正如对Sleep(1000)的解释
一样,1000 毫秒数学上计算是严格等于1 秒钟的,但是有专家又说了,这个应解释为至少等
待1 秒,绝不少于1 秒,但要睡到准确1 秒时结束,至少现在的计算机办不到!所以等待AutoCAD
刷新界面等到准确结束无望了!我也该睡了,越干越没劲!根本就没有个希望所在....
该不该来个塑盆洗手,并将计算机格式化一下,永远不再安装VC++了呢?我纠结了N
多天。也从此不再做任何的程序,网上求救也无望了。闲下来就热衷于QQ 的桌球游戏。
有一天在返回去整理和学习枚举进程代码的时候,有这么几个函数对我有用:
FindWindowEx(),IsWindow(),IsWindowVisable(),于是就进入到MSDN 的文档中无精打
采的看着,看着...,也一级一级的“See Also”中跳着,翻阅着,无意中转到了IsWindowEnabled
()函数的解释,如下:
BOOL IsWindowEnabled( ) const;
Note: Specifies whether CWnd is enabled for mouse and keyboard input.
突然,处于长期对代码帮助信息的敏感,这个好像就是判断鼠标或键盘是否可“输入”
操作的函数。于是写下了如下的代码测试:
//判断AutoCAD是否进入可操作状态(这是延时启动屏幕的关键所在!)
HWND hAcadMain = GetHandlebyProcessID(pi.dwProcessId);//AutoCAD句柄
if(hAcadMain && ::IsWindow(hAcadMain) && ::IsWindowVisible(hAcadMain))
{
if(::BringWindowToTop(hAcadMain) && ::IsWindowEnabled(hAcadMain))
{
isAcadRunTimeOut=FALSE;
break;
}
}
根据测试显示,确实有了我要的效果!(笔者测试方法:在子窗口(如笔者测试的Edit)
在不能输入时,IsWindowEnabled 函数总是返回False,简单的用计数循环测试即可,当然
多线程更明显)
于是无望的问题在MSDN 仅10 个单词的简要解释中得到了我预期的答案!当然笔者要声
明一下,我的期望就是概略的判断AutoCAD 启动、初始化、界面显示、进入用户可输入等待
状态(user can really Input Idle satus!),也不需要特别的精确,当然不能太离谱了,
如:WaitForInputIdle。
真是柳暗花明又一村,就这样终于解决了我的启动程序的技术难题!
接下来的事情就是完成整个代码的编写工作。经AutoCAD2006、2008、2012 和不同的计
算机测试,效果基本相同,达到我的需求!
以下为简化过的代码,展示关键内容即可,当然我的程序代码比较复杂,还加入了自动
判断AutoCAD2000~2013 间任意语种类别的版本判断函数、启动闪屏的Window7 效果等等(关
于自动判断AutoCAD 版本问题还要另外的文章讲解,内容也比较多):
// 启动AutoCAD并加载程序函数
BOOL RunSplashAndMyWork(LPCTSTR lpszAcadExeFile, LPCTSTR strExePath)
{
//LPCTSTR lpszAcadExeFile=_T("C:\\Program Files\\AutoCAD 2008\\acad.exe");
//LPCTSTR strExePath = _T("d:\\123");
.....
pSplash->Show();//开始显示启动屏
//4. 最大容忍等待启动时间值
UINT uTimeoutms=60*1000;
DWORD dwStart = GetTickCount();//计算时间开始
//5. 用CreateProcess创建进程,启动AutoCAD主程序
PROCESS_INFORMATION pi;
STARTUPINFO si;
memset(&si,0,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWMAXIMIZED;
BOOL bRet = CreateProcess(lpszAcadExeFile, _T(" /nologo"),NULL, NULL, FALSE,
0, NULL, strExePath, &si, &pi);
if(!bRet)
{
pSplash->Hide();//关闭启动屏
::AfxMessageBox(_T("AutoCAD启动失败!"));
return FALSE;
}//end if
//6. 等待“进程”初始化完毕
if(WaitForInputIdle(pi.hProcess, uTimeoutms)!=0)//等待AutoCAD执行完毕
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
pSplash->Hide();//关闭启动屏
::AfxMessageBox(_T("启动超时!"));
return FALSE;
}

//7. 等待AutoCAD主程序启动完全,即进入可输入状态为止(IsWindowEnabled)
HWND hAcadMain=NULL;
BOOL isAcadRunTimeOut=FALSE;
while(TRUE)
{
Sleep(100);
pSplash->SetText1(strMsgWaiting);//显示信息
//7.1 计算是否超时
DWORD dwEnd = GetTickCount();//计算时间结束
UINT uWorkTimes = dwEnd-dwStart;
if(uWorkTimes>uTimeoutms)
{
isAcadRunTimeOut=TRUE;
break;
}
//7.2 判断AutoCAD是否进入可操作状态(这是延时启动屏幕的关键所在!)
hAcadMain = GetHandlebyProcessID(pi.dwProcessId);//AutoCAD句柄
if(hAcadMain && ::IsWindow(hAcadMain) && ::IsWindowVisible(hAcadMain))
{
if(::BringWindowToTop(hAcadMain) && ::IsWindowEnabled(hAcadMain))
{
isAcadRunTimeOut=FALSE;
break;
}
}
}//end while(TRUE)
//8. 启动AutoCAD成功, 延时退出启动屏
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
//9. 延时隐藏启动屏
Sleep(1500);
pSplash->Hide();//隐藏启动屏
if(isAcadRunTimeOut) ::AfxMessageBox(strErrMsgTimeOut);
}
第13 页
这就是实现外部程序启动AutoCAD 的完整的过程!其他的问题已经不是问题了,接着就
稍微轻松的解决其他两个问题:
2、自动加载arx 程序:只要在“你的软件包”所在的目录(如d:\123)下用记事本编
写一个acad.rx(当然也可以用代码创建这个文件,这样就不会怕丢掉了,我就是这么干的!),
其中加入arx 程序名称即可(参考前面关于acad.rx 文件编写规则)。当然我要讲彻底了:
BOOL bRet = CreateProcess(lpszAcadExeFile, _T(" /nologo"),NULL, NULL, FALSE, 0, NULL,
strExePath, &si, &pi);
这句代码中的倒数第三个参数strExePath 就是指子进程(在此为acad.exe)的工作目
录,在启动AutoCAD 时,AutoCAD 会在其工作目录中去找寻是否存在一个acad.rx 这样的文
件,若存在此文件,则执行之!这一点对启动AutoCAD 并自动加载“你的arx 程序”很重要。
这样也就实现了当客户不再需要启动“你的软件包”,而纯启动acad.exe 时,不再加载“你
的arx 程序”了,也就还客户一个干净的AutoCAD 环境。
当然我还得啰嗦一句:至于“你的软件包”对于AutoCAD 启动后的环境参数设置,诸如
加载线形文件(acad.lin)定义等直接在你的arx 程序中去设计就可以了,尽量不要永久遗
留在AutoCAD 的配置文件中。
3、如何向AutoCAD 发送命令:至于向AutoCAD 如何发送命令,网上有高人介绍了五种
方法,我仅罗列如下:
1)ads_queueexpr( _T("(command\"_POINT\" \"1,1,0\")") );//该函数CAD 未公开,使用
时提前声明下就可以了。
2)acDocManager->sendStringToExecute(curDoc(), _T("_POINT 2,2,0 "));//该函数在
Arx 帮助中有详细说明。
3)acedCommand(RTSTR, _T("_POINT"), RTSTR,_T("5,5,0"), RTNONE);
4)COM 方法
void SendCommandTest(void)
{
IAcadApplicationPtr pApp = acedGetIDispatch(TRUE);
IAcadDocumentPtr pDoc;
pApp->get_ActiveDocument(&pDoc);
pDoc->SendCommand( _T("_POINT 4,4,0 ") );
}
5)Windows API 方法
void SendCmdToAcad(ACHAR *cmd)
{
COPYDATASTRUCT cmdMsg;
cmdMsg.dwData = (DWORD)1;
cmdMsg.cbData = (DWORD)(_tcslen(cmd) + 1) * sizeof(ACHAR);
第14 页
cmdMsg.lpData = cmd;
SendMessage(adsw_acadMainWnd(), WM_COPYDATA, NULL, (LPARAM)&cmdMsg);
}
笔者要说明的是,对于用外部程序启动AutoCAD 的方法而言,前三种方法显然行不通,
因为那是arx 函数,而“你的启动程序.exe”是exe 可执行文件程序;至于第四中方法是COM
方法, 不是本文所研究的主要内容; 只有第五种方法才可行, 但是该方法中的
adsw_acadMainWnd()显然又是arx 函数,经笔者改写如下:
//向AutoCAD命令行发送命令
void SendAcadCommand(HWND hWnd, LPCTSTR lpszCmd, BOOL IsSendEsc2)
{
if(hWnd == NULL) return;
if(lpszCmd==NULL) return;
CString strCmd = lpszCmd;
CString strRight1 = strCmd.Right(1);
if(strRight1!=_T(" ")) strCmd += _T(" ");//末尾添加至少一个空格
//1. 发命令前加按了两个ESCAPE
if(IsSendEsc2)
{
TCHAR szEsc[3];
COPYDATASTRUCT cmddata;
szEsc[0] = VK_ESCAPE;
szEsc[1] = VK_ESCAPE;
szEsc[2] = NULL;
cmddata.dwData = (DWORD)1;
cmddata.cbData = (DWORD)(_tcslen(szEsc)+1)*sizeof(TCHAR);
cmddata.lpData = (TCHAR*)szEsc;
SendMessage(hWnd,WM_COPYDATA,(WPARAM)hWnd,(LPARAM)&cmddata);
}//end if
//2.发送命令字符串
TCHAR *pszCmd = (TCHAR*)(LPCTSTR)strCmd;
COPYDATASTRUCT cmdMsg;
cmdMsg.dwData = (DWORD)1;
cmdMsg.cbData = (DWORD)(_tcslen(pszCmd)+1) * sizeof(TCHAR);
cmdMsg.lpData = (TCHAR*)pszCmd;
//::SetForegroundWindow(hWnd);
::SendMessage(hWnd, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cmdMsg);
return;
}
该函数同时实现了给AutoCAD 发送命令的方法,且在发送命令前加了两个Esc 模拟按键,
正如AutoCAD 菜单文件(acad.mnu)中命令前面的两个“^C^C”作用相同;至于那个入口参
数HWND hWnd 嘛,前面已有介绍(就是此句柄(HANDLE)转换后的彼句柄(HWND)),如下:
HWND hAcadMain = GetHandlebyProcessID(pi.dwProcessId);//AutoCAD句柄
至此,采用外部程序启动AutoCAD 的方法研究其主要技术环节及注意事项已全部,仅此
抛砖引玉,让同行们少走弯路!当然笔者本人也不是专业的VC++开发人员,仅作为业余爱好
和探索所得,文中所讲内容正如本人的Arx 程序一样Buger 不少,还请读者批评指正!
[参考]
1.我所遇到的参考90%来自于网络的知识;
2.当然枕边总少不了《把脉VC++》、《C++语言学》、《Visual C++ 2010》;
3.最最缺少的是ObjectArx 开发类书籍,张帆的ObjectArx 是我遇到的最好的书籍(电子
版);
4.最好的网站是http://www.vckbase.com。
[后记] 窗外的天已泛起了鱼肚白,枝头的小鸟欢快的叫着,用清脆的歌声迎接
着崭新的一天。我也抽完了那包烟卷,浓浓的苦咖啡,支撑我在这里所写下的每一个
字,也陪伴我度过了这个arx 程序爱好者的不眠之夜。周末就这么过去了,好在是在
工地上,我还可以用一整个的白天去修补我缺少休息的大脑,以便于在周一到来的时
候,能够用饱满的热情,投入到FSDi 的工作当中去。
为了曾经帮助我完成这个启动模块的朋友们,也为了我亲爱的祖国arx 同仁们,
值得!
作者:七彩云南20120805 宝鸡
[附录]
以下是笔者编写的启动AutoCAD 并加载arx 程序的启动闪屏程序所支持的初始化文件
(ayCAD.ini),其闪屏启动效果图见第1 页:
//ayCAD.ini
[启动设置]
AutoCAD=2008,2009,2007
UserLogoBmp=aylogo.bmp,9
UserSoftName=交通选线CAD 设计系统
UserSoftVer=RWET 5.100α Build 20120712
UserSupport=技术支持:(QQ)420304230,(TEL)13991255731
UserTimeout=30
//x,y,cx,cy,字体名,字大小,字加粗,r,g,b,对齐样式,是否透明,输出文本
UserMsg1=50,170,0,0,宋体,90,1,0,0,0,0,0
UserMsg2=330,5,200,16,宋体,80,0,255,255,255,0,1,【RWET 5.00α Build 20120805】
UserMsg3=280,300,0,0,宋体,80,0,0,0,255,0,0,技术支持:QQ-420304230
[应用程序]
1=ayRwetMenu08.arx
2=DMC_CAD2007.arx
3=ayRwetAcad.arx
4=ayRwetSheet.arx
5=ayGeCad.arx
这样就能够完全支持AutoCAD2000~2013 间的任何版本了,只需要修改[启动设置]节中
AutoCAD 键值。其主要键值说明如下:
1、AutoCAD=2008,2009,2007,含义是可启动本地计算机上安装的2008,2009 和2007
中的任何一个版本,其优先级别自左向右。
2、UserLogoBmp=aylogo.bmp,9, 用户自定义启动闪屏背景图片文件(文件格式为Bmp
格式),数字9 为设置启动闪屏的倒圆角半径值,为正值。
3、UserTimeout=30,为设置用户启动超时值,单位:秒
4、UserMsg1、UserMsg2、UserMsg3 为提供的启动闪屏中的文本显示信息设置,其格式
为“x,y,cx,cy,字体名,字大小,字加粗,r,g,b,对齐样式,是否透明,输出文本字符串”,如:
UserMsg2=330,5,200,16,宋体,80,0,255,255,255,0,1,【感谢您使用本软件...】
[应用程序]节为自动加载程序列表节,格式如上,是标准的初始化文件格式。
---- End ----

0 0
原创粉丝点击