外部exe程序启动CAD并且自动加载CAD的ARX程序的过程

程序名:MFCOpenCADWithARX.exe

版本:1.0

时间:15.11.2

*****************************************************

首先感谢 @七彩云南,在网上查找资料的时候发现了他的资料,很是详细,但是没有找到文章的出处。

我的程序没有他的复杂。

*****************************************************

用途:用外部程序启动Autocad程序,同时自动加载和启动ARX程序。

环境:Win7  64位  VS2010  AutoCAD2010 自己的Arx程序

背景:传统的插件加载模式是,打开CAD——工具——加载程序——选择你的ARX程序——成功之后输入命令行启动你的ARX程序或者通过别的方式打开。

优点:1、只是安装好外部程序EXE文件,使用点击打开就能自动加载ARX并打开插件。

    2、这个程序仅仅是加载了自己的Arx文件,不会改变CAD的外观以及你之前的环境配置。不会永久的更改你的AutoCAD的界面。

启动程序应该做如下工作:

1、启动AutoCAD

2、加载ARX程序

3、发送命令到AutoCAD,启动所做的ARX

一、启动AutoCAD的方法:

七彩云南在他的文章中列出了3种方法分别是

1、用COM的方式启动 使用AutoCAD COM接口

2、采用ShellExecute 我也不知道是个什么方法,不做解释

3、CreatProcess方法 比较复杂 下文主要讲这种方法

*************************************************************

关于CreatProcess方法

关于这个函数,如果你看不懂MSDN,这里有个大招:

http://baike.baidu.com/link?url=G4XIZs8nbrteQ3spqXgUFo0bNwHRinatQ4mqfi-mV3VEompeNnRaJ8FmksCDCwokPaT4AKgyWanV7Hq1kZYRCa

百度百科上的,去看看什么用途


STARTUPINFO si;
	memset(&si,0,sizeof(STARTUPINFO));
	si.cb=sizeof(STARTUPINFO);
	si.dwFlags=STARTF_USESHOWWINDOW;
	si.wShowWindow=SW_SHOWMAXIMIZED;
	PROCESS_INFORMATION pi;//必备参数设置结束
	UINT uTimeoutms=60*1000;
	DWORD dwStart = GetTickCount();//计算时间开始
//AfxMessageBox(lpszAcadExeFile);
//AfxMessageBox(strExePath);
	BOOL bRet = CreateProcess(lpszAcadExeFile,
		_T("/nologo"),
		NULL,
		NULL,
		FALSE,
		0,
		NULL,
		strExePath,
		&si,
		&pi);


	if(!bRet)

	{
		MessageBoxW(_T("启动cad失败"));
		return false;
	}//end if




引用:七彩云南的话:

采用CreateProcess 方法虽然抓住了对AutoCAD 程序(对“你的启动程序.exe”
来讲此时的AutoCAD 为子进程)启动之后的控制,可还有很多的问题需要解决,难题依然不
断:
1、如何判断AutoCAD 已经初始化完成,进入等待客户输入消息循环;
2、如何向AutoCAD 发送命令,以方便加载“你的arx 程序”
3、何时才应该进行关闭启动闪屏和退出“你的启动程序.exe”

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 实例。

关于这段话 的意思是这样的:

我们获取的句柄类型为:HANDLE,但是我们想要控制的视窗是这样的句柄HWND,由于MFC封装太深,所以我们必须要找到一个合适的方法找到这个HWND。方法就是我们有pi这个值。其pi.ProcessID是我们启动窗口的唯一值,那么我们可以历桌面上的所有已启动窗口(Window)进程,对比找到。

代码:

HWND CMFCopencadDlg::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;
	}


这就是实现外部程序启动AutoCAD 的完整的过程!其他的问题已经不是问题了,接着就
稍微轻松的解决其他两个问题:

2、加载ARX程序

3、发送命令到AutoCAD,启动所做的ARX


由于cad可以在工作区查找acad.rx文件,因此我们只要编写一个rx文件放在工作区,这样就可以自己启动我们的arx程序

rx程序编写过程是 打开记事本,输入arx程序的完整路径即可

如果要编写程序自己去写rx文件,一定要注意格式。把要自动加载的东西一一列上即可。


发送命令到AutoCAD:


如何向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);
cmdMsg.lpData = cmd;
SendMessage(adsw_acadMainWnd(), WM_COPYDATA, NULL, (LPARAM)&cmdMsg);
}

只有第五种方法才可行, 但是该方法中的
adsw_acadMainWnd()显然又是arx 函数,经七彩云南改写如下:



void CMFCopencadDlg::SendAcadCommand(HWND hWnd, LPCTSTR lpszCmd, BOOL IsSendEsc2)
	{
		if(hWnd == NULL) return;
		if(lpszCmd==NULL) return;
		LPCTSTR strCmd = lpszCmd;
		CString strCmd1 = strCmd; 
		CString strRight1 = strCmd1.Right(1);
		LPCTSTR space = _T(" ");
		if(strRight1!=_T(" ")) 
		{
			CString str=(CString) strCmd + _T("  ");
			
			strCmd = str;
		}//末尾添加至少一个空格
////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);
		}

		//
		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);
		::BringWindowToTop(hWnd);
		::SendMessage(hWnd, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cmdMsg);
		//AfxMessageBox(pszCmd);
		return;
	}

该函数同时实现了给AutoCAD 发送命令的方法,且在发送命令前加了两个Esc 模拟按键,
正如AutoCAD 菜单文件(acad.mnu)中命令前面的两个“^C^C”作用相同;至于那个入口参
数HWND hWnd 嘛,前面已有介绍(就是此句柄(HANDLE)转换后的彼句柄(HWND)),如下:
HWND hAcadMain = GetHandlebyProcessID(pi.dwProcessId);//AutoCAD句柄
至此,采用外部程序启动AutoCAD 的方法研究其主要技术环节及注意事项已全部,仅此
抛砖引玉,让同行们少走弯路!

在此感谢原博主。

PS:不同版本CAD使用的编码不一样 AutoCAD2010使用的是Unicode编码,07-08好像是多节字符集,在开发的时候,要注意版本的使用字符集,并且保证一致!!!!!!!!!!!!!!!

你可能感兴趣的:(cad)