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.  
posted on 2013-01-04 09:41  醉意人间  阅读(9139)  评论(1编辑  收藏  举报