騎驢去看海 發表於 2025-3-31 20:06:00

C# 开发 Office 和 WPS COM 加载项

<h2 id="一实现接口idtextensibility2">一、实现接口&nbsp;IDTExtensibility2</h2>
<p>这是实现&nbsp;Office&nbsp;COM&nbsp;加载项最基本的接口</p>
<p>添加&nbsp;COM&nbsp;引用&nbsp;Microsoft&nbsp;Add-In&nbsp;Designer&nbsp;即可</p>
<blockquote>
<p>对应文件&nbsp;Extensibility.dll&nbsp;只包含&nbsp;IDTExtensibility2&nbsp;接口其中和用到的枚举&nbsp;ext_ConnectMode、ext_DisconnectMode,<br>
可以减少模块引用自行复制代码到自己项目中,注意&nbsp;IDTExtensibility2&nbsp;不可被混淆</p>
</blockquote>
<p>⚠&nbsp;注意:开发&nbsp;Office&nbsp;或&nbsp;WPS&nbsp;COM&nbsp;加载项添加&nbsp;COM&nbsp;引用时,需要安装对应的套件才能找到相关的&nbsp;COM&nbsp;组件,添加&nbsp;WPS&nbsp;COM&nbsp;引用时会受到两者安装的先后顺序和管理员权限影响,导致无法添加引用,若&nbsp;VS&nbsp;报错无法添加,需要卸载&nbsp;Office&nbsp;才能顺利添加。但下文会提到仅需引用其中一套&nbsp;COM&nbsp;组件即可同时兼容&nbsp;Office&nbsp;和&nbsp;WPS</p>
<pre><code class="language-csharp">#if BrandName1
namespace BrandName1 // 品牌1
#else
namespace BrandName2 // 品牌2
#endif
{
    // 不可被混淆
    // COM 组件类可见, 并且类型要设为公开 public
    // CLSID
    // // Office COM 加载项的 ProgID 须与类全名一致
    public class OfficeAddIn : IDTExtensibility2
    {
      public void OnConnection(
            object application, ext_ConnectMode connectMode,
            object addInInst, ref Array custom)
      {
            MessageBox.Show("OnConnection"); // 注册成功的加载项将会在对应应用启动时弹窗
      }

      // 其他 IDTExtensibility2 的接口方法...
    }
}
</code></pre>
<p>⚠&nbsp;注意:Office&nbsp;COM&nbsp;加载项须保证类的&nbsp;ProgID&nbsp;与类全名完全匹配,&nbsp;ProgID&nbsp;特性未设置时默认使用类的全名,故也无需设置;并且类不可被混淆,被其继承的接口也不可被混淆</p>
<blockquote>
<p>ProgID&nbsp;与产品和对应功能相关,文件关联也会用到,建议名称是&nbsp;<brandname>.,故示例中以&nbsp;BrandName1&nbsp;为名称空间,OfficeAddIn&nbsp;为类名,那么&nbsp;ProgID&nbsp;就是&nbsp;BrandName1.OfficeAddIn。为区分品牌,在品牌条件编译中使用不同的名称空间,而不是不同的类名,这样更符合规范,也更好编写注册的代码</assocname></brandname></p>
</blockquote>
<h2 id="二注册officecom加载项">二、注册&nbsp;Office&nbsp;COM&nbsp;加载项</h2>
<p>COM&nbsp;CLSID&nbsp;和&nbsp;Office&nbsp;产品的注册表都有&nbsp;HKCU、HKLM&nbsp;和&nbsp;64、32位的项,为了提高兼容性,可在这些注册表项下都添加上注册信息</p>
<h3 id="注册com组件">注册&nbsp;COM&nbsp;组件</h3>
<p>C#&nbsp;注册&nbsp;COM&nbsp;组件一般通过调用&nbsp;RegAsm.exe&nbsp;文件来注册,区分位数和运行时版本</p>
<blockquote>
<p>%windir%\Microsoft.NET\Framework_"ver"_\RegAsm.exe&nbsp;MyCOM.dll&nbsp;/codebase&nbsp;</p>
</blockquote>
<p>RegAsm.exe&nbsp;作用就是添加注册表项,避免系统缺失该文件,也为了添加日志输出,可自行写注册表实现</p>
<pre><code>{HKCU|HKLM}\Software\Classes
    ProgID
      ● "" = 'ProgID'
      CLSID
            ● "" = 'CLSID'
    CLSID\'CLSID'
      ● "" = 'ProgID'
      Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}
      InprocServer32
            ● "" = "mscoree.dll"
            ● "Assembly" = 'Assembly.FullName'
            ● "Class" = 'ProgID'
            ● "CodeBase" = 'Assembly.CodeBase'
            ● "RuntimeVersion" = 'Environment.Version'
            ● "ThreadingModel" = "Both"
      ProgId
            ● "" = 'ProgID'
</code></pre>
<p>⚠&nbsp;注意:RegAsm.exe&nbsp;注册方式会用到反射,如果不将引用到的程序集文件放到同目录下,并且系统未注册&nbsp;COM&nbsp;类继承的接口时,会出错导致注册失败。如果注册方式和&nbsp;RegAsm.exe&nbsp;一样会用到类型本身,为避免用户未安装&nbsp;COM&nbsp;组件相关的应用,需要打包引用到的程序集</p>
<p>⚠&nbsp;注意:同一个&nbsp;COM&nbsp;组件项目创建不同品牌的程序集并注册时,如果两个程序集文件名相同、签名相同、版本相同,则会导致两者程序集全名相同,导致系统无法区分。区分方式是三者至少有一个不同,最简单的方式就是条件编译设置不同版本号</p>
<p>⚠&nbsp;注意:为提高兼容性,目标平台选择&nbsp;AnyCPU&nbsp;即可兼容&nbsp;64/32&nbsp;位系统和软件。作为&nbsp;COM&nbsp;组件运行时,是作为被&nbsp;.NET&nbsp;虚拟机进程引用的程序集来运行的,只需将目标框架设为&nbsp;.NET&nbsp;Framework&nbsp;3.5,&nbsp;不需要&nbsp;app.config&nbsp;文件就可以兼容&nbsp;3.5&nbsp;和&nbsp;4.0,无需编译多个框架版本。支持&nbsp;.dll&nbsp;和&nbsp;.exe&nbsp;文件,只要是&nbsp;.NET&nbsp;程序集就可以,如果是可将自身注册为&nbsp;COM&nbsp;组件&nbsp;exe,那在注册时还是需要&nbsp;app.config&nbsp;的</p>
<h3 id="添加到office加载项列表">添加到&nbsp;Office&nbsp;加载项列表</h3>
<p>需要在加载项列表下新建加载项类&nbsp;ProgID&nbsp;同名子项,并添加三个必要的注册表键值</p>
<pre><code>{HKCU|HKLM}\Software\Microsoft\Office
    &lt;app&gt;
      AddIns
            'ProgID'
                ● FriendlyName = "加载项列表中显示的友好名称"
                ● Description = "加载项列表中显示的描述"
                ● LoadBehavior = 3 (启动时连接和加载)
</code></pre>
<blockquote>
<p>另外还可以添加&nbsp;CommandLineSafe&nbsp;=&nbsp;1,&nbsp;指示命令行操作安全,可能可以减少弹窗警告</p>
</blockquote>
<p>经过这两步注册示例插件后,启动对应的&nbsp;Office&nbsp;应用时,就会弹出消息框,验证注册成功了</p>
<h2 id="三实现接口iribbonextensibility">三、实现接口&nbsp;IRibbonExtensibility</h2>
<p>这个接口用于在&nbsp;Office&nbsp;应用的&nbsp;Ribbon&nbsp;中添加自定义&nbsp;UI</p>
<p>添加&nbsp;COM&nbsp;引用&nbsp;Microsoft&nbsp;Office&nbsp;<ver>&nbsp;Object&nbsp;Library&nbsp;即可,<ver>&nbsp;是&nbsp;Office&nbsp;版本号</ver></ver></p>
<blockquote>
<p>为提高兼容性,可以安装&nbsp;Office&nbsp;2007&nbsp;获取到&nbsp;12.0&nbsp;版本的&nbsp;COM,并将对应的文件&nbsp;Office.dll&nbsp;复制到项目目录中,并修改引用为相对文件,避免在其他未安装&nbsp;Office&nbsp;2007&nbsp;的电脑上无法生成。注意此接口也要被加载项类继承,故不可被混淆</p>
</blockquote>
<p>此接口只有一个&nbsp;<code>GetCutsomUI</code>&nbsp;的方法,需要返回&nbsp;XML&nbsp;格式的字符串</p>
<p>为了代码可读性,建议使用编写和加载&nbsp;XML&nbsp;资源文件的方式</p>
<p>并且在&nbsp;VS&nbsp;中编写 XML&nbsp;添加名称空间后在编写元素属性时将会有候选词列表,十分方便</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"&gt;
&lt;ribbon&gt;
    &lt;tabs&gt;
      &lt;tab id="TestTab" getLabel="GetLabel"&gt;
      &lt;group id="TestGroup" getLabel="GetLabel"&gt;
          &lt;button id="TestButton" size="large"
            onAction="OnButtonPressed"
            getLabel="GetLabel"
            getImage="GetImage"/&gt;
      &lt;/group&gt;
      &lt;/tab&gt;
    &lt;/tabs&gt;
&lt;/ribbon&gt;
&lt;/customUI&gt;
</code></pre>
<pre><code class="language-csharp">public class OfficeAddIn : IDTExtensibility2, IRibbonExtensibility
{
    public string GetLabel(IRibbonControl control)
    {
      switch(control.ID)
      {
            case "TestTab": return "Test Tab";
            case "TestGroup": return "Test Group";
            case "TestButton": return "Test Button";
      }
      return null;
    }

    public {Bitmap|IPictureDisp} GetImage(IRibbonControl control)
    {
      // 返回控件图像,支持 Bitmap 或 IPictureDisp 类型返回值
    }

    public void OnButtonPressed(IRibbonControl control)
    {
      MessageBox.Show("Test Button Clicked!");
    }
}
</code></pre>
<h3 id="customui注意事项">CustomUI&nbsp;注意事项</h3>
<p>建议携带&nbsp;XML&nbsp;声明部分指定<code>utf-8</code>编码,否则如果有中文会乱码</p>
<p><code>customUI</code>元素中的名称空间,年月可以用<code>2009/07</code>或<code>2006/01</code>,但&nbsp;Office&nbsp;2007&nbsp;不支持解析前者</p>
<p>控件的文本、图像、悬浮提示语等,都可使用对应的属性<code>label</code>、<code>image</code>在&nbsp;XML&nbsp;中直接设置。也可以使用对应的回调方法<code>getLabel</code>、<code>getImage</code>。使用回调方法的方式需要在加载项类中声明同名公开方法,比如&nbsp;XML&nbsp;中编写<code>getLabel="GetLabel"</code>,C#&nbsp;中就须编写对应的<code>public&nbsp;string&nbsp;GetLabel&nbsp;(IRibbonControl&nbsp;control)</code>方法,类似于&nbsp;WPF&nbsp;XAML&nbsp;的事件绑定,支持多个控件使用同一个方法并根据控件的&nbsp;id&nbsp;返回合适的值</p>
<p>第&nbsp;3&nbsp;条中属性和回调方法互斥,只允许使用其中一个。另外图像还可使用内置图像属性<code>imageMso</code>&nbsp;,与<code>image</code>和<code>getImage</code>也是互斥的,比如&nbsp;<code>imageMso="FileSaveAs"</code>使用内置的另存为图像</p>
<p>如果使用了&nbsp;<code>dynamicMenu</code>&nbsp;控件,其&nbsp;<code>getContent</code>&nbsp;方法也需要返回&nbsp;XML&nbsp;格式字符串,但与第&nbsp;1&nbsp;条不同,不能有&nbsp;XML&nbsp;声明部分,否则解析失败</p>
<p>大小写敏感,大小写错误会导致解析失败</p>
<h3 id="使用透明背景图像">使用透明背景图像</h3>
<p>CustomUI&nbsp;中控件的<code>getImage</code>方法支持直接返回<code>Bitmap</code>类型,Office&nbsp;支持透明背景的<code>Bitmap</code>,但&nbsp;WPS&nbsp;不支持,会用浅灰色的背景填充。这里可以转换并返回<code>IPictureDisp</code>类型</p>
<blockquote>
<p>另外值得一提的是,Office&nbsp;在切换深色主题后,黑灰单色图像还会自动转换为白色图像,WPS&nbsp;没有这个机制</p>
</blockquote>
<blockquote>
<p>IPictureDisp&nbsp;在&nbsp;COM&nbsp;组件&nbsp;OLE&nbsp;Automation&nbsp;中定义,一般在添加&nbsp;Microsoft&nbsp;Office&nbsp;<ver>&nbsp;Object&nbsp;Library&nbsp;引用时会自动添加上,对应文件是&nbsp;stdole.dll,我们只需要用到&nbsp;IPictureDisp&nbsp;接口,同样可以减少模块引用自行复制代码到自己项目中</ver></p>
</blockquote>
<pre><code class="language-csharp">
static extern IPictureDisp OleCreatePictureIndirect(
    ref PictDesc pictdesc,
    Guid iid,
    bool fOwn);

struct PictDesc
{
    public int cbSizeofstruct;
    public int picType;
    public IntPtr hbitmap;
    public IntPtr hpal;
    public int unused;
}

public static IPictureDisp CreatePictureIndirect(Bitmap bitmap)
{
    var picture = new PictDesc
    {
      cbSizeofstruct = Marshal.SizeOf(typeof(PictDesc)),
      picType = 1,
      hbitmap = bitmap.GetHbitmap(Color.Black), // 创建纯透明底色位图
      hpal = IntPtr.Zero,
      unused = 0,
    };
    return OleCreatePictureIndirect(ref picture, typeof(IPictureDisp).GUID, true);
}
</code></pre>
<blockquote>
<p><code>Bitmap.GetHbitmap</code>有无参和传参<code>Color</code>两个重载,无参重载在内部传参<code>Color.LightGray</code>调用另一重载,这应该和直接返回<code>Bitmap</code>在&nbsp;WPS&nbsp;中会有浅灰色填充相关。<br>
需要注意的是,<code>GetHbitmap</code>方法内部不会使用到颜色的&nbsp;Alpha&nbsp;值,&nbsp;创建纯透明背景图像句柄,应该使用<code>Color.Black</code>&nbsp;<code>255,0,0,0</code>而不是<code>Color.Transparent``0,255,255,255</code></p>
</blockquote>
<h3 id="customui刷新控件">CustomUI&nbsp;刷新控件</h3>
<ol>
<li>利用<code>customUI</code>元素的<code>onload</code>回调方法,在&nbsp;C#&nbsp;中记录<code>IRibbonUI</code>对象,可调用其<code>Invalidate</code>方法刷新整个&nbsp;UI,或者调用<code>InvalidateControl(string&nbsp;id)</code>根据&nbsp;id&nbsp;刷新指定控件</li>
</ol>
<pre><code class="language-csharp">public class OfficeAddIn : IDTExtensibility2, IRibbonExtensibility
{
    IRibbonUI ribbon;

    public void OnCustomUILoad(IRibbonUI ribbon)
    {
      this.ribbon = ribbon;
    }

    internal void Invalidate()
    {
      ribbon?.Invalidate();
    }

    internal void InvalidateControl(string id)
    {
      ribbon?.InvalidateControl(id);
    }
}
</code></pre>
<ol start="2">
<li><code>dynamicMenu</code>控件<code>invalidateContentOnDrop="true"</code>可在每次展开时重新触发<code>getContent</code>刷新内容</li>
</ol>
<h2 id="四office互操作能力">四、Office&nbsp;互操作能力</h2>
<p>需要添加引用对应&nbsp;Office&nbsp;应用的互操作库,在&nbsp;VS&nbsp;中可以很方便的跳转&nbsp;MSDN&nbsp;文档</p>
<p>添加&nbsp;COM&nbsp;引用&nbsp;Microsoft&nbsp;&nbsp;<ver>&nbsp;Object&nbsp;Library&nbsp;即可</ver></app></p>
<p>下文演示&nbsp;Office&nbsp;导出&nbsp;PDF&nbsp;能力,仅作演示,另外需要释放&nbsp;COM&nbsp;对象</p>
<p>ExportAsFixedFormat&nbsp;方法有很多可选参数,支持设置打印页数、包含文档信息、生成书签等</p>
<p>⚠&nbsp;注意:Office&nbsp;2007(只有&nbsp;32&nbsp;位版本)导出&nbsp;PDF/XPS&nbsp;会提示未安装此功能时,需要用到&nbsp;Office&nbsp;2010&nbsp;才有的&nbsp;EXP_PDF.dll&nbsp;和&nbsp;EXP_XPS.dll&nbsp;文件,复制到&nbsp;Office&nbsp;2007&nbsp;的共享目录即可</p>
<p><code>%CommonProgramFiles[(x86)]%\Microsoft&nbsp;Shared\OFFICE12</code></p>
<h3 id="word导出pdf">Word&nbsp;导出&nbsp;PDF</h3>
<pre><code class="language-csharp">using Microsoft.Office.Interop.Word;

public class OfficeAddIn : IDTExtensibility2, IRibbonExtensibility
{
    Application app;

    public void OnConnection(
      object application, ext_ConnectMode connectMode,
      object addInInst, ref Array custom)
    {
      if (application is Application)
            app = (Application)Application;
    }

    public void OnButtonPressed(IRibbonControl control)
    {
      app?.ActiveDocument?.ExportAsFixedFormat(fileName, WdExportFormat.wdExportFormatPDF);
    }
}
</code></pre>
<h3 id="excel导出pdf">Excel&nbsp;导出&nbsp;PDF</h3>
<pre><code class="language-csharp">using Microsoft.Office.Interop.Excel;
// 工作簿
app?.ActiveWorkbook?.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, fileName);

// 工作表
(app?.ActiveSheet as Worksheet)?.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, fileName);

// 图表, WPS 不支持
app.ActiveChart?.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, fileName);

// 框选区域
var range = app.Selection as Range;
var sheet = range.Worksheet;
var area = sheet.PageSetup.PrintArea;
sheet.PageSetup.PrintArea = range.Address; // 设置打印区域为选定区域
sheet.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, fileName);
sheet.PageSetup.PrintArea = area; // 还原打印区域
</code></pre>
<h3 id="powerpoint导出pdf">PowerPoint&nbsp;导出&nbsp;PDF</h3>
<pre><code class="language-csharp">using Microsoft.Office.Interop.PowerPoint;

app?.ActivePresentation?.ExportAsFixedFormat(fileName, PpFixedFormatType.ppFixedFormatTypePDF);
</code></pre>
<h3 id="publisher导出pdf">Publisher&nbsp;导出&nbsp;PDF</h3>
<pre><code class="language-csharp">using Microsoft.Office.Interop.Publisher;

app?.ActiveDocument?.ExportAsFixedFormat(PbFixedFormatType.pbFixedFormatTypePDF, fileName);
</code></pre>
<h3 id="outlook导出邮件为pdf">Outlook&nbsp;导出邮件为&nbsp;PDF</h3>
<pre><code class="language-csharp">using Microsoft.Office.Interop.Outlook;
using Microsoft.Office.Interop.Word;

var mailItem = outlook?.ActiveExplorer()?.Selection?.OfType&lt;MailItem&gt;()?.FirstOrDefault();
var inspector = mailItem?.GetInspector;
var document = inspector?.WordEditor as Document;
document?.ExportAsFixedFormat(fileName, WdExportFormat.wdExportFormatPDF);
// GetInspector 会打开一个隐藏窗口,比较吃内存,需要及时关闭
inspector?.Close(OlInspectorClose.olPromptForSave);
</code></pre>
<h2 id="五实现wpscom加载项">五、实现&nbsp;WPS&nbsp;COM&nbsp;加载项</h2>
<p>须在注册&nbsp;Office&nbsp;COM&nbsp;加载项基础上(包括添加到&nbsp;Office&nbsp;加载项列表),另外添加到&nbsp;WPS&nbsp;加载项列表</p>
<h3 id="添加到wps加载项列表">添加到&nbsp;WPS&nbsp;加载项列表</h3>
<p>Word&nbsp;对应&nbsp;WPS,Excel&nbsp;对应&nbsp;ET,PowerPoint&nbsp;对应&nbsp;WPP,不区分&nbsp;64/32位</p>
<pre><code>HKCU\Software\kingsoft\office
    {WPS|ET|WPP}
      AddinsWL
            'ProgID' = ""
</code></pre>
<h3 id="office与wpscom组件对应表">Office&nbsp;与&nbsp;WPS&nbsp;COM&nbsp;组件对应表</h3>
<p>⚠&nbsp;注意:WPS&nbsp;和&nbsp;Office&nbsp;官方为了互相兼容,Office、Word、Excel、PowerPoint&nbsp;相关的&nbsp;COM&nbsp;接口使用相同的&nbsp;CLSID。如果插件需要兼容两者,对应的互操作库文件只需要一组,因程序集内名称空间不同,且用户基本只会安装其中一套,须复制互操作库文件到运行目录,否则无法同时兼容</p>
<table>
<thead>
<tr>
<th>Office</th>
<th>WPS</th>
</tr>
</thead>
<tbody>
<tr>
<td>Microsoft&nbsp;Add-In&nbsp;Designer<br>Extensibility.dll</td>
<td>Kingsoft&nbsp;Add-In&nbsp;Designer<br>Interop.AddInDesignerObjects.dll</td>
</tr>
<tr>
<td>Microsoft&nbsp;Office&nbsp;<ver>&nbsp;Object&nbsp;Library<br>Office.dll</ver></td>
<td>Upgrage&nbsp;WPS&nbsp;Office&nbsp;<ver>&nbsp;Object&nbsp;Library<br>Interop.Office.dll</ver></td>
</tr>
<tr>
<td>Microsoft&nbsp;Word&nbsp;<ver>&nbsp;Object&nbsp;Library<br>Microsoft.Office.Interop.Word.dll</ver></td>
<td>Upgrade&nbsp;Kingsoft&nbsp;WPS&nbsp;<ver>&nbsp;Object&nbsp;Library<br>Interop.Word.dll</ver></td>
</tr>
<tr>
<td>Microsoft&nbsp;Excel&nbsp;<ver>&nbsp;Object&nbsp;Library<br>Microsoft.Office.Interop.Excel.dll</ver></td>
<td>Upgrage&nbsp;WPS&nbsp;Spreadsheets&nbsp;<ver>&nbsp;Object&nbsp;Library<br>Interop.Excel.dll</ver></td>
</tr>
<tr>
<td>Microsoft&nbsp;PowerPoint&nbsp;<ver>&nbsp;Object&nbsp;Library<br>Microsoft.Office.Interop.PowerPoint.dll</ver></td>
<td>Upgrage&nbsp;WPS&nbsp;Presentation&nbsp;<ver>&nbsp;Object&nbsp;Library<br>Interop.PowerPoint.dll</ver></td>
</tr>
</tbody>
</table>
<h2 id="六卸载清理注册表">六、卸载清理注册表</h2>
<p>除了清理上文中添加的&nbsp;COM&nbsp;组件和加载项的注册表,还可以清理以下相关的注册表:</p>
<ol>
<li>
<p><code>HKCU\Software\Microsoft\Office\&lt;app&gt;\AddinsData</code>插件数据</p>
</li>
<li>
<p><code>HKCU\Software\Microsoft\Office\&lt;ver&gt;\Common\CustomUIValidationCache</code>CustomUI&nbsp;校验缓存</p>
</li>
<li>
<p><code>HKCU\Software\Microsoft\Office\&lt;ver&gt;\&lt;app&gt;\Addins</code>版本插件列表</p>
</li>
<li>
<p><code>HKCU\Software\Microsoft\Office\&lt;ver&gt;\&lt;app&gt;\AddInLoadTimes</code>版本加载次数</p>
</li>
<li>
<p><code>HKCU\Software\Microsoft\Office\&lt;ver&gt;\&lt;app&gt;\Resiliency\NotificationReminderAddinData</code>Office&nbsp;禁用通知</p>
</li>
</ol>
<h2 id="七其他问题">七、其他问题</h2>
<h3 id="未加载加载com加载项时出现运行错误">未加载,加载&nbsp;COM&nbsp;加载项时出现运行错误</h3>
<p>这是一个比较令人头疼的问题,可能原因有很多,但&nbsp;Office&nbsp;没有报错日志,导致很难排查问题</p>
<p>微软官方博客 给出了一些解答,个人也复现了一些情况:</p>
<ul>
<li>
<p>部署问题:COM&nbsp;组件注册表内容缺失项或键值,需要注意&nbsp;COM&nbsp;组件与&nbsp;Office&nbsp;加载项注册表的位数</p>
</li>
<li>
<p>运行问题:在&nbsp;Outlook&nbsp;中比较明显,本身就启动缓慢卡顿,切忌在启动时调用&nbsp;Sleep&nbsp;函数,轻则&nbsp;Office&nbsp;直接提示建议禁用插件,重则直接出现未加载的问题</p>
</li>
</ul>
<h3 id="outlook退出前操作">Outlook&nbsp;退出前操作</h3>
<p>Outlook&nbsp;16.0(其他版本未测试)退出时不会触发&nbsp;<code>OnBeginShutdown</code>和<code>OnDisconnection</code>,原因未知,应该是&nbsp;Outlook&nbsp;自己的&nbsp;Bug,故&nbsp;Outlook&nbsp;插件不要在这两个方法中进行退出前操作</p>
<p>经过测试,程序退出时会触发<code>System.Windows.Forms.Application.ThreadExit</code>,但是不会触发(来不及?)<code>AppDomain.CurrentDomain.ProcessExit</code>,可以利用前者来进行退出前操作,比如保存配置和释放资源</p>
<h3 id="office应用关闭后进程不结束">Office&nbsp;应用关闭后进程不结束</h3>
<p>出现此问题一般是&nbsp;COM&nbsp;对象资源未释放干净,但是频繁使用&nbsp;Office&nbsp;互操作很难保证所有&nbsp;COM&nbsp;对象都及时正确释放。为了让进程正确退出,不可使用<code>Process.Kill</code>等强制方法手动结束进程,一是强制结束进程可能会导致下次打开文档时会提示文档保存异常,二是插件可在程序运行中被手动卸载,可以使用卸载当前应用程序域的方式友好解决问题</p>
<pre><code class="language-csharp">public void OnDisconnection(ext_DisconnectMode removeMode, ref Array custom)
{
    try
    {
      AppDomain.Unload(AppDomain.CurrentDomain);
    }
    catch (CannotUnloadAppDomainException)
    {
      // ignored
    }
}
</code></pre>
<h2 id="相关资料">相关资料</h2>
<p>如何使用 Visual C# .NET 生成 Office COM 加载项 - Office</p>
<p>: CustomUI |Microsoft 学习</p>
<p>COM Add-In 加载失败疑难解答 |Microsoft 学习</p><br><br>
来源:https://www.cnblogs.com/BluePointLilac/p/18802868
頁: [1]
查看完整版本: C# 开发 Office 和 WPS COM 加载项