使用VC++/ATL创建一个Office2K AddIn Com 组件

本文详细介绍了如何使用纯ATLCom对象编写Outlook2000/2K+ COM Add-In程序,包括添加界面元素、属性页及事件处理等关键步骤。
原文: http://www.xaradio.com/errorpages/GeneralError.htm?aspxerrorpath=/ShowFAQ.aspx


概况:
通过这篇文章,我们将会了解怎样使用纯ATL Com 对象编写Outlook2000/2K+ COM addin程序。我们将从写一个最基本的Com AddIn程序开始。接下来我将向你们展示怎样将标准的界面元素比如工具栏或是菜单项加入到outlook中去,并响应他们的事件。紧接着,我们要为Outlook's Tools->Options加入我们自己编写的属性表。接着我们将看一些相关的注册键和ATL向导的一些非常有用的特征并且学习有效地使用他们。
虽然我们写的是一个Outlook2000 COM addin的程序。但是Office2000的应用程序,比如Word,Access等等,他们的Com AddIn的写法是非常相似的。除了注册键和接口,其余的部分基本上是一样的。
我假设你是一个VC++ Com的开发人员,并且也有一些基于ATL的组件开发和OLE/自动化方面的经验,尽管这也不是必须的。创建和测试这个AddIn程序,你必须安装Office2000,至少有outlook2000。程序代码使用VC++ 6.0 sp3+/ATL3.0创建,使用的操作系统是:安装了Office2000的Windows2000。

开始:
Office AddIn 是一个可以动态扩充和增强的Com 自动化组件,可以控制任何的Office应用程序。微软的Office2000和以后的版本都支持创建Add_Ins的一个新的、统一的应用设计架构。AddIn通常都被置于一个ActiveX动态库中(进程内服务器),并且能被用户动态的从主程序中引导和卸载。
Office AddIn 必须实现 _IDTExtensibility2 接口。IDTExtensibility2接口定义于MSADDin Designer typelibrary (MSADDNDR.dll/MSADDNDR.tlb)文件中。一般在/Program Files/Common Files/Designer目录下。
接口象这样定义:
enum

{
    ext_cm_AfterStartup = 0,
    ext_cm_Startup = 1,
    ext_cm_External = 2,
    ext_cm_CommandLine = 3
} ext_ConnectMode;

enum

{
    ext_dm_HostShutdown = 0,
    ext_dm_UserClosed = 1
} ext_DisconnectMode;

...
...
...

interface _IDTExtensibility2 : IDispatch

{
[id(0x00000001)]
HRESULT OnConnection(
[in] IDispatch* Application,
[in] ext_ConnectMode ConnectMode,
[in] IDispatch* AddInInst,
[in] SAFEARRAY(VARIANT)* custom);
[id(0x00000002)]

HRESULT OnDisconnection(
[in] ext_DisconnectMode RemoveMode,
[in] SAFEARRAY(VARIANT)* custom);
[id(0x00000003)]
HRESULT OnAddInsUpdate([in] SAFEARRAY(VARIANT)* custom);
[id(0x00000004)]
HRESULT OnStartupComplete([in] SAFEARRAY(VARIANT)* custom);
[id(0x00000005)]
HRESULT OnBeginShutdown([in] SAFEARRAY(VARIANT)* custom);
};


所有的Com AddIn继承于IDTExtensibility2,而且必须实现他的五个方法。
当AddIn被引导和卸载的时候,OnConnection 和 OnDisconnection, 就像他们的名字显示的一样。AddIn程序可以被引导,也可以在应用程序使用过程中被用户启动或者通过自动化和enumerator ext_Connect 指示连接到那些模块。当一组Com AddIn组件被改变,那么OnAddinsUpdate被调用。OnStartupComplete 只有在应用程序使用过程中启动Com AddIn组件时才被调用,如果AddIn在主应用程序被关掉的时候断开与主应用程序的连接,那么OnBeginShutdown 被调用。

注册AddIn组件:
使用主应用程序注册AddIn组件,我们需要在注册表目录:
HKEY_CURRENT_USER\Software\Microsoft\Office\<TheOfficeApp>\Addins\<ProgID> 下创建两个子键,这里ProgID指的是Addin Com对象的唯一标识符。别的入口通过AddIn提供的关于他自己的信息和制定的引导选项给主应用程的是:
FriendlyName – 字符串 – 主应用程序显示的这个AddIn程序的名字。
Description – 字符串 – 对AddIn的描述.
LoadBehavior - DWORD 值. –一个决定AddIn怎样被主应用程序引导的值的组合。 设置成 0x03 表示主应用程序启动时引导,设置成0x08表示由用户来激活。
CommandLineSafe - DWORD 值. 0x01(TRUE) 或者 0x00(FALSE).
对于所有值和可选项的完整描述,请参考MSDN。

创建一个小的Com AddIn:
现在我们了解了足够的知识,应该朝前一步编写一个小的Outlook2K COM addin。创建一个新的ATL COM Appwizard 工程,命名为OutlookAddin。记住如果你把他命名成别的,他可能会不能运行(开个玩笑)。
在向导的第一个对话框中接收默认的服务器类型Dynamic Link Library(DLL),检查Allow merging of proxy-stub code,选择这个可选项,点击完成。接着点击OK,产生工程文件。
下一步,点击Insert->New ATL Object菜单项,通过从Category中选择Objects从Objects列表中选择Simple Object插入一个ATL simple object到工程中。点击Next,输入”AddIn”作为ShortName,在属性表里选上Support ISupportErrorInfo。接受剩下的默认选项,然后点击OK。
到现在为止,向导已经给我们了一个置于动态链接库中的自动化兼容的、DispInterface-savvy的进程内的Com对象。默认的情况下,一个加到Com对象上的指定注册值的注册脚本文件被提交给我们。Build这个工程,看看一切是否运行良好。
如果你想我一样雄心勃勃,起码在继续往下进行前还应该编译你工程中的.idl文件。现在就去做吧。
接下来我们为AddIn写一些特定的代码去实现IDTExtensibility2 接口。在类视图里,我们在CAddIn类上右键点击,选择Implement Interface,这将带出ATL Implement Interface 向导。点击Add Typelib,在Browse Typelibraries对话框里向下滚动,选上Microsoft Add-in Designer(1.0),点击OK。在AddinDesignerObjects列表中选择_IDTExtensibility2接口点击OK。
向导为IDTExtensibility2接口的五个方法中每一个生成默认的实现,将他们加到CAddIn类中,并且更新 COM_INTERFACE_MAP()宏。当然在加有些有用的代码之前每个方法都只会返回E_NOTIMPL。现在,为ComAddIn进行必要的注册,我们的Com AddIn已经就绪了。
使用主应用程序注册我们的Addin组件。如果是outlook2000,打开工程的AddIn.rgs注册脚本文件。把下面的代码加到文件的结尾。
HKCU
{
Software
{
Microsoft
{
Office
{
Outlook
{
Addins
{
'OutlookAddin.Addin'
{
val FriendlyName = s 'ADOutlook2K Addin'
val Description = s 'ATLCOM Outlook Addin'
val LoadBehavior = d '00000008'
val CommandLineSafe = d '00000000'
}
}
}
}
}
}
}
既然我们希望在程序启动的时候AddIn被引导,那么LoadBehavior设置为3。现在Build这个工程。如果一切顺利,那么将会创建成功并且注册了这个AddIn。为了测试这个AddIn,我们要运行这个工程并输入完整的outlook.exe的完整的路径(\Program Files\Microsoft Office\Office\Outlook.exe),或者在注册了这个DLL之后从VC++IDE环境外运行outlook。如果你的AddIn被成功的注册了,那么在outlook里,点击Tools->Options,在Other页点击Advanced Options->COM Addins,我们的AddIn应该已经出现在可获得的AddIns的列表中。字符串是我们在脚本中为'FriendlyName'指定的值。
AddIn可以被编写来执行各种不同的任务。典型的,包括为outlook添加一些界面元素,比如工具条和菜单项,而且用户可以控制AddIn。通过点击这个工具条按钮和菜单项,用户可以实现AddIn的功能。接下来我们将制作这样一个工具条和附加的菜单项。

命令与征服:
在Office应用程序中,菜单和工具条被组合在一个名叫“CommandBars “的完全可编程的集合中。CommandBars通常是可共享可编程的对象,并且作为所有的office应用程序的一部分被暴露。CommandBars 代表一个同一的机制,通过他可以将单个的工具条和菜单项加到相应的应用程序里。每一个CommandBars由几个独立的CommandBar对象组成。每一个CommandBar又由CommandBarControl对象集合组成,这个集合被叫做CommandBarControls。
CommandBarControls代表了一个复杂的对象和组成它的子对象层次。一个CommandBarControl能被包含在一个 CommandBar中,并且通过控件的CommandBar属性访问。最后每一个在控件的CommandBarControls集合中的 CommandBarControl即可能是CommandBarComboBox、CommandBarButton(工具条按钮)也可能是 CommandBarPopup(弹出式菜单)。我很希望我能画出一个代表这个对象层次的图例,但是我很不擅长这个(我很诚实!)。我保证在MSDN中一定有关于MS Office CommandBars描述的图例。
在我们的AddIn里,我想加入以下的界面元素:
? 在一个新的工具条里加入两个位图按钮。
? 在“Tool“菜单里添加一个新的带位图的弹出式菜单项。
首先,我们应该将office和outlook的类型库导入到我们的工程中。我们打开stdAfx.h,然后添加以下语句:
#import "C:\Program Files\Microsoft Office\Office\mso9.dll" \
rename_namespace("Office") named_guids
using namespace Office;

#import "C:\Program Files\Microsoft Office\Office\MSOUTL9.olb"
rename_namespace("Outlook"), raw_interfaces_only, named_guids
using namespace Outlook;
注意:你应该改变这些路径,是他们匹配你安装的office的路径。
好了,现在让我们来看看代码。首先式ToolBand和ToolBar Button。
在outlook模块里,Application 对象位于代表整个应用程序的对象层次的最顶层。通过他的ActiveExplorer 方法我们可以得到代表当前窗口的Explorer对象。下来我们使用GetCommandBars方法得到CommandBars对象(他是 outlook工具条和菜单项的集合)。我们使用CommandBars集合的Add方法加上相应的参数就可以添加一个新的工具条。如果想向工具条中加入按钮只需要得到工具条的CommandBarControls集合,接着调用他的Add方法。最后我们为那些对应于按钮的 CommandBarButton对象(我们可以用它来设置按钮的风格和别的属性,比如标题、提示和文本等等)。
代码片断如下:
STDMETHODIMP CAddin::OnConnection(IDispatch * Application,
ext_ConnectMode ConnectMode,
IDispatch * AddInInst, SAFEARRAY * * custom)
{

CComPtr < Office::_CommandBars> spCmdBars;
CComPtr < Office::CommandBar> spCmdBar;

// QI() for _Application
CComQIPtr <
Outlook::_Application> spApp(Application);
ATLASSERT(spApp);
// get the CommandBars interface that represents Outlook's
//toolbars & menu items

CComPtr<Outlook::_Explorer> spExplorer;
spApp->ActiveExplorer(&spExplorer);

HRESULT hr = spExplorer->get_CommandBars(&spCmdBars);
if(FAILED(hr))
return hr;
ATLASSERT(spCmdBars);

// now we add a new toolband to Outlook
// to which we'll add 2 buttons
CComVariant vName("OutlookAddin");
CComPtr <Office::CommandBar> spNewCmdBar;

// position it below all toolbands
//MsoBarPosition::msoBarTop = 1
CComVariant vPos(1);

CComVariant vTemp(VARIANT_TRUE); // menu is temporary
CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
//Add a new toolband through Add method
// vMenuTemp holds an unspecified parameter
//spNewCmdBar points to the newly created toolband
spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty, vTemp);

//now get the toolband's CommandBarControls
CComPtr < Office::CommandBarControls> spBarControls;
spBarControls = spNewCmdBar->GetControls();
ATLASSERT(spBarControls);

//MsoControlType::msoControlButton = 1
CComVariant vToolBarType(1);
//show the toolbar?
CComVariant vShow(VARIANT_TRUE);

CComPtr < Office::CommandBarControl> spNewBar;
CComPtr < Office::CommandBarControl> spNewBar2;

// add first button
spNewBar = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
ATLASSERT(spNewBar);
// add 2nd button
spNewBar2 = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
ATLASSERT(spNewBar2);

_bstr_t bstrNewCaption(OLESTR("Item1"));
_bstr_t bstrTipText(OLESTR("Tooltip for Item1"));

// get CommandBarButton interface for each toolbar button
// so we can specify button styles and stuff
// each button displays a bitmap and caption next to it
CComQIPtr < Office::_CommandBarButton> spCmdButton(spNewBar);
CComQIPtr < Office::_CommandBarButton> spCmdButton2(spNewBar2);

ATLASSERT(spCmdButton);
ATLASSERT(spCmdButton2);

// to set a bitmap to a button, load a 32x32 bitmap
// and copy it to clipboard. Call CommandBarButton's PasteFace()
// to copy the bitmap to the button face. to use
// Outlook's set of predefined bitmap, set button's FaceId to //the
// button whose bitmap you want to use
HBITMAP hBmp =(HBITMAP)::LoadImage(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDB_BITMAP1),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);

// put bitmap into Clipboard
::OpenClipboard(NULL);
::EmptyClipboard();
::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);
::CloseClipboard();
::DeleteObject(hBmp);
// set style before setting bitmap
spCmdButton->PutStyle(Office::msoButtonIconAndCaption);

HRESULT hr = spCmdButton->PasteFace();
if (FAILED(hr))
return hr;

spCmdButton->PutVisible(VARIANT_TRUE);
spCmdButton->PutCaption(OLESTR("Item1"));
spCmdButton->PutEnabled(VARIANT_TRUE);
spCmdButton->PutTooltipText(OLESTR("Tooltip for Item1"));
spCmdButton->PutTag(OLESTR("Tag for Item1"));

//show the toolband
spNewCmdBar->PutVisible(VARIANT_TRUE);

spCmdButton2->PutStyle(Office::msoButtonIconAndCaption);

//specify predefined bitmap
spCmdButton2->PutFaceId(1758);

spCmdButton2->PutVisible(VARIANT_TRUE);
spCmdButton2->PutCaption(OLESTR("Item2"));
spCmdButton2->PutEnabled(VARIANT_TRUE);
spCmdButton2->PutTooltipText(OLESTR("Tooltip for Item2"));
spCmdButton2->PutTag(OLESTR("Tag for Item2"));
spCmdButton2->PutVisible(VARIANT_TRUE);

//..........
//..........
//code to add new menubar to be added here
//read on
//..........
我们用相似的方法来给outlook的Tools菜单添加菜单项,我们照以下方法做。CommandBars的ActiveMenuBar属性返回一个表示在Application容器中活动的菜单。我们通过GetControls方法找到活动的菜单控件集合。我们想要加入一个弹出式的菜单项到 outlook的Tools菜单(第6个菜单项),我们从Activemenubars控件集合中可以找到第6个菜单项,直接调用Add方法创建一个新的菜单项并且将他连接到Tools菜单。这里没有什么新东西。
相应的代码片断如下所示:
//......
//code to add toolbar here
//......

_bstr_t bstrNewMenuText(OLESTR("New Menu Item"));
CComPtr < Office::CommandBarControls> spCmdCtrls;
CComPtr < Office::CommandBarControls> spCmdBarCtrls;
CComPtr < Office::CommandBarPopup> spCmdPopup;
CComPtr < Office::CommandBarControl> spCmdCtrl;

// get CommandBar that is Outlook's main menu
hr = spCmdBars->get_ActiveMenuBar(&spCmdBar);
if (FAILED(hr))
return hr;
// get menu as CommandBarControls
spCmdCtrls = spCmdBar->GetControls();
ATLASSERT(spCmdCtrls);

// we want to add a menu entry to Outlook's 6th(Tools) menu //item
CComVariant vItem(5);
spCmdCtrl= spCmdCtrls->GetItem(vItem);
ATLASSERT(spCmdCtrl);

IDispatchPtr spDisp;
spDisp = spCmdCtrl->GetControl();

// a CommandBarPopup interface is the actual menu item
CComQIPtr < Office::CommandBarPopup> ppCmdPopup(spDisp);
ATLASSERT(ppCmdPopup);

spCmdBarCtrls = ppCmdPopup->GetControls();
ATLASSERT(spCmdBarCtrls);

CComVariant vMenuType(1); // type of control - menu
CComVariant vMenuPos(6);
CComVariant vMenuEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
CComVariant vMenuShow(VARIANT_TRUE); // menu should be visible
CComVariant vMenuTemp(VARIANT_TRUE); // menu is temporary


CComPtr < Office::CommandBarControl> spNewMenu;
// now create the actual menu item and add it
spNewMenu = spCmdBarCtrls->Add(vMenuType, vMenuEmpty, vMenuEmpty,
vMenuEmpty, vMenuTemp);
ATLASSERT(spNewMenu);

spNewMenu->PutCaption(bstrNewMenuText);
spNewMenu->PutEnabled(VARIANT_TRUE);
spNewMenu->PutVisible(VARIANT_TRUE);

//we'd like our new menu item to look cool and display
// an icon. Get menu item as a CommandBarButton
CComQIPtr < Office::_CommandBarButton> spCmdMenuButton(spNewMenu);
ATLASSERT(spCmdMenuButton);
spCmdMenuButton->PutStyle(Office::msoButtonIconAndCaption);

// we want to use the same toolbar bitmap for menuitem too.
// we grab the CommandBarButton interface so we can add
// a bitmap to it through PasteFace().
spCmdMenuButton->PasteFace();
// show the menu
spNewMenu->PutVisible(VARIANT_TRUE);

return S_OK;
}
点击F5,如果一切都没问题,那么工程将成功建立,并且你将第一次看见你的AddIn程序的运行。现在我们运行outlook来测试我们的AddIn。在'Executable for Debug'对话框,设置outlook可执行程序的当前路径,现在我们准备测试。在outlook中点击Tools->Option,点击Other页面,点击Advanced Options。在Advanced Option对话框中,点击Com AddIns 按钮。接着从可获得的AddIns列表中选择我们的AddIn并点击OK。当我们的AddIn被引导,一个停靠工具条将被创建,你也可以看到你加入到Tools菜单的菜单项。
他们就在那里!一个有你写的AddIn的outlook,一个带有很酷的工具条和新的菜单项的扩展的outlook!感谢ATL!你的小于50Kb的AddIn同样提供了轻量级的有意义的Com服务。享受这一刻吧!

单单放置两个工具条按钮和一个菜单项并没有什么用处,除非我们写命令处理代码和响应他们的事件。现在我们回到正题。当然在这里,点击不同的按钮和菜单项,我们紧紧弹出简单的对话框。这就是你添加AddIn功能的地方。从CRM 工具、自动联系管理、邮件通知、邮件过滤到高级的文档管理到各种各样的应用,Com AddIns可以执行各种各样的任务的验证。
CommandBarButton控件暴露了一个点击事件(当用户点击一个Command Bar 按钮时触发)。当用户点击工具条按钮或者是点击菜单项的时候我们将使用这个事件去运行代码。对于这些,我们的Com AddIn对象不得不处理_CommandBarButtonEvents事件。点击事件被声明如下:
//...
//....Office objects typelibrary
//....

[id(0x00000001), helpcontext(0x00038271)]
void Click(
[in] CommandBarButton* Ctrl,
[in, out] VARIANT_BOOL* CancelDefault);

//....
//...
我们不得不做所有我们能做的事情去实现那些将被事件源通过规范的连接点协议调用的接收器接口(无论什么时候一个工具条按钮或菜单项被点击)。通过回调函数我们可以得到一个源CommandBarButton 对象的指针和一个用来接受和取消默认操作的布尔值。就像实现一个dispatch接收器接口一样,那也不是什么新东西,作为一个ATL程序员你可能要花一段时间去做这些。
但是对于那些非初始化的,ATL为ATLCom对象提供两个模板类IDispEventImpl<> 和 IDispEventSimpleImpl<> ,这为IDispatch接口提供了实现。我更喜欢用轻量级的IDispEventSimpleImpl,因为它不需要另外的类型库信息。你的类紧紧源于 IDispEventSimpleImpl<>。建立你的接收器映射,通过_ATL_SINK_INFO结构体设置你的回调参数,最后调用 DispEventAdvise 和 DispEventUnadvise从源接口连接和断开。对于我们的工具条按钮和菜单项,如果我们要写一个单一的回调函数来处理所有的事件,那么,一旦我们有一个指向触发事件的CommandBarButton的指针,我们可以使用GetCaption去得到这个按钮的文本,在这个基础上,我们可以执行一些选择性的动作。但是对于这个例子,我们为每一个事件编写一个回调函数。
下面是编写的步骤:
使你的类继承于IDispSimpleEventImpl-第一个参数是封装在ActiveX控件中的子窗口的ID。但是对于我们来说,它可以是任何预先定义的唯一标识事件源的整数(在这里指的是第一个工具条按钮)。
class ATL_NO_VTABLE CAddin :
public CComObjectRootEx < CComSingleThreadModel>,
.....
.....
public IDispEventSimpleImpl<1,CAddin,&__uuidof(Office::_CommandBarButtonEvents>
建立回调函数-第一个我们定义的,如下所示:
void __stdcall OnClickButton(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);
接下来我们使用_ATL_SINK_INFO结构去描述回调参数。打开AddIn.h文件,在文件顶部添加如下声明:
? extern _ATL_FUNC_INFO OnClickButtonInfo;
接着打开AddIn.cpp,添加如下定义:
? _ATL_FUNC_INFO OnClickButtonInfo ={CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};
OnClickButton是非常基础的,就像下面的:
? void __stdcall CAddin::OnClickButton(IDispatch* /*Office::_CommandBarButton* */ Ctrl,
? VARIANT_BOOL * CancelDefault)
? {
? USES_CONVERSION;
? CComQIPtr<Office::_CommandBarButton> pCommandBarButton(Ctrl);
? //the button that raised the event. Do something with this...
? MessageBox(NULL, "Clicked Button1", "OnClickButton", MB_OK);
?
? }
我们使用ATL宏BEGIN_SINK_MAP() 和 END_SINK_MAP()建立接收器消息映射。接收器消息映射由SINK_ENTRY_XXX组成。接收器消息映射提供定义事件的Dispatch ID和处理他的成员函数。
? BEGIN_SINK_MAP(CAddin)
? SINK_ENTRY_INFO(1, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01,
? OnClickButton, &OnClickButtonInfo)
? END_SINK_MAP()
现在每一件事情都到位了,我们不得不使用DispEventAdvise() and DispEventUnadvise()连接和断开事件源.我们的CAddIn类的OnConnection() 和OnDisconnection()仅仅是替代了这些。对于DispEventAdvise() and DispEventUnadvise()的参数分别是事件源上的任何的接口和任何被期望的事件源上的接口。
//connect to event source in OnConnection
// m_spButton member variable is a smart pointer to _CommandBarButton
// that is used to cache the pointer to the first toolbar button.

DispEventAdvise((IDispatch*)m_spButton,&DIID__CommandBarButtonEvents);

//when I'm done disconnect from the event source
//some where in OnDisconnection()

DispEventUnadvise((IDispatch*)m_spButton);
为我们的命令按钮和菜单项实现Dispatch 接收器是很相似的,写处理代码并且连接和断开他们就像上面的描述。如果每一步都进行的畅通无阻,在你Rebuild你的程序并且运行它。无论什么时候,按钮和菜单项被点击,你的回调函数将被执行。

添加属性页:
在这篇文章里我们最后要学会去做的是添加我们自己的“Option“属性页到outlook的Tools->Option的属性表中。
下来我们要加一个页到outlook的option菜单里作为我们我们的AddIn的一部分。我们将象ActiveX控件一样实现实现属性页。当用户点击 Tools->Option菜单项,应用程序对象发出一个OptionsPagesAdd事件(通过outlook对象模块中的 _ApplicationEvents接口)。
dispinterface ApplicationEvents
{
....

[id(0x0000f005), helpcontext(0x0050df87)]
void OptionsPagesAdd([in] PropertyPages* Pages);
....
}

[
odl,
uuid(00063080-0000-0000-C000-000000000046),
helpcontext(0x0053ec78),
dual,
oleautomation
]
....
....

interface PropertyPages : IDispatch {
[id(0x0000f000), propget, helpcontext(0x004deb87)]
HRESULT Application([out, retval] _Application** Application);
....
....

[id(0x0000005f), helpcontext(0x00526624)]
HRESULT Add([in] VARIANT Page,
[in, optional] BSTR Title);

[id(0x00000054), helpcontext(0x00526625)]
HRESULT Remove([in] VARIANT Index);
};

OptionsPagesAdd事件传递给我们我们一个PropertyPages Dispatch接口,他的Add方法用来添加页。Add方法的参数是我们的控件的ProgID和新的页的标题文本。相似的,我们调用Remove()方法和要删除页的索引来删除页。
现在我们来加一个ActiveX复合控件。我们点击Insert->new ATL Object.从Category中选择Controls,从Object列表中选择Lite Composite Control,点击OK。在ShortName中输入PropPage,在属性页面选上Support ISupportErrorInfo选项。点击Ok,接受所有的默认选项。
现在我们要来实现PropertyPage接口。在类视图里右键点击CPropPage,选择Implement Interface,点击Add TypeLib按钮。选中Microsoft Outlook 9.0 Object Library 点击OK。从接口列表中选择PropertyPage点击OK。
向导自动为PropertyPage接口添加三个方法:Apply()、Get_Dirty()、GetPageInfo()。现在做下面的修改,在Com Map中把这一行:
COM_INTERFACE_ENTRY(IDispatch)
改成:
COM_INTERFACE_ENTRY2(IDispatch,IPropPage)
以排除不明确的地方。
接下来实现IDispatch,我们使用IDispatchImpl<>模板类。我们在CPropPage类的声明部分用以下代码:
public IDispatchImpl <
Outlook::PropertyPage,&__uuidof(Outlook::PropertyPage),
&LIBID_OUTLOOKADDINLib>
替换掉:
class ATL_NO_VTABLE CPropPage :
public CComObjectRootEx<CComSingleThreadModel>,
public IDispatchImpl<IPropPage, &IID_IPropPage, &LIBID_TRAILADDINLib>,
....
....
public PropertyPage
从PropPage.h文件的顶部删掉多余的#import语句。类型库已经在stdAfx.h中导入了,因此这里没有必要再导入。
下来我们要连接和断开ApplicationEvents接口,并为他写回调函数。你已经知道该做什么了。我们再次使用 IDispEventSimpleImpl<>为ApplicationEvents建立Dispatch接收器,更新接收器映射,为 OptionsAddPage事件写回调函数。因为我们多次使用了IDispEventSimpleImpl<>, 我们为每一个接口事件使用TypeDef。代码片段如下:
extern _ATL_FUNC_INFO OnOptionsAddPagesInfo;

class ATL_NO_VTABLE CAddin :
....
....
public IDispEventSimpleImpl<4,CAddin,&__uuidof(
Outlook::ApplicationEvents)>
{
public:
//typedef for applicationEvents sink implementation
typedef IDispEventSimpleImpl</*nID =*/ 4,CAddin,
&__uuidof(
Outlook::ApplicationEvents)> AppEvents;
....
....
....
BEGIN_SINK_MAP(CAddin)
....
SINK_ENTRY_INFO(4,__uuidof(
Outlook::ApplicationEvents),
/*dispid*/0xf005,OnOptionsAddPages,&OnOptionsAddPagesInfo)
END_SINK_MAP()

public:
//callback method for OptionsAddPages event
void __stdcall OnOptionsAddPages(IDispatch *Ctrl);
};

//in PropPage.cpp file

_ATL_FUNC_INFO OnOptionsAddPagesInfo = (CC_STDCALL,VT_EMPTY,1,{VT_DISPATCH}};

void __stdcall CAddin::OnOptionsAddPages(IDispatch* Ctrl)
{
CComQIPtr<
Outlook::PropertyPages> spPages(Ctrl);
ATLASSERT(spPages);

//ProgId of the propertypage control
CComVariant varProgId(OLESTR("OutlookAddin.PropPage"));

//tab text
CComBSTR bstrTitle(OLESTR("OutlookAddin"));

HRESULT hr = spPages->Add((_variant_t)varProgId,(_bstr_t)bstrTitle);
if(FAILED(hr))
ATLTRACE("\nFailed adding propertypage");
}
最后,在OnConnection和OnDisConnection里,调用DispEventAdvise 和 DispEventUnadvise连接和断开ApplicationEvents。现在一切就绪,我们ReBuild工程。下来点击F5,点击 Outlook的Tools->Options菜单。你应该看见了我们新加的页。但是当我们点击这个新的页,一个对话框将出现告诉我们属性页不能被显示。发生了什么?难道我们的辛苦劳动白费了?
发生这个情况的原因是:尽管我们的属性页创建了,但是outlook并没有得到关于这个页的键盘行为的任何信息。IOleControl的 GetControlInfo方法的ATL的默认实现返回E_NOTIMPL,因此包容器无法为这个属性页和包容器处理击键事件。因此我们的页不能被显示。修改这个问题,只需重载GetControlInfo()方法,让他返回S_OK。
在.PropPage.h里添加如下声明:
STDMETHOD(GetControlInfo)(LPCONTROLINFO lpCI);
我们在PropPage.cpp文件里重载GetControlInfo()方法,仅仅将返回值改为S_OK,代码如下:
STDMETHODIMP CPropPage::GetControlInfo(LPCONTROLINFO lpCI)
{
return S_OK;
}
就是这些了。现在再次Build工程,点击outlook的tools->Option,激活我们的页,现在我们的属性页应该正确无误的显示了。
我们的学习要结束了。我们能在office里我们能做的事情无穷无尽。因为在一个AddIn里你可以获得父应用程序的内部对象模块,你能做所有主应用程序能做的事,或者更多。另外你也能使用别的接口比如MS Assistant(并不直接关联到应用程序)。没有做不到的只有想不到的。

 

内容概要:本文围绕“单相逆变器闭环逆变电路PWM模型仿真研究”展开,基于Simulink平台构建单相逆变器的闭环控制系统仿真模型,重点研究PWM调制技术在逆变电路中的应用与实现。文中详细阐述了系统架构设计、电压电流双闭环控制策略的实现原理、控制器参数设计及仿真建模全过程,并通过仿真结果验证了控制方案在动态响应、稳态精度与系统稳定性方面的有效性。同时,文档还涵盖多种电力电子系统典型应用场景,如多类型短路故障仿真(中性点不接地、经小电阻接地、经消弧线圈接地等)、软开关技术、微电网能量管理、MPPT控制等,体现出较强的技术综合性和工程实践价值。; 适合人群:电气工程、自动化、电力电子与新能源等相关专业的高校本科生、研究生、科研人员,以及从事电力系统仿真、逆变器设计与新能源并网技术研发的工程技术人员。; 使用场景及目标:①掌握基于Simulink的单相逆变器闭环控制系统建模与PWM仿真方法;②深入理解双闭环控制、SPWM/SVPWM调制、系统稳定性分析等核心技术原理;③为课程设计、毕业设计、科研项目或实际工程开发提供可复用的仿真模型与技术支持; 阅读建议:建议结合文中仿真模型动手实践,重点掌握PI控制器参数整定、PWM信号生成机制与仿真结果分析方法,同时可延伸学习文档中涉及的软开关、故障仿真、微电网控制等关联技术,以拓展系统级设计能力。
重要提示】本资源设置为0积分下载,若非0积分请勿轻易下载 亲爱的CSDN用户: 首先感谢你点进这个资源页面。我需要提前说明一个重要情况: 本资源原本已设置为“0积分下载”,即作者希望完全免费共享。但CSDN平台有时会根据文件的下载热度、文件大小、用户权限等因素,自动将部分资源的积分调整为非0数值(如1积分、2积分、5积分等)。这是平台系统的自动行为,而非作者本人的设定。 因此,如果你当前看到该资源的下载所需积分不是0(例如显示为1、2、3……),请谨慎决定是否下载。 如果你按照非0积分支付并下载后发现资源内容不符合预期、链接失效,或者实际上该资源本应是免费的,作者无法为此承担积分损失或退还操作。强烈建议:仅在页面显示为0积分时进行下载。 另外,本资源描述中并未直接提供具体的下载地址或外部链接,因为它本身是一个通过CSDN官方上传通道提交的文件/内容包。如果你看到描述中没有外部网盘地址,这是正常的——资源文件应通过CSDN内置的“下载”按钮获取。若因平台积分显示异常导致你支付了积分,请优先联系CSDN客服咨询积分退还政策,作者没有权限修改平台自动设定的积分值。 感谢你的理解与支持。技术分享本应开放,但受限于平台规则,特此提醒如上。祝学习进步!
重要提示】本资源设置为0积分下载,若非0积分请勿轻易下载 亲爱的CSDN用户: 首先感谢你点进这个资源页面。我需要提前说明一个重要情况: 本资源原本已设置为“0积分下载”,即作者希望完全免费共享。但CSDN平台有时会根据文件的下载热度、文件大小、用户权限等因素,自动将部分资源的积分调整为非0数值(如1积分、2积分、5积分等)。这是平台系统的自动行为,而非作者本人的设定。 因此,如果你当前看到该资源的下载所需积分不是0(例如显示为1、2、3……),请谨慎决定是否下载。 如果你按照非0积分支付并下载后发现资源内容不符合预期、链接失效,或者实际上该资源本应是免费的,作者无法为此承担积分损失或退还操作。强烈建议:仅在页面显示为0积分时进行下载。 另外,本资源描述中并未直接提供具体的下载地址或外部链接,因为它本身是一个通过CSDN官方上传通道提交的文件/内容包。如果你看到描述中没有外部网盘地址,这是正常的——资源文件应通过CSDN内置的“下载”按钮获取。若因平台积分显示异常导致你支付了积分,请优先联系CSDN客服咨询积分退还政策,作者没有权限修改平台自动设定的积分值。 感谢你的理解与支持。技术分享本应开放,但受限于平台规则,特此提醒如上。祝学习进步!
内容概要:本文系统阐述了CUDA并行计算的核心优化技巧,围绕提升SM利用率、最大化内存带宽、隐藏访存延迟和减少指令开销四大目标,从GPU硬件架构、线程模型、内存访问、指令执行、内核设计及工程实践六个维度展开。重点讲解了线程块配置、Warp分支发散规避、全局内存合并访问、共享内存Bank冲突避免、寄存器与常量内存使用、异步传输与多流并行、快速数学函数、原子操作优化、内核拆分与融合、Tensor Core利用等关键技术,并提供了编译优化参数和Nsight系列性能分析工具的使用指导,形成了一套完整的CUDA性能优化方法论。; 适合人群:具备CUDA编程基础,从事高性能计算、深度学习、科学计算或GPU加速开发的工程师与研究人员,尤其适合工作2年以上的开发者提升底层优化能力。; 使用场景及目标:①解决CUDA程序中SM利用率低、内存带宽不足、访存延迟高等性能瓶颈;②掌握从基础到高阶的系统性优化策略,实现程序性能的指数级提升;③结合Nsight工具进行性能剖析与迭代优化。; 阅读建议:学习时应结合实际代码调试与性能分析工具(如Nsight Compute和Nsight Systems)进行验证,优先实施线程块配置、合并访问、-O3编译等低成本高回报的基础优化,再逐步深入共享内存优化、内核融合、Tensor Core利用等高阶技术,同时推荐优先使用cuBLAS、cuDNN等NVIDIA官方优化库以逼近硬件极限性能。
内容概要:本文提供了一份完整的“大学生创新创业训练计划项目”申报材料模板包,围绕“基于深度学习的智能垃圾分类回收箱设计与实现”项目,详细展示了从项目申报书、答辩PPT、中期检查表到结题报告的全套规范文档。内容涵盖项目背景、目标、研究内容、技术路线、创新点、进度安排、预期成果、经费预算及风险应对等关键环节,并以实际案例呈现各阶段成果,如YOLOv8轻量级模型识别准确率达96%、单台成本控制在780元、校园试点回收520kg可回收物、获得软著与论文成果等,形成可复制推广的校园绿色解决方案。; 适合人群:参与大学生创新创业训练计划(大创项目)的本科生团队,尤其是工科类、计算机相关专业、有意向开展人工智能+环保类实践项目的1-3年级学生;同时也适用于指导教师和项目评审人员作为参考模板。; 使用场景及目标:①帮助学生团队系统规划并撰写高质量的大创项目申报书与结题报告;②指导项目全过程管理,包括技术实施、进度控制、经费使用与成果凝练;③支撑项目答辩展示,提升项目规范性与竞争力,冲击“互联网+”“挑战杯”等赛事奖项; 阅读建议:此资源不仅提供文本模板,更体现了项目从立项到结题的完整逻辑链条,使用者应结合自身课题,参照其结构化表达方式、量化目标设定和技术落地路径进行模仿与创新,注重理论与实践结合,强化数据支撑与成果可视化。
内容概要:本文提供了一个基于Simulink的光伏储能单相逆变器并网仿真模型,系统实现了并网逆变电路的PWM调制控制、闭环控制策略及并网运行特性的仿真分析,涵盖系统建模、控制算法设计、稳定性验证与动态性能评估等关键环节。该模型不仅支持对单相逆变器在并网过程中的电流谐波、功率因数、电能质量及系统稳定性的深入研究,还可拓展应用于多类型电力系统仿真场景,如MPPT控制、软开关技术、微电网能量管理、短路故障分析(包括单相、两相接地及相间短路)、直流电机双闭环控制、Buck/Boost类变换器控制等,展现出广泛的科研适配性与工程实践价值。; 适合人群:面向具备电力电子、自动控制理论或电气工程背景,熟练掌握Simulink/Matlab仿真工具,从事新能源发电系统、微电网控制、逆变器拓扑与控制策略研究的硕士/博士研究生、科研人员及电力系统相关领域的工程技术人员。; 使用场景及目标:①开展光伏发电系统并网控制策略的设计与仿真验证;②学习并掌握单相逆变器PWM调制、锁相环(PLL)、电压电流双闭环控制等核心技术的建模方法;③作为课程设计、毕业设计或科研项目的仿真平台,支撑控制系统开发与优化;④结合文中提供的多种电力系统案例(如故障仿真、储能控制、微网调度),进行横向对比与综合能力提升; 阅读建议:建议读者结合文中列出的多个仿真案例进行扩展学习,重点关注控制器参数设计与系统动态响应之间的关系,动手复现模型并进行仿真调试,通过改变负载、电网条件或控制参数,深入理解并网逆变器的工作机理与控制规律,从而提升实际科研与工程应用能力。
重要提示】本资源设置为0积分下载,若非0积分请勿轻易下载 亲爱的CSDN用户: 首先感谢你点进这个资源页面。我需要提前说明一个重要情况: 本资源原本已设置为“0积分下载”,即作者希望完全免费共享。但CSDN平台有时会根据文件的下载热度、文件大小、用户权限等因素,自动将部分资源的积分调整为非0数值(如1积分、2积分、5积分等)。这是平台系统的自动行为,而非作者本人的设定。 因此,如果你当前看到该资源的下载所需积分不是0(例如显示为1、2、3……),请谨慎决定是否下载。 如果你按照非0积分支付并下载后发现资源内容不符合预期、链接失效,或者实际上该资源本应是免费的,作者无法为此承担积分损失或退还操作。强烈建议:仅在页面显示为0积分时进行下载。 另外,本资源描述中并未直接提供具体的下载地址或外部链接,因为它本身是一个通过CSDN官方上传通道提交的文件/内容包。如果你看到描述中没有外部网盘地址,这是正常的——资源文件应通过CSDN内置的“下载”按钮获取。若因平台积分显示异常导致你支付了积分,请优先联系CSDN客服咨询积分退还政策,作者没有权限修改平台自动设定的积分值。 感谢你的理解与支持。技术分享本应开放,但受限于平台规则,特此提醒如上。祝学习进步!
因为工作需要,每天需要打很多次卡,然后忙起来就忘了,忙完了就会想,刚才打卡了吗?弄错就会漏打卡了,漏打卡会有处罚。就想到写一个程序来解决这个痛点。就有了本次发布的这个程序。 PHP项目,修改起来也简单,也方便二开。本来就是H5页面布局,部署好,直接手机浏览器打开,或者使用封装工具,封装成apk。本人已打包为微信小程序,使用起来很方便。 项目简介 本项目是一个多用户打卡记录系统,基于 PHP + MySQL 开发,提供简洁的用户打卡功能和记录管理。 核心功能 功能模块 描述 用户认证 支持用户注册、登录、密码修改、密码重置 打卡功能 用户可进行每日打卡,记录打卡时间 记录查询 支持按日期查询打卡记录 用户管理 支持头像上传、个人信息查看 数据统计 提供打卡统计功能 技术特点 轻量级架构:纯 PHP 开发,无需框架依赖,部署简单 响应式设计:移动端友好的 UI 界面,支持触摸操作 安全性: 使用 prepare + bind_param 防止 SQL 注入 密码采用哈希加密存储 Session 会话管理用户状态 模块化设计:API 接口与前端分离,便于扩展 项目结构 Plain Text ├── api/ # RESTful API 接口 │ ├── checkin.php # 打卡接口 │ ├── login.php # 登录接口 │ ├── register.php # 注册接口 │ ├── records.php # 记录查询接口 │ ├── stats.php # 统计接口 │ └── … ├── config/ # 配置文件 │ ├── database.php # 数据库配置 │ └── auth.php # 认证配置 ├── sql/ # 数据库脚本 │ └── init.sql # 初始化脚本 ├── avatars/ # 头像存储目录 ├── ind
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值