VC调用AutoCAD自动化的两种方法(包装类、接口)使用详解

----哆啦刘小洋 原创,转载需说明出处 2022-12-29

1 简介

在工程领域,使用AutoCAD时经常利用自动化(Automation)技术提高工作效率,VC调用AutoCAD自动化时有两种常用的方式,一个是包装类的方式,另一个是接口的方式,当然不只是AutoCAD,一般的支持Automation的软件都可以使用这两种方式,本文就这两种方式给出详细的使用方法,并简要总结各自的优缺点。文中代码基于VC6及以上。

2 AutoCAD的Automation类型库说明文件

不管是用包装类方式还是接口方式,编程时都需要用到类型库说明文件(.tlb),这个文件是微软Com(Component)技术的一个标准文件,有点类似于c++的声明头文件,文件中给出了所有的接口声明、常量定义、枚举声明等等,方便使用者调用,但这个文件是二进制文件,无法直观的查看内容。其实Com还有一种接口声明的标准文件“.tlh”,这种文件是文本格式的,可以直接查看。不过AutoCAD提供的就是tlb格式。

AutoCAD在安装后,提供了组件的接口文件,一般型如:acax16chs.tlb文件,文件名中的16是AutoCAD的内部版本号,比如AutoCAD2016的内部版本号是20;文件名中的chs是语言,chs就是简体中文,我们一般都用这个。这个tlb文件的位置也和Windows版本以及AutoCAD的版本有关,比如“C:\Program Files\Common Files\Autodesk Shared”,不行就先用文件搜索找到位置再来。

3 包装类方式

包装类是VC对Automation组件的接口进行类封装,然后使用时,把这些类当作普通的类使用即可。需要说明,VC不同的版本对同一个tlb文件的包装结果是不一样的,甚至会差别非常大。据我的使用经验,至少有几次版本有断代式的明显差异。这里我就以VC6和VC2022两个版本分别给出使用方法。

3.1 VC6

通过工程中任意一个类进入类向导窗口,在右侧的AddClass里选择“From a type library”,如下图:

在这里插入图片描述
接下来会打开文件对话框让你选择,我们选择acax??chs.tlb,??是AutoCAD的版本号。
在这里插入图片描述
打开后,会让你确认要包装哪些接口类:
在这里插入图片描述
一般全选即可。(先点中第一个,然后滚动到最后按住Shift点中最后一个)。这里可以对h和cpp文件命名或指定目录。再OK,VC6会生成那两个文件。同时,VC6的ClassView中会出现一大堆新的I开头的类,这些就是AutoCAD的接口包装类。

使用时,代码如下:

#include "acax20chs.h"

void OnTest_class_vc6() 
{
	CoInitialize(NULL);
	
	IAcadApplication	app;
	CLSID clsid;
	HRESULT   h=NOERROR;
	IUnknown* pUnknown;
	LPDISPATCH pDispatch;
	BOOL bSuccessget_Object = FALSE;
        
	//获取AutoCAD的Class ID
	h = ::CLSIDFromProgID(OLESTR("AutoCad.Application"), &clsid); 
	if(!SUCCEEDED(h))
		return;
	
	//获取正在运行的AutoCAD对象
	h = GetActiveObject(clsid, NULL, &pUnknown);
	if(SUCCEEDED(h))
	{
		h = pUnknown->QueryInterface(IID_IDispatch, (void**)&pDispatch);
		if(SUCCEEDED(h))
		{
			bSuccessget_Object = TRUE;
			pDispatch->Release();
			app.AttachDispatch(pDispatch);
		}
	}

     //没有正在运行的AutoCAD对象,就创建一个
	if(!bSuccessget_Object)
		h=app.CreateDispatch(clsid);

	if(FAILED(h) || app.m_lpDispatch == NULL)
		return;
	
	app.SetVisible(TRUE);
	
	//...
}

3.2 VC2022

在解决方案资源管理器栏,选中你的目标项目,点击右键,选择“添加 > 新建项”,如果是英文版,在Solution Explore栏,选择“ Add > New Item”:
在这里插入图片描述
注意,在VC2022中,一定要在解决方案资源管理器(Solution Explore)栏,类视图中点击项目右键是没有这个选项的。这和以前一些版本不同。
在添加新项对话框中,选择“已安装”下的“Visual C++”下面的“MFC”,在右面子项中,选择“TypeLib中的MFC类”(英文版:选择“Installed”下的“Visual C++”下面的“MFC”,在右面子项中,选择“MFC Class from Typelib”)。
在这里插入图片描述
选择好后,点击“添加”:
在这里插入图片描述
如上图所示,选择文件方式,再选择tlb文件(如“acax16chs.tlb”),在可用接口栏,一般点击“>>"全部选择,再确定。这时,VC2022会生成各个类的包装文件,和VC6最不一样的是,VC2022每个类对应一个文件,因此会有很多文件。在生成的时候等待时间长一点,不要做动作,否则可能出现文件没有内容的隐含错误。

使用时,也和VC6不太一样了,因为文件多了嘛。代码如下:

#include "CAcadApplication.h"
#include "CAcadDocuments.h"
#include "CAcadDocument.h"
#include "CAcadModelSpace.h"

void OnTest_class_vc2022()
{
	CoInitialize(NULL);

	CLSID clsid;
	HRESULT  h;

	//获取Class ID
	h = CLSIDFromProgID(OLESTR("AutoCad.Application"), &clsid);
	if (!SUCCEEDED(h))
		return;

	CAcadApplication app;
	IUnknown* pUnknown;
	LPDISPATCH pDispatch;
	BOOL bSuccessGetObject;

	//获取正在运行的对象
	bSuccessGetObject = FALSE;
	h = ::GetActiveObject(clsid, NULL, &pUnknown);
	if (SUCCEEDED(h))
	{	
		h = pUnknown->QueryInterface(IID_IDispatch, (void**)&pDispatch);
		if (SUCCEEDED(h))
		{
			bSuccessGetObject = TRUE;
			pDispatch->Release();
			app.AttachDispatch(pDispatch);
		}
	}

	//没有正在运行的对象,就创建一个
	if (!bSuccessGetObject)
		h = app.CreateDispatch(clsid);

	if (!SUCCEEDED(h) || app.m_lpDispatch == NULL)
		return;

	app.put_Visible(TRUE);
	app.put_WindowState(3/*acMax*/);

	CAcadDocuments docs;
	CAcadDocument doc;
	CAcadModelSpace ms;

	//新建一个AutoCAD文档
	docs = (CAcadDocuments)app.get_Documents();
	doc = (CAcadDocument)docs.Add(vtMissing);

	//模型空间
	ms = (CAcadModelSpace)doc.get_ModelSpace();

	//添加一条多义线
	//ms.AddPolyline(...)
}

如果运气不好,编译会出现错误:error C2059: 语法错误:“,” 。神奇吧!竟然是包装类中出了错,比如下面这个函数。而且,不同VC版本,每次生成的文件错误还不一样(但VC6很稳定),所以,这里强调一下,在VC生成类文件的时候一定不要动,等个30秒。

	__int64 get_HWND()
	{
		__int64 result;
		InvokeHelper(0x2c, DISPATCH_PROPERTYGET, , (void*)&result, nullptr);//这一句,两个","间少了参数
		return result;
	}

下面列举了一些我碰到的错误和解决方法:

CAcadAppliction::get_HWND()和CAcadDocument::get_HWND()有错误,类型名添加一个VT_I8。

	InvokeHelper(0x2c, DISPATCH_PROPERTYGET, , (void*)&result, nullptr); //原来
	InvokeHelper(0x2c, DISPATCH_PROPERTYGET, VT_I8, (void*)&result, nullptr);//修改
	
	InvokeHelper(0x40, DISPATCH_PROPERTYGET, , (void*)&result, nullptr); //原来
	InvokeHelper(0x40, DISPATCH_PROPERTYGET, VT_I8, (void*)&result, nullptr);//修改

CAcadDocuments::get__NewEnum()有错误,类型名添加一个VT_UNKNOWN。

	InvokeHelper(0xfffffffc, DISPATCH_PROPERTYGET, , (void*)&result, nullptr); //原来
	InvokeHelper(0xfffffffc, DISPATCH_PROPERTYGET, VT_UNKNOWN, (void*)&result, nullptr);//修改

还可能有别的一些缺少参数类型的错误,修改方法也很简单,比如错误代码如下:

InvokeHelper(0x405, DISPATCH_PROPERTYGET, VT_I8, (void*)&result, nullptr);	__int64 get_ObjectID()
	{
		__int64 result;//看这里
		InvokeHelper(0x405, DISPATCH_PROPERTYGET, , (void*)&result, nullptr);
		return result;
	}

看函数第一行声明的是什么类型,就在第二句缺少类型参数的地方添加相应的类型。如果你不熟悉VARIANT,一般按下面的替换方式:
__int64 -> VT_I8 long -> VT_I4 LPUNKNOWN->VT_UNKNOWN 基本上可以应付了。其实看看别的函数也可以现学现卖解决问题。

此外,每个文件前面有一行类似下面的语句:
#import “C:\Program Files\Common Files\Autodesk Shared\acax20chs.tlb” no_namespace

建议第一,把acax20chs.tlb拷贝到工程文件中来,第二,只保留CAcadApplication.h文件中的这句话,并改成如下所示的代码。其他需要用到的h文件,我的建议是都注释掉或删除。也可以所有文件都删除这句话,只是无法使用接口需要用到的常量和枚举定义。

#import "acax20chs.tlb" no_namespace

4 接口调用方式

好了,包装类的方式总算好了。现在介绍另一种方式,接口调用方式对不同VC版本都是一样的使用。

这个方式不需要先去生成包装类,直接把AutoCAD提供的acax20chs.tlb拷贝到你工程来。拷贝过来的目的是避免AutoCAD被卸载或换了版本,就找不到文件了。代码如下:

#import "acax20chs.tlb" no_namespace named_guids
	
void OnTest_Interface()
{
	CoInitialize(NULL);

	IAcadApplicationPtr		pApp;
	IAcadDocumentsPtr		pDocs;
	IAcadDocumentPtr		pDoc;
	IAcadModelSpacePtr		pMs;

	HRESULT   h = NOERROR;
	CLSID   clsid;

	//获取Class ID
	h = ::CLSIDFromProgID(OLESTR("AutoCad.Application"), &clsid);
	if (FAILED(h))
		return;

	//获取正在运行的对象
	h = pApp.GetActiveObject(clsid);
	if (!SUCCEEDED(h))
	{
		//没有正在运行的对象,就创建一个
		h = pApp.CreateInstance(clsid, NULL, CLSCTX_ALL);
		if (FAILED(h))
			return;
	}

	pApp->WindowState = AcWindowState::acMax;
	pApp->Visible = VARIANT_TRUE;

	//新建一个AutoCAD文档
	pApp->get_Documents(&pDocs);
	pDoc = pDocs->Add();

	//模型空间
	pDoc->get_ModelSpace(&pMs);

	//添加一条多义线
	//pMs->AddPolyline(...)
}

接口方式和包装类的方式差异甚大,这里大概解释一下:

1)为什么简单一句#import,后面的IAcadApplicationPtr类型、AcWindowState::acMax常量定义就都有了呢,也没有看到我们包含什么头文件啊。这是因为#import,VC会生成Com的另外一种标准文件.tlh和.tli,一个是接口声明,一个是接口实现代理,这两个文件是文本文件,在输出目录里面,可以方便的查看各种接口定义、常量定义。所以我们才能方便的使用IAcadApplicationPtr来定义变量等等。
2)型如IAcadApplicationPtr的类型是生成的接口智能指针,这种方式就是用接口指针来调用AutoCAD的Automation接口。

此外,代码中在模型空间添加一条多义线被注释了,是因为使用没有那么简单,这里要牵涉到变体数组的问题,可以参考我的另一篇文章:”SegeX SgxVariant:VC封装支持多维数组的变体类型“,里面提供了变体数组的封装代码。

5 两种方式对比

5.1 使用便捷性

明显接口方式大比分获胜,第一是整个准备工作就是拷贝一个文件,添加一行代码。第二是接口使用便捷,接口方法返回的接口是一个接口指针,可以实现连续指向,比如:

pApp->GetActiveDocument()->GetModelSpace()->AddPolyline(...)

包装类就很难啦!每个函数返回的是一个分发接口LPDISPATCH,因此需要转换,比如下代码,CAcadAppliction类接口函数get_Documents返回的就是一个LPDISPATCH,不信去看代码。而docs呢,是CAcadDocuments类,所以必须要转换,因此很难实现接口的连续指向。

docs = (CAcadDocuments)app.get_Documents(); 

并且,包装类不同版本生成的文件不同,还会出错!可见MFC对OLE的态度。

5.2 兼容性

兼容性恐怕比便捷性重要得多。因为便捷性不好嘛,只是开发人员一时的痛哭,但如果用户使用你的软件,却出现各种打不开、死机,那就要命了,可能职位不保!

接口方式的兼容性差。第一,接口可能是区分32位和64位的(不同软件支持不同),比如程序#import的是一个32位AutoCAD自带的tlb文件,那么一般情况下,你的程序只能在32位AutoCAD中运行良好,为了保持兼容,你需要编写两个版本的代码,根据目标计算机自动做出选择。同时,接口还可能区分AutoCAD版本的,也就是说,如果程序#import的是AutoCAD2012,用户计算机是AutoCAD2023的话,那么可能会出错!为了兼容,你还得编写多个版本的AutoCAD版本,至于哪些兼容,哪些不兼容,我也没有统计过。

5.3 结论

因此,我建议采用包装类的方式。

6 后记

一般认为,Com技术,特别是OLE技术,对于一般开发人员而言过时了,我认为,确实过时了。第一,Com技术仅能良好的用于Windwos,不能适应现在的多平台融合的需求;第二,Com技术理念很好,但使用时要求的技术相对较高,特别是用VC,如果你想编写一个支持OLE和Automation让别人调用你的程序,那是难上加难(也可以看我的文章系列:”SegeX Automation:VC程序的Automation支持(让你的程序既是应用程序,又具备Automation服务器的功能 可被别的程序调用,类似Word AutoCAD)“)(还没写好)。但我想说的不是这个,我想说的是Automation技术有时会大大提高工作效率和图件的准确性,这对于很多工程人员来讲,实在是太重要了。举个真实例子,2007年左右,某单位因为一个施工图件在引用勘探资料时,勘探资料比例尺标注错了,注意只是标注错了,显然是拷贝别的图件没有修改全,从而引起施工图不准确,导致施工中大面积变更,直接经济损失超过1个亿(就是一个数字错了,1写成了2),而如果是AutoCAD图件自动化处理,取代人工成图和过程中的手工转移引用,就可以避免此类错误。回到正题,这么重要的技术,虽然落魄了,但你得给一个新的替代技术呀,不然成天只知道叫过时了,过时了,有什么用呢。当然,话说回来,AutoCAD功能强大,它提供了多种开发手段,不一定用Automation技术,比如script、AutoLisp、Arx,还有基于.Net的开发组件,这个我打算另外写文章再专门介绍。但除了AutoCAD,别的很多软件二次开发最多也就支持Automation。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值