C#开发word(wps)插件(com加载项)作为一个web前端开发,不搞点后端的东西玩玩也说不过去。除了常见的node
转载地址https://juejin.cn/post/6844903992846204936
作为一个web前端开发,不搞点后端的东西玩玩也说不过去。除了常见的nodejs 或者java以外,最近收到需求,要搞一个word(wps)插件,用于方便总裁办的同事发布写一些公文,格式化版头,标题,附件等。所以最近也一直在研究这方面的知识。奈何网上参考的文献太少,太杂。于是这里…
前言
作为一个web前端开发,不搞点后端的东西玩玩也说不过去。除了常见的nodejs 或者java以外,最近收到需求,要搞一个word(wps)插件,用于方便总裁办的同事发布写一些公文,格式化版头,标题,附件等。所以最近也一直在研究这方面的知识。奈何网上参考的文献太少,太杂。于是这里我便总结一下开发全过程,供大家参考交流。
一、需求确认
关于word(wps)插件,要实现的最终效果,无非如下两种:
- 1.自定义宏.生成一个xxx.dotm模板文件。打开这个模板文件,界面如下:
- 2.com加载项.生成一个xxx.dll写入到word关键路径,使得word能读取xx.dll,并在word的菜单栏能展示你自定义的菜单
对比上述两种方案,我们发现,通过com加载项来实现word(wps)插件对使用者来说是相当简洁和便利的,一目了然。因此,我们确定需求,就按照这个来实现,产出xxx.dll等相关文件。
二、技术方案确定
因为这些基于microsoft office的上做的二次开发,必须要基于window系统,参考我微软的技术栈开发。微软的技术主要的无非两种:
- vb (开发工具visual basic 6.0)
- C# (开发工具visual studio 9)
两者无所谓好坏,但我个人对vb不太感冒,而且气语法跟java的有点点差异的,因此技术选型选择C#。
于此同时,本次我是基于金山软件wps(国内很多公司,尤其是国企央企等电话都会统一安装wps)开发的插件,调用word的接口方法跟microsoft office一模一样。因此开发环境准备如下:
- 操作系统 win7~win10
- wps 10.8.0.6370-个人专业版
- Microsoft Visual Studio 2019 Community/professional(都行,哪个免费下载哪个)
- Microsoft.NET 的Framework版本 v4.0.30319
三、本地开发
1、新建项目
建议安装完 Microsoft Visual Studio后以管理员身份运行启动
选中解决方案下的项目---【右键】---【属性】,开始如下设置:
【生成】---勾选【为com互操作注册】
【调试】---选中【启动外部程序】---选择wps安装路径
我这里是wps文字,因此选中后的完成路径是:
C:\Program Files (x86)\Kingsoft\WPS Office\10.8.0.6370\office6\wps.exe
如果你们是基于wps 表格
C:\Program Files (x86)\Kingsoft\WPS Office\10.8.0.6370\office6\et.exe
wps ppt 的话
C:\Program Files (x86)\Kingsoft\WPS Office\10.8.0.6370\office6\wpp.exe
【签名】--【新建签名】
图中的xx_officialDocument.pfx就是此处下拉框选则【新建】后当场生成的。
至此,新建项目完毕,
2、添加引用
因为我们要掉office的相关接口,所以就必须引用office 和kingsoft 相关的dll,即类库,
Upgrade WPS Office 3.0 Object Library对应的是WPS文字、Upgrade WPS Spreadsheets 3.0 Object Library对应的是WPS表格。
提示:若添加后提示引用出错,可以尝试的解决方法有:
- 以Administrator账户登录Windows
- 以管理员身份运行Visual Studio
- 在Administrator账户中,以管理员身份安装WPS Office
- WPS Office个人版需要运行专业版中的 WPSOfficePIA.exe 以注册所引用的程序集 安装WPS Office专业版,安装时WPS会自动注册相关程序集
引用添加完毕后,我们可以看到我们总共依赖了哪些引用
3、新增资源文件
在Properties上右键→添加→新建项→找到“资源文件”→输入或保持默认名称→添加
打开Resource1.resx后--【添加资源】---【添加新文本文件】---【输入MyRibbon】
这个MyRibbon.txt文件就是一个xml菜单文件,配置你在wps中的菜单的显示
4、编辑资源(菜单)文件
双击MyRibbon.txt后进入编辑,输入如下文本
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui">
<ribbon startFromScratch="false">
<tabs>
<tab id="MyTab" label="wps中菜单名字" visible="true" insertAfterMso="TabDeveloper">
<group id="rhgroup" label= "红头设置">
<button id="rh" label="通用红头" onAction="setCommonRH2" getImage="GetRibbonImage" size="large"/>
<button id="Test4" label="专会" onAction="setSMRH2" getImage="GetRibbonImage" size="large"/>
<button id="Test5" label="上行文" onAction="setUpLineRH2" getImage="GetRibbonImage" size="large"/>
<button id="Test6" label="人事任免" onAction="setHRRH2" getImage="GetRibbonImage" size="large"/>
</group>
<group id="Test" label= "段落格式设置">
<button id="Test" label="公文标题" onAction="setOfficialTitle" getImage="GetRibbonImage" size="large"/>
<button id="Test" label="一级标题" onAction="set1Title" getImage="GetRibbonImage" size="large"/>
<button id="Test" label="二级标题" onAction="set2Title" getImage="GetRibbonImage" size="large"/>
<button id="Test" label="正文" onAction="setMainPara" getImage="GetRibbonImage" size="large"/>
<button id="Test" label="附件" onAction="setAttachIn" getImage="GetRibbonImage" size="large"/>
</group>
<group id="Test" label= "附件(后附)">
<button id="Test" label="附件(后附)" onAction="setAttachAfter" getImage="GetRibbonImage" size="large"/>
</group>
<group id="Test" label= "间距调整">
<button id="Test" label="加大间距" onAction="addFontSpace" getImage="GetRibbonImage" size="large"/>
<button id="Test" label="减小间距" onAction="reduceFontSpace" getImage="GetRibbonImage" size="large"/>
</group>
</tab>
</tabs>
</ribbon>
</customUI>
- tabs表示选项卡,id是“MyTab”,显示的名称是“我的插件”,在“开发工具”选项卡之后插入本选项卡
- group表示选项卡中的组,id是“Test”,名称是“组名称”
- button表示命令按钮,id是“Test”,名称是“Hello”,回调“Test”事件,从“GetRibbonImage”事件中获取按钮图标,按钮显示大尺寸 Id、Label、onAction、getImage引号中的字符可以自定义,且保证唯一性
关于这个ribbon,详情可以参考微软官方文档: Customizing the 2007 Office Fluent Ribbon for Developers (Part 1 of 3)
5、修改主类
这里的主类,就是我们一开始把Class1.cs该成了OfficialDocument.cs这个文件。当然你可以命名为其他。
打开了OfficialDocument.cs: 文件最上方就是我们引用的类,都写进来
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AddInDesignerObjects;
using Office;
using System.Windows.Forms;
继续往下写
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AddInDesignerObjects;
using Office;
using System.Windows.Forms;
// namespace [yourProjectName] 视具体情况改成你的项目名
namespace yourProjectName
{
public class Class1 : IDTExtensibility2, IRibbonExtensibility
{
{
}
}
有红色的波浪线,提示我们有错,不过没关系,鼠标悬浮在上面,点击黄色灯泡图标的下拉箭头---【实现接口】,visual studio 会自动实现其接口。IRibbonExtensibility 这个也一样这么操作一下。
得到修复后的代码如下:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AddInDesignerObjects;
using Office;
using System.Windows.Forms;
using System;
namespace yourProjectName
{
public class Class1 : IDTExtensibility2, IRibbonExtensibility
{
public void OnConnection(object Application, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom)
{
throw new NotImplementedException();
}
public void OnDisconnection(ext_DisconnectMode RemoveMode, ref Array custom)
{
throw new NotImplementedException();
}
public void OnAddInsUpdate(ref Array custom)
{
throw new NotImplementedException();
}
public void OnStartupComplete(ref Array custom)
{
throw new NotImplementedException();
}
public void OnBeginShutdown(ref Array custom)
{
throw new NotImplementedException();
}
public string GetCustomUI(string RibbonID)
{
throw new NotImplementedException();
}
}
}
在OnConnection事件中初始化app和wordDoc对象
public class Class1 : IDTExtensibility2, IRibbonExtensibility
{
public static Word.Application app = null;
public static object wps;
public static Word.Document wordDoc;
Object Nothing = System.Reflection.Missing.Value;
public void OnConnection(object Application, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom)
{
wps = Application;
app = wps as Word.Application;
wordDoc = app.Documents.Add(ref Nothing, ref Nothing, ref Nothing, ref Nothing);
//wordDoc = app.ActiveDocument;
wordDoc.PageSetup.PaperSize = Word.WdPaperSize.wdPaperA4;
wordDoc.PageSetup.TopMargin = app.CentimetersToPoints(3.7f); // 37mm,对应104.9磅(1磅约等于0.3572mm)
wordDoc.PageSetup.BottomMargin = app.CentimetersToPoints(3.5f);
wordDoc.PageSetup.LeftMargin = app.CentimetersToPoints(2.8f);
wordDoc.PageSetup.RightMargin = app.CentimetersToPoints(2.7f);
}
...
}
在GetCustomUI事件中调用在Resource1.resx中添加的MyRibbon.txt,获取菜单配置文件
public string GetCustomUI(string RibbonID)
{
return Properties.Resource1.MyRibbon;
}
添加事件试试。我们在MyRibbon.txt文件中的每一项按钮里面,都有一个onAction,这个就是绑定的事件。需要我们在Class1.cs文件中去实现
<button id="Test" label="加大间距" onAction="addFontSpace" getImage="GetRibbonImage" size="large"/>
我们来实现一个addFontSpace,当你点击这个按钮的时候,就触发了此事件
public void reduceFontSpace(IRibbonControl ctrl)
{
MessageBox.Show("you click reduceFontSpace")
//float former = app.Selection.Font.Spacing;
//app.Selection.Font.Spacing = former - 0.3f;
}
提示:MessageBox需要引用System.Windows.Forms。引用方法如下:
四、注册表操作
在第三部中,我们实现了一个自定义的菜单,并期望其加到wps的菜单栏中,具体展示结果如下
怎么样才能展示这个菜单呢?
在【开发工具】---【com加载项】选中我们的实现的com加载项(wps专业版才有【开发工具】这个菜单选项):
那么问题又来了,怎么样才能在这里显示这么多的com加载项来供我们选中? 这里我们要自定义Zxxx.OfficialDocument这个com加载项,就必须操作注册表,在启动wps的时候会自动读取相关的内容(实际情况会更复杂)。现在来实现实现一个reg文件,操作注册表
1、新增reg文件
新增一个空的txt记事本文件→ 修改后缀名,另存为install.reg→ 右键此文件编辑→ 拷贝如下代码:
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USERSOFTWAREMicrosoftOfficeWordAddinsZhjk.OfficialDocument]
"FriendlyName"="Zhjk.OfficialDocument"
"Description"="【自定义中文描述方便他人理解】"
"LoadBehavior"=dword:00000003
"CommandLineSafe"=dword:00000001
[HKEY_CURRENT_USERSoftwareKingsoftOfficeWPSAddinsWL]
"Zhjk.OfficialDocument"=""
需要注意的是:
[HKEY_CURRENT_USERSOFTWAREMicrosoftOfficeWordAddinsZhjk.OfficialDocument]
这里 [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\Word\Addins
固定不变,后面的xxx1.xxx2,表示 xxx1是你项目的名,xxx2是你cs入口文件的类名,本例中就是yourProjectName.Class1
以上,写入注册表信息install.reg文件完成。
但是,如果你想清理掉这个注册表信息,你可以手动在命令行输入regedt,打开操作界面,你也可以专门生成一个unistall.reg文件,双击实行即可。 unistall.reg 内容如下:
Windows Registry Editor Version 5.00
[-HKEY_CURRENT_USERSOFTWAREMicrosoftOfficeWordAddinsZhjk.OfficialDocument]
[HKEY_CURRENT_USERSoftwareKingsoftOfficeWPSAddinsWL]
"Zhjk.OfficialDocument"=-
注意减号(横杠)“-”,还有xxx1.xxx2(本例子中是Zhjk.OfficialDocument)要与你安装的install.reg里面的保持一致。若开发的是WPS文字、WPS表格的外接程序,则注册路径中HKEY_CURRENT_USER\SOFTWARE\Microsoft
后面:WPS文字对应的是Word和WPS;WPS表格对应的是Excel和ET
某些电脑可能要用管理权权限来运行这个reg文件
2、验证注册表写入
命令行输入 regedit
沿着这个HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\Word\Addins
路径一直打开目录,如果看到如下,即说明写入注册表成功
五、开发环境启动调试
之前做了那么多准备工作,现在可以启动调试,看看我们的wps 文字到底有没有按照预定的情况加载自定义菜单。 打开visual studio 界面,在degug模式下,点击启动按钮:
然后自动自动wps文字(如果此前你打开了wps文字应用,或者word,请先关掉)
看到此界面,说明我们的自定义com加载项本地调试成功。
六、C# 操作word(wps)版面格式,字体样式等
详情请见我的另一篇文章。
七、基础dll强签名
1、生成WPS演示所需的PIA
部署之前,我们要先对我们的dll签名,保证系统信任此文件
- 首先、D盘新建一个PIA文件夹,打开WPS的安装文件夹,找到ksoapi.dll和wpsapi.dll,拷贝到PIA文件夹中。(如果你是ppt的com插件,就把wppapi.dll拷贝过来,如果是excel,那么就拷贝etapi.dlll)。
- 开始菜单中,找到visual studio 2019 文件目录,点击 开发人员命令提示符(Developer Command Prompt for vs 2019)
- 进入到PAI路径下
- 输入TlbImp wpsapi.dll(低版本的visual studio 可能需要输入执行两次命令: 一次是
TlbImp ksoapi.dll
,再执行TlbImp wpsapi.dll
) 后回车,会在PIA文件夹中生成三个dll:
Office.dll
VBIDE.dll
Word.dll
后面我们需要这三个dll。
2、强签名
若用户是WPS专业版则不需要,为了兼容性,推荐进行强签名。 另外,如果项目中引用了第三方未签名的dll,则必须进行强签名才能引用。
- 输入sn -k zhjk.pfx后回车,生成随机密钥对
- 输入ildasm Office.dll/out:Office.il后回车,采用反汇编工具ildasm生成中间语言。
- 输入ilasm /dll /res:Office.res /key:zhjk.pfx Office.il /out:Office.dll后回车,采用汇编工具ilasm(注意不要看成ildasm)重新生成dll
- Word.dll的强签名操作按照此前类推,输入以下命令
ildasm Word.dll /out:Word.il
ilasm /dll /res:Word.res /key:zhjk.pfx Word.il /out:Word.dll
可以看到强签名后的文件如下:
Word.dll、Office.dll这两个强签名的,就是我们后面需要的文件。
八、Release 打包生成应用dll
1、再visual studio 中debug模式改成release,生成(为COM互操作注册)、调试(启动外部程序)跟dubug模式下保持一致
2、关闭所有的wps文字文档,然后点击“启动”,这样可以得到Release模式下的dll,在工程的bin/Release目录下
3、将刚才的强签名生成的Word.dll、Office.dll拷贝到工程的bin/Release目录下
凑齐这么多dll等相关的文件,我们可以召唤神龙了。
九、生产部署
在此前的步骤中,我们拿到了强签名的dll 和Release 模式下应用生曾的dll,
Zhjk.dll
Zhjk.pdb
Zhjk.tlb
Word.dll
Office.dll
现在开始对window系统进行操作了。
1、此前创建的安装install.reg和卸载unistall.reg注册表文件也复制过来,就在当前bin/Reease路径下新建一个reg文件夹存放这两个reg文件。
2、将NETFX 4.0 Tools文件夹复制过来
这个文件夹路径
C:Program Files (x86)Microsoft SDKsWindowsv10.0Abin
一般来说,安装了visual studio 后,本地会有这个文件夹,里面包含一些我们需要用到的工具类。因为是生产并部署,其他人员电脑未必有这个文件夹,因此我们准备好这个文件夹,放入我们的部署包里面,方便其他人安装。
3、执行安装
你可以在命令行,一行行输入命令来实现。这里我们推荐用批处理命令。 把所有的命令都放在一个xxx.bat文件里。
这里我们的在当前Release目录下新增一个install.bat,里面内容如下:
@echo off
@set baseDir=%~dp0
regedit /s %baseDir%regZhjk.OfficialDocument.reg
C:WindowsMicrosoft.NETFrameworkv4.0.30319RegAsm %baseDir%Zhjk.dll /tlb:%baseDir%Zhjk.tlb
@SET GACUTIL="%baseDir%NETFX 4.8 Toolsgacutil.exe"
%GACUTIL% -i %baseDir%Zhjk.dll
%GACUTIL% -i %baseDir%Word.dll
%GACUTIL% -i %baseDir%Office.dll
pause
同样,再建一个卸载的bat批处理命令文件 uninstall.bat:
@echo off
@set baseDir=%~dp0
Echo 1.从缓存中移除程序集
@SETGACUTIL="%baseDir%NETFX 4.8 Toolsgacutil.exe"
rd /s /QC:WindowsMicrosoft.NETassemblyGAC_MSILZhjk
rd /s /QC:WindowsMicrosoft.NETassemblyGAC_MSILWord
rd /s /QC:WindowsMicrosoft.NETassemblyGAC_MSILOffice
Echo 2.注销类型
C:WindowsMicrosoft.NETFrameworkv4.0.30319RegAsm /u %baseDir%Zhjk.dll /tlb:%baseDir%Zhjk.tlb
Echo.
Echo 3.清除注册表
regedit /s %baseDir%regZhjk.OfficialDocument-delete.reg
pause
%~dp0是bat文件当前文件夹路径;RegAsm是程序集注册工具,需要用户先安装Microsoft .Net Framework 4.0;gacutil.exe是全局程序集缓存工具,用户电脑一般没有,所以需要我们将NETFX 4.8 Tools文件夹附带在安装包里
4、完善安装报的一些说明文档,最终将Release发给你想要的用户,让他们点击install.bat进行插件安装。(做好前提准备工作,比如.net framework版本必须升级到4.x)
最终输出包如下:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!