VC2010+ArcGIS Engine 10.1 開發(一)


 

 

 

  

 

 

 

 

 

  

 

  1. 使用MFC(VS2010)開發ArcGIS Engine 10.1

網上C#結合ArcGIS Engine的資料簡直太多了,多的都無法形容,但是C++的卻很少,前一段時間不斷的有人問在VC中如何開發ArcGIS Engine,說實話我幾乎沒怎么用過VC,在學校用過,那已經是好多年的事情了,現在重溫VC,不知道會是什么樣的感覺,年末了,大家都比較忙,我也是抽空,靜下心來嘗試的使用VC去開發,2個星期前在博客中發了一篇(http://www.gisall.com/html/63/151663-8220.html),那個是沒有界面的,也就是沒有用到MFC,訪問量還不錯,於是決定寫一個MFC的小例子,界面這塊,我一點都不擅長,習慣了C#中的做法,在C#中就是拖個按鈕,然后直接就在下面寫功能,界面是一個體力活,更是一個藝術活,像我這沒有藝術細胞的人,估計這輩子都做不了漂亮的界面,所以下面的小例子,大家也就不談論界面了,哈哈。現在言歸正傳,開始我們的旅程。

  1. 建立MFC工程

 

 

在這里可以選擇單文檔,也可以選擇基於對話框的,我選擇了單文檔,如下圖:

 

注意下面要選CFormView,默認的是CView,關於這兩個的區別看中間的這個詞語就知道了,如下圖:

 

  1. 添加類庫

在工程上右鍵,屬性找到VC目錄的欄目,在包含那個選項中添加

Engine安裝目錄下的com

SDK目錄下的CPPAPI

還有Common Files\ArcGIS\bin

因為我的目錄中有x86,添加后變成這個樣子了,如下圖:

 

在C/C++選項的預處理中添加:ESRI_WINDOWS,如下圖:

  1. 引入頭文件

在stdfx.h中引入ArcSDK.h 這個目錄文件,編譯的時候

會看到下面的錯誤(不要怕,名稱沖突而已)

兩種解決辦法:

  • 重命名,找到相應的頭文件,在import指令后添加rename屬性(關於這些屬性大家可以自己搜索下)

#include "esrisystem.h"

#import "esrisystemui.olb" raw_interfaces_only raw_native_types no_namespace named_guids exclude( "OLE_HANDLE", "OLE_COLOR", "UINT_PTR" ) rename("ICommand", "esriICommand") rename("IProgressDialog", "esriIProgressDialog")

  • 使用全名(命名空間+接口名稱)

#include "esrisystem.h"

#import "esrisystemui.olb" raw_interfaces_only raw_native_types named_guids exclude( "OLE_HANDLE", "OLE_COLOR", "UINT_PTR" )

其他的依次類推,注意如果重新命名之后,在程序中應使用新的名稱,不然還是會出錯。

  1. 綁定許可和初始化許可,綁定許可是10.0之后的必要操作

bool CMainFrame::AEinit(void)

{

    

#pragma region 綁定許可

        IArcGISVersionPtr ipVer(__uuidof(VersionManager));

        VARIANT_BOOL succeeded;

        if (FAILED(ipVer->LoadVersion(esriArcGISEngine , L"10.1",&succeeded)))

            return false;

#pragma endregion

 

#pragma region 初始化許可

        IAoInitializePtr ipInit(CLSID_AoInitialize);

        esriLicenseStatus status;

        ipInit->Initialize(esriLicenseProductCodeEngine, &status);

        if (status != esriLicenseCheckedOut)

            AoExit(0);

        return true;

#pragma endregion

    

}

 

 

 

  1. 如何顯示地圖

現在頭文件,初始化許可的事情都搞定了,但是地圖如何顯示?想象下C#是如何做的,拖一個地圖控件上去,然后將數據在這個地圖控件上顯示,但是這是VC,不是這么簡單,當然地圖控件肯定是ESRI提供,微軟不會提供一個容納地圖的控件吧?兩種方法:插入控件和直接生成相關的類,我們先用第二個方法,順便回一下MFC。

  1. 添加和地圖控件相關的MFC類

在工程上右鍵,添加類,選擇第一個,如下圖:

彈出一個向導,在可用的ActiveX控件中找到Esri的地圖控件,設置生成的類,如下圖:

在CMapMFCView類中添加頭文件,定義一個CMapControl2的變量,如下圖:

 

 

在類視圖中,右鍵該類,轉到"對話框",出現如下界面:

 

在上圖中拖一個按鈕上去,改一下屬性,並且添加單擊事件(使用類向導完成),然后添加下面的代碼(先不要管,我會在后面介紹,CWsClass類是我添加的用來獲取要素類的見后面)

void CMapMFCView::OnClickedBtn()

{

    

    CRect r;

    GetClientRect(&r);

 

    CWsClass pWs;

 

    BSTR bStringWS = SysAllocString(L"D:\\guest\\數據\\1");

    BSTR bStrFcName = SysAllocString(L"1.shp");

    IFeatureClassPtr ipFeatureClassTest=pWs.GetFeatureClass(bStringWS,bStrFcName);

    SysFreeString(bStrFcName);

    SysFreeString(bStringWS);

    IFeatureLayer *ipFeatureLayerX;

 

    // 查看注冊表或者tlh文件{E663A651-8AAD-11D0-BEC7-00805F7C4268} 完完全全通過COM自己創建對象

    HRESULT hr1=::CoCreateInstance(CLSID_FeatureLayer,NULL,CLSCTX_ALL,IID_IFeatureLayer,(void**)&ipFeatureLayerX);

    if(FAILED(hr1))

        return;

 

    //賦值

    ipFeatureLayerX->putref_FeatureClass(ipFeatureClassTest);

    

    //這句類似Windows的用類來創建窗體(MapControl控件)

    m_MapControl.Create(L"Test",WS_CHILD|WS_VISIBLE,r,this,10000);

    //獲取指針

    IMapControl2Ptr m_ipMapControl=m_MapControl.GetControlUnknown();

 

    IDataset *ipDsetTest;

    HRESULT hr2=ipFeatureLayerX->QueryInterface(__uuidof(IDataset),(void**)&ipDsetTest);

    if(FAILED(hr2))

        return;

 

    m_ipMapControl->AddLayer(ipFeatureLayerX,0);

 

 

    m_MapControl.put_BackColor(16777215);

    //手動釋放

    ipFeatureLayerX->Release();

    ipDsetTest->Release();

 

    ////ATL方式也是智能指針,我們不需要手動釋放

    //CComPtr<IFeatureLayer> ipFtLayer;

    //ipFtLayer.CoCreateInstance(CLSID_FeatureLayer);

 

    //

    //ipFtLayer->putref_FeatureClass(ipFeatureClassTest);

    //m_MapControl.Create(L"Test",WS_CHILD|WS_VISIBLE,r,this,10000);

 

    ////QI

    //IDataset *ipDset;

    //ipFtLayer.QueryInterface(&ipDset);

 

    //BSTR bSName=SysAllocString(L"");

 

    //ipDset->get_Name(&bSName);

 

    //ipFtLayer->put_Name(bSName);

    //m_MapControl.put_BackColor(16777215);

 

    //m_MapControl.AddLayer(ipFtLayer,0);

    //

 

    ////第三種方式,這個比智能指針有優勢,自動QueryInterface

    /*IFeatureLayerPtr ipFeatureLayer;

    HRESULT hr = ipFeatureLayer.CreateInstance(CLSID_FeatureLayer);

    

    ipFeatureLayer->putref_FeatureClass(ipFeatureClassTest);

    m_MapControl.Create(L"Test",WS_CHILD|WS_VISIBLE,r,this,10000);

 

    m_MapControl.put_BackColor(16777215);

 

    m_MapControl.AddLayer(ipFeatureLayer,0);*/

    

    ////第四種方式

    /*IFeatureLayerPtr ipFeatureLayer(CLSID_FeatureLayer);

    ipFeatureLayer->putref_FeatureClass(ipFeatureClassTest);

    IDatasetPtr ipDs(ipFeatureClassTest);

    m_MapControl.Create(L"Test",WS_CHILD|WS_VISIBLE,r,this,10000);

    m_MapControl.put_BackColor(16777215);

    BSTR bSName=SysAllocString(L"");;

    ipDs->get_Name(&bSName);

    ipFeatureLayer->put_Name(bSName);

    m_MapControl.AddLayer(ipFeatureLayer,0);*/

    

    //m_MapControl.AddShapeFile(L"D:\\guest\\數據\\1",L"1.shp");

}

添加一個獲取要素類的類,如下圖:

 

創建完類之后,添加一個獲取要素類的函數(該函數的代碼,等看完后面的代碼說明之后應該就一目了然)

IFeatureClassPtr CWsClass::GetFeatureClass(BSTR sWorkspacePath,BSTR sFileName)

{

    CComPtr<IWorkspaceFactory> ipWorkspaceFactory;

    ipWorkspaceFactory.CoCreateInstance(CLSID_ShapefileWorkspaceFactory );

    IWorkspace * m_workspace;

 

    ipWorkspaceFactory->OpenFromFile(sWorkspacePath,NULL,&m_workspace);

 

    IFeatureClassPtr ipFeatureClasses;

    if(m_workspace!=NULL)

    {

 

        IFeatureWorkspacePtr ipFeatWorksapce(m_workspace);

        HRESULT hr=    ipFeatWorksapce->OpenFeatureClass(sFileName,&ipFeatureClasses);

 

        if(FAILED(hr))

            return NULL;

    }

    return ipFeatureClasses;

 

 

}

運行后可以看到如下效果(已經說了,不要嘲笑界面)

 

  1. 代碼解釋

需要對Windows編程和MFC有了解,在這里我只介紹和ArcGIS Engine相關的.

  1. 如何實例化對象

    1. 最原始做法(我自己這樣稱呼,學習COM的時候就是這種方式吧)

ArcGIS Engine 的組件庫都是基於COM技術的,在COM中我們都是通過接口來完成任務的,但是在使用接口之前,必須要知道這個接口指向的是那個對象,也就是說我們必須實例化對象,然后將對象的返回地址賦給一個接口變量,在C#中這種關系似乎很清楚,如下:

IRasterGeometryProc pRasterGProc = new RasterGeometryProcClass();

之后我們就可以通過pRasterGProc相關操作了,但是我們現在用的是C++,上面這個是不能用了,如果看過《COM本質論》或者其他和COM相關的書籍的人可能知道在COM中是通過CoCreateInstance函數來獲取一個對象的指針變量的,具體的代碼如下:

    // 查看注冊表或者tlh文件{E663A651-8AAD-11D0-BEC7-00805F7C4268} 完完全全通過COM自己創建對象

    HRESULT hr1=::CoCreateInstance(CLSID_FeatureLayer,NULL,CLSCTX_ALL,IID_IFeatureLayer,(void**)&ipFeatureLayerX);

    if(FAILED(hr1))

        return;

    //賦值

    ipFeatureLayerX->putref_FeatureClass(ipFeatureClassTest);

 

在COM中,因為一個類可以實現多個接口,每個接口只可以訪問自己定義的方法,如果要使用定義在別的接口中的方法呢?我們自然而然的想到了將這個接口"切換"過去,通俗的講就是這樣,只不過專業術語要做QueryInterface(接口查詢/訪問,英語的理解就行了,不糾結這個翻譯了),在C#中我們使用一個as就可以解決問題,如下(IRaster QI到IRasterProps 上):

IRaster pRaster = pRasterLayer.Raster;

IRasterProps pRasterPro = pRaster as IRasterProps;

C++中的做法和這個不一樣,但是目的一樣,都是為了"切換"到另外一個接口上,因為要素類實現了IDataset中的方法,那么肯定是可以QI到這個上面去的,下面是做法:

    IDataset *ipDsetTest;

    HRESULT hr2=ipFeatureLayerX->QueryInterface(__uuidof(IDataset),(void**)&ipDsetTest);

    if(FAILED(hr2))

        return;

我們使用了接口,如果在不用的時候一定要自己給釋放掉,如下面所示:

ipFeatureLayerX->Release();

ipDsetTest->Release();

 

現在要素類有了,要素圖層有了,我們將這兩個關聯起來,一起放到地圖控件中去,但是我們的地圖控件在哪里,如果習慣了C#中的那種直接將控件拖到窗體上的做法,乍一看一頭霧水,別急,待會兒就雨過天晴了。

Esri在在VC中也提供了我們所謂的控件,我們通常稱之為ActiveX控件,在這里我沒有插入和拖放,而是直接用類來創建,還記得我剛才插入類的時候,選擇了ActiveX控件中的MFC類,又接着選擇了MapControl,然后我們的工程中就多了一個類,這個類其實跟一般的MFC類沒多大區別,只是這個類里包含和和地圖相關的東西,在這里說一下MFC類,MFC類其實就是一個C++類,只是這個類里面封裝了Windows的句柄(窗口,菜單等),而ActiveX控件本質上也算是一個窗口吧。我們要得到一個窗口句柄,按照MFC的邏輯:首先實例化一個類,然后用類的Create函數構造這個句柄(MFC在內部已經做了處理):

    //這句類似Windows的用類來創建窗體(MapControl控件)

    m_MapControl.Create(L"Test",WS_CHILD|WS_VISIBLE,r,this,10000);

    

//獲取指針

    IMapControl2Ptr m_ipMapControl=m_MapControl.GetControlUnknown();

 

 

    m_ipMapControl->AddLayer(ipFeatureLayerX,0);

 

    m_MapControl.put_BackColor(16777215);

    在這里我們可以通過類來操作,也可以通過接口來操作,這里我用m_ipMapControl添加了圖層,下面的幾個代碼中用m_MapControl來操作,請注意這兩個的不同。

如果使用上面的方式,我們要不斷的使用CoCreateInstance(),QueryInterface(),Release()等方法,對我們來說有太多的不便,如果忘記了Release可能還會出問題,下來我們討論我注釋掉的幾種方法:

  1. ATL方式(智能指針)

使用智能指針,我們不需要自己手動release等,這個會自動完成,但是對於QI,我們也需要寫,但是已經比上面的少了很多代碼了:

    CComPtr<IFeatureLayer> ipFtLayer;

    ipFtLayer.CoCreateInstance(CLSID_FeatureLayer);

 

    ipFtLayer->putref_FeatureClass(ipFeatureClassTest);

    m_MapControl.Create(L"Test",WS_CHILD|WS_VISIBLE,r,this,10000);

 

    //QI

    IDataset *ipDset;

    ipFtLayer.QueryInterface(&ipDset);

 

    BSTR bSName=SysAllocString(L"");

 

    ipDset->get_Name(&bSName);

 

    ipFtLayer->put_Name(bSName);

    m_MapControl.put_BackColor(16777215);

 

    m_MapControl.AddLayer(ipFtLayer,0);

    

  1. Ptr結尾的方式

當我們import ArcGIS Enine的COM組件是,編譯器自己會使用_com_ptr_t幫我們生成,如下圖:

關於中方式和ATL的區別,我在網上查了下,看到下面幾點:

在用VC開發應用程序時,有兩個引用計數類可供我們使用。_com_ptr_t與CComPtr,它們都能很好的幫助我們解決引用計數處理。但這兩個類還是有一點小小的區別,有的時候這一點區別也是致命的,因此我們必須清楚它們的差別。下面我羅列了它們之間的差別:

1. CComPtr的&運算符不會釋放原指針,而_com_ptr_t會釋放原指針。

2. CComPtr對AddRef與Release做了限制,也就是不充許調用這兩個方法,而_com_ptr_t並沒有限制。

3. CComPtr只能接受模版參數指定的指針,_com_ptr_t可以接受任何接口指針,並自動調用QueryInterface得到模板參數指定的指針類型。

 

這種方式又可以有兩種方式來實例化對象,通過CreateInstance函數和直接用在聲明的時候用CLSID:

    IFeatureLayerPtr ipFeatureLayer;

    HRESULT hr = ipFeatureLayer.CreateInstance(CLSID_FeatureLayer);

和:

IFeatureLayerPtr ipFeatureLayer(CLSID_FeatureLayer);

ipFeatureLayer->putref_FeatureClass(ipFeatureClassTest);

 

我也不知道這種方式如何命名,但是這種比ATL的那種方式更智能,這體現在QI的時候也簡單了很多:

IFeatureLayerPtr ipFeatureLayer(CLSID_FeatureLayer);

ipFeatureLayer->putref_FeatureClass(ipFeatureClassTest);

    IDatasetPtr ipDs(ipFeatureClassTest);//這句話就QI過去了

    m_MapControl.Create(L"Test",WS_CHILD|WS_VISIBLE,r,this,10000);

 

    m_MapControl.put_BackColor(16777215);

 

    BSTR bSName=SysAllocString(L"");;

 

    ipDs->get_Name(&bSName);

 

    ipFeatureLayer->put_Name(bSName);

    m_MapControl.AddLayer(ipFeatureLayer,0);

 

  1.  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM