• CorelDRAWX4的C++插件开发(四十一)纯C++插件开发(5)实现六个纯虚函数


    • 这一节我们实现六个剩余的纯虚函数,先上代码
    HRESULT __stdcall CongLingKaiShi::QueryInterface(REFIID riid, void** ppvObject) {
    	//如果查询的IID接口是自动化调度接口IDispatch或未知接口IUnknown或插件IVGAppPlugin则统统返回IVGAppPlugin接口
    	if (riid == IID_IDispatch || riid == IID_IUnknown || riid == __uuidof(VGCore::IVGAppPlugin)) { 
    		*ppvObject = (VGCore::IVGAppPlugin*)this;
    		return S_OK;
    	}
    	//其它接口一律返回无
    	return E_NOINTERFACE;
    }
    
    HRESULT __stdcall CongLingKaiShi::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) {
    	switch (dispIdMember) {
    	//在CDR中鼠标点击任意插件时触发该DISPID号0x0014
    	case 0x0014:
    		//pDispParams是函数传参结构体,如果它存在且cArgs(参数的总数)为1个时继续判定
    		if (pDispParams != NULL && pDispParams->cArgs == 1) {
    			//pDispParams(函数传参结构体)的rgvarg(参数数组)中的第一个[0]参数的名称和我的按钮名一样时触发
    			if (_bstr_t(pDispParams->rgvarg[0].bstrVal) == _bstr_t("CongLingCeShi")) {
    				MessageBox(hAppWnd,_bstr_t("C++插件调用成功:"), _bstr_t("提示"), MB_OK);
    			}
    		}
    		break;
    	case 0x0015:
    		// 设置工具栏的显隐状态
    		if (pDispParams != NULL && pDispParams->cArgs == 3 ) {
    			//名称里面前八个字母是CongLing的时候才修改为显示图标
    			std::string lpszText = _com_util::ConvertBSTRToString(pDispParams->rgvarg[2].bstrVal);
    			if (lpszText.substr(0, 8) == "CongLing") {
    				*pDispParams->rgvarg[1].pboolVal = VARIANT_TRUE;
    			}
    		}
    		break;
    	}
    	return S_OK;
    }
    
    HRESULT __stdcall CongLingKaiShi::raw_OnLoad(VGCore::IVGApplication* Application) {
    	//获取CDR的根结构体Application
    	corelApp = Application;
    
    	//获得CDR主窗口的句柄
    	hAppWnd = reinterpret_cast<HWND>(corelApp->AppWindow->Handle);
    
    	//获取本DLL的HINSTANCE句柄
    	myInstance = GetModuleHandle(LPCWSTR(strrchr(__FILE__, '\\') + 1));
    	//myInstance = GetModuleHandle(L"CongLingKaiShi.cpg");    上一行等价于这行
    
    	return S_OK;
    }
    
    HRESULT __stdcall CongLingKaiShi::raw_StartSession() {
    		//设一个值=true 默认要新建插件面板
    		bool createBar = true;
    		//如果已有同名面板则改为不新建
    		for (int i = 1; i <= corelApp->CommandBars->Count; i++) {
    			if (corelApp->CommandBars->Item[i]->Name == _bstr_t("CongLingToolBar")) {
    				createBar = false;
    				break;
    			}
    		}
    		//根据该值判定是否新建面板
    		if (createBar) {
    			//新建工具栏  最后一个值设为TRUE是永久化,改动了位置大小等状态都会记忆
    			VGCore::CommandBarPtr CongLingToolBar = corelApp->CommandBars->Add(_bstr_t("CongLingToolBar"), VGCore::cuiBarPosition::cuiBarFloating, VARIANT_TRUE);	
    			CongLingToolBar->Visible = TRUE;//显示工具栏
    		}
    
    		//设定一个按钮
    		corelApp->AddPluginCommand(_bstr_t("CongLingCeShi"), _bstr_t("测试"), _bstr_t("用来测试各种功能的"));
    
    		//添加该按钮(最后一个参数:是否设为临时按钮)
    		VGCore::CommandBarControlPtr ctl = corelApp->CommandBars->Item[_bstr_t("CongLingToolBar")]->Controls->AddCustomButton(VGCore::cdrCmdCategoryPlugins, _bstr_t("CongLingCeShi"), 1, VARIANT_TRUE);
    
    		//注册调度接口IDispatch
    		zhuCe = corelApp->AdviseEvents(this);
    
    		return S_OK;
    }
    
    HRESULT __stdcall CongLingKaiShi::raw_StopSession() {
    	corelApp->UnadviseEvents(zhuCe);//注销调度接口
    	return S_OK;
    }
    
    HRESULT __stdcall CongLingKaiShi::raw_OnUnload() {
    	delete this;//回收结构体
    	return S_OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 好了, 以上我们就实现了剩余所有的纯虚函数
    • 第一个函数是QueryInterface,就是未知接口中的第一个查询接口,第一个参数是一个riid,就是UUID,也就是主程序跑过来问我们有没有这个ID号的接口,这时CDR问的一般就是IDispatch接口,所以我们把这个IID_IDispatch 写在第一位进行判定,在我的实际测试中,未知接口IID_IUnknown 和IVGAppPlugin接口都没有被访问过所以我写在第二位和第三位,也是为了程序的健壮性而写上去的,只要第满足第一个 ,那么第二和第三个就不会再判断了,不会浪费算力.
    • 然后我们返回一个接口到*ppvObject = (VGCore::IVGAppPlugin*)this;,这里面的*ppvObject就是一个实际的需要填写的回传值[out],主程序会得到这个*ppvObject,所以我们必须把*ppvObject赋值为程序所需要的接口,(VGCore::IVGAppPlugin*)this就相当于返回CongLingKaiShi这个结构体的地址,this就是代表的本体CongLingKaiShi, 因为CongLingKaiShi包含了这三个接口.我不管你呼叫的是哪个,反正到这里面找就对了
    • 然后第二个函数Invoke是最后第七个函数,也是实现功能最重要的函数,在注册了IDispatch之后主程序就会访问我们这个QueryInterface来查找IDispatch,然后在IDispatch里面运行这个Invoke
    • 然后对switch (dispIdMember)对这个DISPID 也就是ID号进行读取,我们只需要判断它是否为0x0014就行了,当用户在CDR主程序的插件上点击了鼠标左健就会触发这个0x0014的Invoke函数,所以我们接着再判断一下参数结构体pDispParams 是否被定义了,且里面的参数个数是1pDispParams->cArgs == 1这个cArgs 就是参数结构体当中记录参数个数的成员,当都满足之后,我们再接着判断其中这个参数的名称是否是我们按钮名称if (_bstr_t(pDispParams->rgvarg[0].bstrVal) == _bstr_t("myCeShi")) ,如果是myCeShi那就说明按中了我们的插件按钮,然后我们把想要执行的代码放在其中即可.
    • 我随便写了一个弹窗MessageBox(hAppWnd,_bstr_t("C++插件调用成功:"), _bstr_t("提示"), MB_OK);调用效果如下图
      在这里插入图片描述
    • 接下来就是raw_OnLoad函数,首先获取CDR的根结构体Application,这个在第三十二章中详细说明了
    • 然后获取CDR主窗口的句柄hAppWnd = reinterpret_cast(corelApp->AppWindow->Handle);Handle是一个长整数LONG,我们对它进行强制符号转换为HWND,一个窗口句柄,这样子我们使用模态弹窗就非常方便了
    • 然后获取本DLL的HINSTANCE句柄,myInstance = GetModuleHandle(LPCWSTR(strrchr(__FILE__, '\\') + 1));实际上就是这一行myInstance = GetModuleHandle(L"CongLingKaiShi.cpg"); 是一样的效果,GetModuleHandle是通过文件名获取句柄的函数,所以要把文件名进行传参,这这LPCWSTR(strrchr(__FILE__, '\\') + 1)这种写法,就是用了一个预定义宏__FILE__,这里面就是一个从盘符开始到后缀的全部的一个名称,所以只要把路径去掉即可,strrchr(__FILE__, '\\')在全路径查到最右的一个\路径反斜杠的下标指针,然后加上+ 1就返回CongLingKaiShi.cpg这一串的第一个字母C的下标指针,所以就得到了我们想要的文件名了,LPCWSTR()这是把这个字符串改成双字节的UNICODE模式用以符合GetModuleHandle的传参要求加上去的.
    • 如果不想这么复杂直接用myInstance = GetModuleHandle(L"CongLingKaiShi.cpg"); 是一样的效果,但是建议用上面一种写法哈,这样子更改了文件名也不会出错
    • 接下来在raw_StartSession函数中我们要开始新建插件面板和按钮了
    • 我们一个循环当中一个个判定插件面板的名称corelApp->CommandBars->Item[i]->Name是否是_bstr_t("CongLingToolBar"),如果已经有了这个面板我们就不需要新建, 把createBar 的值改成false;
    • 然后判定if (createBar) ,当然第一次运行的时候这个值一定是true,从第二次往后都会是false,因为面板新建一次就是永久的,所以必须这样子写不然会遇上每运行一次程序就新增一个面板的BUG
    • case 0x0015: 是设计插件按钮的显隐状态,我们用了一个判定只有前面八字个母匹配了CongLing就让这个按钮设置为显示状态
    • 面板新建完之后,就设定一个按钮AddPluginCommand函数,第一个参数是按钮的程序名称,第二个参数是图标名称,第三个参数是鼠标悬停名称
    • 然后就是把这个按钮添加进面板当中的函数AddCustomButton,第一个参数是按钮的模式,有两种一种是GMS另一种就是我们自己定义的,我使用VGCore::cdrCmdCategoryPlugins,第二参数是按钮的程序名称,第三个参数是插入位置,最后一个:是否设为临时按钮.我们在制作插件的时候应该要设为VARIANT_TRUE临时状态,
    • 当然如果做成了成品定型了的话就可以设成FALSE,只要在第一次启动时添加一次就可以了
    • 在启动的最后我们开始注册调度接口zhuCe = corelApp->AdviseEvents(this);
    • 在我们关闭CDR主程序的时候会运行raw_StopSession,这时我们注销本插件corelApp->UnadviseEvents(zhuCe);
    • 最后在卸载时delete this;回收结构体在堆区的内存空间
    • 以上就是纯C++插件的全部的比较精简的代码展示了,我们把所有代码组合起来顺序如下,头文件->声明->插件结构体->六个虚函数实体->暴露导出函数,用这个顺序组合文件,然后编译即可,然后就得到一个CPG后缀的文件
      在这里插入图片描述
    • 然后把这个CPG文件放到CDR的主路径下CorelDRAW X4\Draw\Plugins大家根据自己的安装路径来修改如下图
      在这里插入图片描述
  • 相关阅读:
    vlan trunk stp攻防
    javascritp如何判断是从刷新(重新加载)、正常打开(或链接打开)、还是从浏览器回退进入页面的
    淘宝如何选词打造黄金标题?构词规则是什么?
    web前端-javascript-立即执行函数(说明、例子)
    Ajax异步
    瓦斯抽采VR应急救援模拟仿真系统筑牢企业安全生产防线
    学习MyBatis框架及学习的过程中遇到的问题
    C#使用自定义的泛型节点类 Node<T>实现二叉树类BinaryTree<T>及其方法
    【Java 基础篇】Java多线程编程详解:线程创建、同步、线程池与性能优化
    《进阶篇第9章》学习vuex知识点后练习:把求和案例改成getters
  • 原文地址:https://blog.csdn.net/weixin_43716462/article/details/127729617