65.9K
CodeProject 正在变化。 阅读更多。
Home

导入类型库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (36投票s)

2003年2月23日

Ms-PL

9分钟阅读

viewsIcon

266685

使用替代方法导入类型库和COM对象可以提高源代码的可移植性。

引言

在讨论CodeProject上发表的一篇题为“从Outlook导入联系人”的文章时,我指出使用文件引用来导入类型库通常不是一个好主意。你不应该假定所有人的项目或类型库都在相同的路径甚至相同的驱动器上。通过使用任何COM开发人员都应该熟悉的简单替代方法,你可以提高源代码的可移植性。

要求

要使用替代文件引用的方法导入类型库,你需要C/C++编译器版本13.00。此编译器随Microsoft Visual C++ .NET、Microsoft Visual Studio .NET和Microsoft Platform SDK一起提供。

关于类型库

在深入探讨导入类型库的替代方法之前,你可能会想知道类型库(typelib)是什么。根据MSDN的定义,类型库是“[一个]文件或另一个文件中的组件,其中包含有关公开对象的类型信息。类型库是使用MIDL编译器创建的,可以通过ITypeLib接口访问。”这意味着可以在不实际引用实现这些事物的对象的情况下获取有关接口、结构、枚举及其所有成员的信息。这在开发COM客户端时很重要,因为你通常不知道实现库位于哪个目录或计算机中,而这正是COM的一个目标和特性:可重用性和可扩展性。

由于你通常不知道实现类在哪里,因此你必须了解更多关于它的信息。早期绑定自动化客户端使用此信息在编译时缓存成员ID,并使用该信息直接调用方法。后期绑定自动化客户端在设计时使用此信息,但通过IDispatch接口的能力在运行时根据其ID调用方法。有关此主题的更多信息,请参阅IDispatch 接口的文档。

既然你对类型库有了一些了解,你可能会想为什么以及如何使用它们。“如何”将在以下各节中回答,也是本教程的范围。“为什么”是因为你通常必须在设计时和编译时知道哪些成员可用。除非你正在编写一个完全自主的客户端,它通过IDispatch在运行时发现所有类型信息,并且知道如何处理公开的更多接口,否则你需要知道要调用什么,即使是在像Visual Basic这样的后期绑定环境中,在这种情况下你会“引用”库。如果你利用编译器,它们会为你处理很多细节。

引用类型库

与大多数COM对象一样,你可以使用几种方法引用类型库。它们通过全局唯一标识符(GUID)来标识,更具体地说是LIBID。然而,与COM对象不同,类型库通常只有一个LIBID用于类型库的所有版本。仅此一点就提高了可移植性,特别是对于后期绑定客户端或其他实现类。类似的technique也可以用于导入特定的COM对象,可以使用它们的类标识符(CLSID)或程序ID,这两种都是独立于版本和依赖于版本的标识符。

你仍然可以通过文件路径(包括相对路径和绝对路径)引用类型库,但通常只在是你或你的公司正在开发类型库,并且可能不希望在设计时在系统上注册类型库时才这样做。

导入类型库

既然你知道可以使用LIBID引用类型库,你必须知道如何利用这些信息。在前面提到的文章中,作者需要导入Office和Outlook的类型库。你可以使用 oleview.exe(Platform SDK中提供)来查找有关接口的信息,例如系统上存在的类型库的LIBID和版本。在大多数情况下,类型库是向后兼容的,但建议并且通常需要确保客户端计算机具有最低或正确版本的COM库。

这些LIBID可以在注册表中的 HKEY_CLASSES_ROOT\TypeLib 下找到,或者使用 oleview.exe。使用 oleview.exe 会在我的系统上得到关于 Office 和 Outlook 类型库的以下信息

OleView.exe Screen Shots - Type Libraries

要导入这些类型库,无需我们使用 using 声明命名空间,并使用命名 GUID 以便轻松引用(有关更多属性,请参阅 MSDN),我们将输入以下内容:

#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" named_guids no_namespace
#import "libid:00062FFF-0000-0000-C000-000000000046" named_guids no_namespace

您可以在右窗格中看到这些信息。GUID(格式为 {12341234-1234-1234-1234-123412341234})是 LIBID,其下方的十进制数是版本,格式为 major.minor。再次强调,LIBID 通常对类型库保持不变,但版本最常会更改。从屏幕截图中可以看出,Outlook XP 的类型库使用版本 9.1。如果您想修改上述代码以使用特定的类型库版本,以便只针对 Outlook XP 类型库编译代码,您将更改以下内容

#import "libid:00062FFF-0000-0000-C000-000000000046" version(9.1) named_guids no_namespace

使用此方法,你仍然可以控制应用程序使用的类型库版本,但仍然无需担心文件路径。这在开发团队中没有通用工作站映像时尤其方便。你甚至可以指定语言ID来指定要使用的类型库区域设置。如果不指定版本号,则使用最新的类型库版本。在这种情况下,请指定 lcid 属性以及语言ID的十六进制表示。例如,en-US(美国英语)将是“409”。这在十进制形式中是“1033”。如果你不指定区域设置ID或只有一个区域设置可用,则使用第一个或唯一的区域设置。

关于类型库的信息也可以用于可移植性较差的替代方案,即使用相对或绝对路径。这两种技术都比另一种具有优势,但在将源代码移植到不同系统时会带来缺点。然而,当你知道项目布局的目录结构在整个开发团队中保持一致,并且在测试或部署之前不想注册任何库时,使用文件引用是一个好主主意。你可以在我题为“在Java中嵌入.NET控件”的文章中,或者从下面的示例中看到这种情况的一个例子

#import "C:\Program Files\Microsoft Office\Office10\msoutl.old" named_guids

这可以将我们限制在特定版本,但请记住,类型库不必注册就可以从中提取信息。在需要文件引用的开发情况下,你总是可以将类型库包含在项目中——甚至可以包含在像Visual SourceSafe或CVS这样的源代码控制系统中——以确保开发团队的所有成员都拥有相同的版本。

引用COM对象

如果您不需要引用整个类型库,而只想从类型库中获取特定的控件呢?可以使用类似的替代方案——以及另一种替代方案——以及帮助。COM对象也由GUID标识,具体称为类ID或CLSID;然而,这些CLSID通常随对象的版本而变化。为了解决这种情况,您可以使用对象的程序ID或ProgID。ProgID可以是依赖于版本的,也可以是独立于版本的,这意味着您可以指定特定版本或使用最新版本(或引用的任何版本)。

版本依赖的ProgID通常采用 AppID.ClassID.Major[.Minor] 的形式。例如,如果你想引用Outlook Application对象(所有特定于应用程序的方法通常都源自该对象),你可以使用版本依赖的ProgID,Outlook.Application.10。如果你知道你的方法调用与Outlook的许多版本兼容——甚至可能所有版本都兼容,因为Outlook并不那么老——你可以使用版本独立的ProgID Outlook.Application。这些ProgID可以直接在注册表中的 HKEY_CLASSES_ROOT 下找到,或者使用 oleview.exe,它会产生以下结果

OleView.exe Screen Shots - Program IDs

您可以在右侧窗格中看到独立于版本和依赖于版本的 ProgID。注册表将这些信息存储在单独的键中,并且通常将独立于版本的 ProgID 指向最新的依赖于版本的 ProgID,但这并非总是如此,因为许多因素可能会错误地(或有意地)改变这种行为。然而,这种情况很少发生。

导入 COM 对象

现在您已经知道了要导入的对象的 ProgID,您只需修改之前的语句,使用 ProgID 而不是 LIBID。

#import "progid:Outlook.Application" named_guids no_namespace

上面使用的是版本独立的 ProgID。如果您想指定导入 Outlook 10 类型库,您可以使用版本依赖的 ProgID 或类型库的 version 属性。然而,请记住这些可能不一致。您应该记住,我们之前了解的类型库版本是 9.1,而不是 10。在使用 ProgID 时,我建议您坚持使用版本依赖的 ProgID,而不是使用 version 属性;这可能会有点令人困惑。使用版本依赖的 ProgID 看起来像这样

#import "progid:Outlook.Application.10" named_guids no_namepace

属性

在整篇文章中,我主要保持了所使用的 #import 属性的一致性。这些是我的偏好,不同的情况需要不同的措施。有关可用属性及其作用的更多信息,请参阅 MSDN

上述代码示例的一种常见变体是删除 no_namespace。这样做,您可以将 #import 语句分组到单个文件中,同时限制整个源代码中调用的范围。如果我删除 no_namespace 属性,我将不得不像以下代码一样使用 using 指令

#import "progid:Outlook.Application" named_guids
using namespace Outlook;

// Or we can use it explicitly...
CComPtr<Outlook::_ContactItem> spContactItem;

有关 using 声明using 指令的更多信息,请参阅 MSDN。

示例

因为本教程受到 Deepesh Dhapola 的文章“从 Outlook 导入联系人”中讨论的启发,并且因为我在本教程中使用了 Office 和 Outlook 类型库以及 COM 对象引用,所以我邀请您下载他的 示例源代码 并修改 OutlookContactsDlg.cpp 中的以下几行:

// #import "E:\Program Files\Common Files\Microsoft Shared\Office10\mso.dll" \
//    named_guids
// #import "E:\Microsoft Office\Office10\MSOUTL.OLB" \
//    no_namespace exclude("_IRecipientControl", "_DRecipientControl")

#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" named_guids
#import "libid:00062FFF-0000-0000-C000-000000000046" \
    no_namespace exclude("_IRecipientControl", "_DRecipientControl")

现在,万一您的E:盘(如果有的话)像我的一样是CD-RW,只要您注册了类型库,即使应用程序未安装,编译示例也应该没有问题。

摘要

不应该完全抛弃文件引用。当开发团队同时开发自动化服务器和客户端,尤其是在处理现有项目的新版本时,文件引用通常是必要的。然而,我希望本教程为您提供了替代方案,可以在大多数情况下使您的源代码在机器之间更具可移植性,当您使用预先存在的类型库来扩展自己的应用程序,甚至扩展现有功能时。导入类型库通过可重用代码减少您的代码,并使您的开发工作通常更容易。下次为COM开发时,通过考虑这些导入类型库和COM对象的替代方案,让您的工作变得更加轻松。

© . All rights reserved.