天天看點

VC調用AutoCAD自動化的兩種方法(包裝類、接口)使用詳解

----哆啦劉小洋 原創,轉載需說明出處 2022-12-29

VC調用AutoCAD自動化

    • 1 簡介
    • 2 AutoCAD的Automation類型庫說明檔案
    • 3 包裝類方式
      • 3.1 VC6
      • 3.2 VC2022
    • 4 接口調用方式
    • 5 兩種方式對比
      • 5.1 使用便捷性
      • 5.2 相容性
      • 5.3 結論
    • 6 後記

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”,如下圖:

VC調用AutoCAD自動化的兩種方法(包裝類、接口)使用詳解

接下來會打開檔案對話框讓你選擇,我們選擇acax??chs.tlb,??是AutoCAD的版本号。

VC調用AutoCAD自動化的兩種方法(包裝類、接口)使用詳解

打開後,會讓你确認要包裝哪些接口類:

VC調用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”:

VC調用AutoCAD自動化的兩種方法(包裝類、接口)使用詳解

注意,在VC2022中,一定要在解決方案資料總管(Solution Explore)欄,類視圖中點選項目右鍵是沒有這個選項的。這和以前一些版本不同。

在添加新項對話框中,選擇“已安裝”下的“Visual C++”下面的“MFC”,在右面子項中,選擇“TypeLib中的MFC類”(英文版:選擇“Installed”下的“Visual C++”下面的“MFC”,在右面子項中,選擇“MFC Class from Typelib”)。

VC調用AutoCAD自動化的兩種方法(包裝類、接口)使用詳解

選擇好後,點選“添加”:

VC調用AutoCAD自動化的兩種方法(包裝類、接口)使用詳解

如上圖所示,選擇檔案方式,再選擇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檔案,我的建議是都注釋掉或删除。也可以所有檔案都删除這句話,隻是無法使用接口需要用到的常量和枚舉定義。

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 使用便捷性

明顯接口方式大比分獲勝,第一是整個準備工作就是拷貝一個檔案,添加一行代碼。第二是接口使用便捷,接口方法傳回的接口是一個接口指針,可以實作連續指向,比如:

包裝類就很難啦!每個函數傳回的是一個分發接口LPDISPATCH,是以需要轉換,比如下代碼,CAcadAppliction類接口函數get_Documents傳回的就是一個LPDISPATCH,不信去看代碼。而docs呢,是CAcadDocuments類,是以必須要轉換,是以很難實作接口的連續指向。

并且,包裝類不同版本生成的檔案不同,還會出錯!可見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。