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

vmime.NET - Smtp, Pop3, Imap 库 (适用于 C++ 和 .NET)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (56投票s)

2014 年 2 月 1 日

GPL3

18分钟阅读

viewsIcon

261204

downloadIcon

1823

借助此项目,C++ 和 .NET Windows 程序员可以获得一个功能非常强大的库,通过 SMTP、POP3 和 IMAP 发送和下载电子邮件,并支持 TLS 和 SSL。

下载次数

  • Vmime.NET_All.zip (9 MB):包含编译 32 位平台所需的所有文件的完整项目。
  • Vmime.NET_64.zip (6 MB):仅包含编译 64 位平台所需的一些额外文件。

摘要

借助此项目,C++.NET Windows 程序员可以获得一个功能非常强大的库,通过SMTP、POP3 IMAP 发送和下载电子邮件,并支持TLSSSL

特点

  • 这是一个高质量的库,可用于生产环境。
  • 实现了SMTP、POP3、IMAP 协议。
  • TLSSSL 加密(使用 openssl 库),包含 185 个内置的X.509 根证书。
  • SASL 身份验证(简单身份验证和安全层)。
  • 电子邮件构建器可构建 RFC-2822 和多部分消息。
  • 支持带嵌入对象的 HTML 电子邮件生成。
  • 完全支持附件,并自动检测 1010 个内置MIME 类型的 MIME 类型。
  • 电子邮件解析器允许提取附件或文本。
  • 您将在 15 分钟内学会如何使用此库。它遵循KISS 原则(“保持简单,笨蛋”)。
  • 该项目主要用纯 C++ 编写,并包含一个托管 C++ 包装器,可将功能暴露给 .NET(C#Visual Basic .NET)。
  • 完全RFC 兼容的实现。
  • 8 位 MIME、编码字扩展、流水线和分块消息传输。
  • 跟踪输出显示与服务器的全部通信。
  • 完全支持Unicode,适用于 CJK(中文、日文、韩文)。
  • 由经验丰富的程序员编写的非常简洁的代码
  • C++ 演示应用程序。
  • C# 演示应用程序。
  • 所有必需的依赖项(例如 openssl)都包含在下载中。您无需下载其他源来编译此项目。
  • 下载内容包含适用于 .NET 框架 2.0、3.0、3.5 的vmime.NET_2.dll
  • 下载内容包含适用于 .NET 框架 4.0、4.5、4.6 等的vmime.NET_4.dll
  • 第二个下载包含两者的64 位版本。
  • 该 DLL 是强命名(已签名)。
  • GNU GPL 许可证。(提供商业许可证)
  • 2016 年 2 月更新:现在项目可以编译,无需使用已弃用的编译器开关 '/clr:oldsyntax'。

为什么使用 vmime?

我一直在寻找一个 SMTP/POP3/IMAP 库。我找到的要么是价格昂贵的商业库,要么是免费但不支持加密的库。如今,许多 SMTP 服务器都要求使用 TLS 或 SSL 连接。没有加密支持的电子邮件库将无法连接到 Gmail SMTP 服务器发送电子邮件。

最后我找到了 vmime,它是一个适用于 SMTP、POP3、IMAP、SendMail 和 MailDir 的跨平台库,代码编写得非常简洁,托管在 Github 上,主要由 Vincent Richard 编写,他做了出色的工作。

一场噩梦开始...

但这个库是 Gnu 项目,主要在Mac / Linux 环境下开发和测试。理论上,可以在 Windows 上使用 Cmake 编译器进行编译,但这会产生大量我从未见过的晦涩的错误消息,所以我放弃了。

此外,如果您想编写用于 .NET 的托管 C++ 项目,CMake 无用。所以我必须找到一种在Visual Studio 中编译这些东西的方法。如果您曾经将 Linux 代码移植到 Windows,您就知道这是多么糟糕的经历。

在消除了 Visual Studio 中编译 Linux 项目到 Windows 时出现的各种错误后,我又遇到了 Config.hpp 文件缺失的问题,该文件包含所有项目设置。我不得不手动找出如何构建这个文件。

然后 vmime 依赖于几个其他 Gnu 项目,我不得不单独下载它们

  1. GnuTLSOpenSSL 库用于加密,
  2. iconv,一个字符集转换库,
  3. gsasl,一个身份验证库

所有这些库都有预编译的 Windows DLL 版本,并附带一个链接器 Lib 文件。其中一些带有类似“libgsasl.a”的文件名。我以前从未见过这种文件扩展名,并且认为:这些文件是为 Windows 制作的,所以我将其更改为“libgsasl.lib”。最糟糕的是,Visual Studio 会接受这些文件而不报错。但编译后的二进制文件将无法运行。
最后我发现这些 *.a 文件是为 Windows 上的 Gnu 编译器准备的。
那么 Visual Studio 的 Lib 文件在哪里?
它们根本不存在。
经过调查,我发现可以使用 Visual Studio 命令行工具从Def 文件创建 Lib 文件。请参阅“libvmime\src\gsasl\CreateLIB.bat”。

我开始用GnuTLS 进行第一次测试,这是一个错误,因为

  1. GnuTLS 依赖于另外 4 个库(总共 5 个 DLL!)
  2. 这些 DLL 都没有 64 位版本
  3. 它在 Windows 上运行不正常(在电子邮件传输过程中出现错误“TLS 连接未正确终止”)。

我在这上面浪费了大量时间,最后我转向了OpenSSL,它运行得非常好。

iconv 库也给我带来了麻烦,因为没有预编译的 Windows 64 位版本,而且这个库似乎从未为 64 位编译过。最后我用我自己的代码替换了它,该代码使用了 Windows 代码页转换 API。

vmime 的问题

然后我开始与 vmime 代码本身搏斗

一个严重的不足是 vmime 没有Unicode 支持。所有字符串都是 std::string,而 std::wstring 从未被使用过。文件名使用当前 ANSI 代码页进行转换,并传递给 ANSI 文件 API(例如 CreateFileA)。

为了让 vmime 也能被中文日文用户使用,我必须找到一种方法将 Unicode 传递给 vmime。我最终将所有输入字符串在包装器类中转换为 UTF-8。然后,在 Windows 平台处理程序中,我将 UTF-8 字符串转换回 Unicode,并将其传递给宽字符 API(CreateFileW)。

vmime 的另一个严重不足是缺少跟踪输出。当服务器出现错误时,您根本不知道原因,因为您看不到与服务器的通信。我实现了跟踪支持,正如您在下面的屏幕截图中所见。

另一个不足是 vmime 代码无法中止。如果服务器没有响应,用户必须等到超时(最多 30 秒!)才能继续。我已修复此问题。

另一个问题是 vmime 中的错误处理没有得到妥善实现。

然后我发现了一些bug,例如 openssl 抛出“SSL3_WRITE_PENDING:bad write retry”。我联系了 vmime 的作者 Vincent,他为我提供了所有问题的出色支持,并修复了一些 bug。

然后我添加了新功能,这些功能对于易于使用是必不可少的,例如自动MIME 类型检测(通过文件扩展名)和从资源加载根证书

vmime 库被设计为非常灵活和可扩展。这很好,但缺点是使用起来不方便。对于提取电子邮件纯文本等简单任务,您需要编写一个循环,枚举所有消息部分,使用 MessageParser,使用 OutputStreamStringAdapter,ContentHandler,SmartPointer 类,动态转换和字符集转换。所有这些都很笨拙,而且经验丰富的程序员——不熟悉 vmime 的——很容易需要几个小时才能弄清楚如何做到。对于 C++ 初学者来说,使用 vmime 几乎是不可能的。
所以我添加了包装器类,将所有这些复杂的东西隐藏在一个函数中。(KISS 原则

然后我发现了一个非常奇怪的现象:每当 vmime 抛出异常时,我都会遇到内存泄漏和未关闭的 TCP 套接字,因为几个析构函数从未被调用(没有栈展开)。您可以在Stackoverfow 上阅读我如何解决这个问题。

我做了更多的事情,但在此一一列举会太多了……
我所有对源代码的更改都用注释标记为:// FIX by Elmue,如果您搜索一下,会找到 240 多个修改。

最后...

……最后,我花了两个月时间做这个项目。我经历了非常令人沮丧的工作,不止一次差点放弃。

现在您很幸运,可以下载一个即用型库,它运行完美,并且非常易于使用且直观。

EmailBuilder

只需几行代码,您就可以创建一个 MIME 电子邮件,其中包含纯文本部分、HTML 部分、嵌入的 HTML 对象(例如图片)和附件。纯文本和 HTML 文本都是可选的。如果存在 HTML 部分,现代电子邮件客户端将不显示纯文本部分。

C#

using vmimeNET;
using (EmailBuilder i_Email = new EmailBuilder("John Miller <jmiller@gmail.com>", 
                                               "vmime.NET Test Email"))
{
    i_Email.AddTo("recipient1@gmail.com"); // or "Name <email>"
    i_Email.AddTo("recipient2@gmail.com");

    i_Email.SetPlainText("This is the plain message part.\r\n" +
                         "This is Chinese: \x65B9\x8A00\x5730\x9EDE");
                     
    i_Email.SetHtmlText ("This is the <b>HTML</b> message part.<br/>" +
                         "<img src=\"cid:VmimeLogo\"/><br/>" +
                         "(This image is an embedded object)<br/>" +
                         "This is Chinese: \x65B9\x8A00\x5730\x9EDE");

    i_Email.AddEmbeddedObject("E:\\Images\\Logo.png", "", "VmimeLogo");

    i_Email.AddAttachment("E:\\Documents\\ReadMe.txt", "", "");

    i_Email.SetHeaderField(Email.eHeaderField.Organization, "ElmueSoft");

    String s_Email = i_Email.Generate();
} // i_Email.Dispose()

C++

using namespace vmime::wrapper;
cEmailBuilder i_Email(L"John Miller <jmiller@gmail.com>", L"vmime.NET Test Email", 
                      IDR_MIME_TYPES);

i_Email.AddTo(L"recipient1@gmail.com"); // or "Name <email>"
i_Email.AddTo(L"recipient2@gmail.com");

i_Email.SetPlainText(L"This is the plain message part.\r\n"
                     L"This is Chinese: \x65B9\x8A00\x5730\x9EDE");
                     
i_Email.SetHtmlText (L"This is the <b>HTML</b> message part.<br/>"
                     L"<img src=\"cid:VmimeLogo\"/><br/>"
                     L"(This image is an embedded object)<br/>"
                     L"This is Chinese: \x65B9\x8A00\x5730\x9EDE");

i_Email.AddEmbeddedObject(L"E:\\Images\\Logo.png", L"", L"VmimeLogo");

i_Email.AddAttachment(L"E:\\Documents\\ReadMe.txt", L"", L"");

i_Email.SetHeaderField(cEmail::Head_Organization, L"ElmueSoft");

std::wstring s_Email = i_Email.Generate();

在此示例中,“VmimeLogo”是嵌入图像的内容标识符。在 HTML 中,您可以引用嵌入图像,方式如下:<img src="cid:ContentID"/>。

上面的代码将在 Thunderbird 中生成如下所示的电子邮件

这里是变量 s_Email 的源代码,输出到控制台


SMTP

发送电子邮件只需 3 行代码:这再简单不过了。(KISS 原则

C#

using (Smtp i_Smtp = new Smtp(s_Server, u16_Port, e_Security, b_AllowInvalidCertificate))
{
    i_Smtp.SetAuthData(s_User, s_Password);
    i_Smtp.Send(i_Email);
} // i_Smtp.Dispose()

C++

cSmtp i_Smtp(u16_Server, u16_Port, e_Security, b_AllowInvalidCertificate, IDR_ROOT_CA);
i_Smtp.SetAuthData(u16_User, u16_Password);
i_Smtp.Send(&i_Email);

如果 u16_Port == 0,则使用默认端口。

如果服务器发送无效证书

  • 如果 b_AllowInvalidCertificate == true,则会将错误写入跟踪输出;
  • 如果 b_AllowInvalidCertificate == false,则会抛出异常,并且电子邮件不会被发送。

Trace

上面看到的跟踪输出是通过回调由 vmime.NET 返回的。您可以随意处理它:将其写入控制台、显示在日志窗口或保存到日志文件。或者,您可以关闭编译器开关VMIME_TRACE,这样跟踪就会被完全禁用。

关于批量电子邮件的说明

如果您打算发送例如包含 500 个收件人的新闻通讯,您将遇到许多问题

  • 所有免费邮件提供商(Gmail、Yahoo、Gmx 等)都限制每个电子邮件的最大收件人数量(例如 100)。
  • 所有免费邮件提供商都限制每天发送的电子邮件数量。
  • 如果您违反规则,免费邮件帐户可能会被关闭。

这些措施对于打击垃圾邮件是必不可少的,垃圾邮件主要由木马和僵尸网络通过 SMTP 发送。(阅读我关于木马和僵尸网络的文章)
因此,发送新闻通讯的最佳解决方案是安装自己的电子邮件服务器。(我推荐MDaemon Email Server
但即使使用此解决方案,您也必须小心,因为如果您的 IP 地址被列入黑名单,您的服务器可能会被阻止很多天。

安全

您有 4 种将电子邮件发送到 SMTP 服务器的选项:(enum e_Security

  1. 在普通端口(默认 25)未加密。此已弃用选项只能在服务器与您位于同一内网且无法使用其他选项时使用。
  2. 在安全端口(默认 465)使用SSL 加密。
  3. 在普通端口(默认 25)使用TLS 加密(如果可能)。
  4. 在提交端口(默认 587)需要TLS 加密,否则失败。

SSL(Secure Sockets Layer)和 TLS(Transport Layer Security)都会将身份验证(用户名/密码)和电子邮件消息加密。
SSL 立即建立加密连接。
TLS 是 SSL 的后继者。它以未加密连接开始,发送 SMTP 命令“STARTTLS”,然后在同一端口加密流量。

阅读 Wikipedia 关于SSLTLS 的介绍。

服务器证书

在使用 SSL 或 TLS 时,服务器会用 X.509 证书进行身份验证。vmime.NET 具有185 个内置根证书,并会检查服务器证书是否已通过这些根权威机构(例如 Verisign)进行数字签名,证书的到期日期是否尚未过期,并且服务器的主机名是否与证书中的相同。通过此证书,客户端可以证明没有中间人攻击

阅读 Wikipedia 关于X.509 的介绍。

重要提示
如果您在发布项目几个月后(2014 年 1 月)下载了此项目,您应该更新证书,因为它们可能会过期、被吊销,或者可能存在新的根权威。

  1. 删除 Converter\RootCertificates\ 文件夹中的所有文件。
  2. 安装最新版本的 Mozilla Thunderbird
  3. 转到 工具 -> 选项 -> 高级 -> 查看证书。
  4. 点击每个证书,并将其导出为 PEM 文件到 Converter\RootCertificates\ 文件夹。
    (如果已存在同名证书文件,请为其指定另一个名称)
  5. 运行 Converter\Release\ 文件夹中的 Converter 工具,该工具读取这些证书文件并将它们写入一个单独的文本文件中。
  6. 重新编译项目,这将把此文本文件嵌入到二进制文件(DLL 或 EXE)的资源中。
  7. 确保 Visual Studio 在输出窗格中显示“编译资源...”!


Pop3

此代码枚举 POP3 收件箱中的所有电子邮件。

C#

using (Pop3 i_Pop3 = new Pop3(s_Server, u16_Port, e_Security, b_AllowInvalidCertificate))
{
    i_Pop3.SetAuthData(s_User, s_Password);

    int s32_EmailCount = i_Pop3.GetEmailCount();
    for (int M=0; M<s32_EmailCount; M++)
    {
        using (EmailParser i_Email = i_Pop3.FetchEmailAt(M))
        {
            // do something with the email...
        } // i_Email.Dispose()
    }
} // i_Pop3.Dispose()

C++

cPop3 i_Pop3(u16_Server, u16_Port, e_Security, b_AllowInvalidCertificate, IDR_ROOT_CA);
i_Pop3.SetAuthData(u16_User, u16_Password);

int s32_EmailCount = i_Pop3.GetEmailCount();
for (int M=0; M<s32_EmailCount; M++)
{
    GuardPtr<cEmailParser> i_Email = i_Pop3.FetchEmailAt(M);
    // do something with the email...
}
i_Pop3.Close(); // Close connection to server

注意
某些 POP3 服务器在端口 110 上无响应(例如 Gmail)或拒绝连接。
对于这些服务器,唯一可能的安全模式是SSL (端口 995)。

重要提示
Gmail 的 POP3 行为非常糟糕:无论您在 POP3/IMAP 配置中选择了什么设置,所有电子邮件只能通过 POP3 下载一次!电子邮件存在于 Web 界面中,但 pop.googlemail.com 会告诉您收件箱中没有邮件。或者,POP3 可能会显示您多年前已删除的邮件(但仅一次)!Gmail 的 IMAP 行为正常。

EmailParser

EmailParser 类允许提取电子邮件的任何部分。

C#

// ================== HEADER (POP3 command TOP) ==================

String s_Subject = i_Email.GetSubject(); // "vmime.NET Test Email"

// s_From[0] = "jmiller@gmail.com", 
// s_From[1] = "John Miller"
String[] s_From = i_Email.GetFrom(); 

int s32_Timezone; // deviation (+/-) from GMT in minutes
DateTime i_Date = i_Email.GetDate(out s32_Timezone);

String s_UserAgent = i_Email.GetUserAgent(); // "vmime.NET 0.9.2 (www.codeproject.com)"

List<String> i_Emails = new List<String>(); // "jmiller@gmail.com", "ccastillo@gmail.com", ...
List<String> i_Names  = new List<String>(); // "John Miller",       "Cristina Castillo",   ...
i_Email.GetTo(i_Emails, i_Names); // get all TO: recipients

// ================== Size (POP3 command LIST) ==================

UInt32 u32_Size = i_Email.GetSize(); // Email size in bytes

// ================== Uid (POP3 command UIDL) ==================

String s_UID = i_Email.GetUID();  // Unique identifier of this email on the server

// ================== Body (POP3 command RETR) ==================

String s_PlainText = i_Email.GetPlainText(); // "This is the plain message part..."
String s_HtmlText  = i_Email.GetHtmlText();  // "This is the <b>HTML</b> message part..."

UInt32 u32_ObjCount = i_Email.GetEmbeddedObjectCount(); // number of embedded objects in the HTML part

String  s_ObjId;   // "VmimeLogo"
String  s_ObjType; // "image/png"
Byte[]  u8_ObjData;
UInt32  u32_Object = 0;
while (i_Email.GetEmbeddedObjectAt(u32_Object++, out s_ObjId, out s_ObjType, out u8_ObjData))
{
    // do something with the embedded object
}

UInt32 u32_AttCount = i_Email.GetAttachmentCount(); // number of attachments

String  s_AttName;  // "Readme.txt"
String  s_AttType;  // "text/plain"
Byte[]  u8_AttData;
UInt32  u32_Attach = 0;
while (i_Email.GetAttachmentAt(u32_Attach++, out s_AttName, out s_AttType, out u8_AttData))
{
    // do something with the attachment
}

C++

 

See DemoCpp project.

 

注意
控制台使用 1252 代码页,无法显示中文。

性能

当您使用 i_Pop3.FetchEmailAt(M) 获取电子邮件时,仅下载邮件(POP3 命令“TOP”)。这速度很快,并且可以访问主题、发件人、收件人、抄送、日期、用户代理等头字段。

当您调用 i_Email.GetSize() 时,会向服务器发送 POP3 命令“LIST”以获取电子邮件的大小。

当您调用 i_Email.GetUID() 时,会向服务器发送 POP3 命令“UIDL”以获取电子邮件的唯一标识符。

当您访问电子邮件的正文部分时,POP3 命令“RETR”将检索整个电子邮件,如果电子邮件有几兆字节,可能会很慢。正文包含纯文本、HTML 文本、嵌入对象和附件。

因此,为了提高性能,您应该只在确实需要时访问正文部分。


IMAP

Imap 与 Pop3 具有相同的命令。
此外还有

C#

// Enumerate all folders on the server
String[] s_Folders = i_Imap.EnumFolders();

// Select the folder to retrieve emails from
i_Imap.SelectFolder("[Gmail]/Sent Mail");

// Get the current folder
String s_CurFolder = i_Imap.GetCurrentFolder();

C++

// Enumerate all folders on the server
void i_Imap.EnumFolders(vector<wstring>& i_FolderList);

// Select the folder to retrieve emails from
void i_Imap.SelectFolder(L"[Gmail]/Sent Mail");

// Get the current folder
wstring s_CurFolder = i_Imap.GetCurrentFolder();

正如您所见,IMAP 比 POP3 复杂得多。每次打开一个新文件夹时,都会与服务器建立一个新的连接。(IMAP (1) 是第一个,IMAP (2) 是第二个连接,依此类推……)

性能

这里也适用于 POP3,除了获取大小和 UID 不需要向服务器发送额外的命令。


删除电子邮件

以下代码显示了如何删除所有主题为“vmime.NET Test Email”的电子邮件,这些电子邮件是您之前通过 SMTP 演示发送给自己的。

C#

// i_Service = POP3 or IMAP
int s32_EmailCount = i_Service.GetEmailCount();

// Run the loop reverse to avoid M indexing an already deleted email
for (int M=s32_EmailCount-1; M>=0; M--)
{
    using (EmailParser i_Email = i_Service.FetchEmailAt(M))
    {
        if (i_Email.GetSubject() == "vmime.NET Test Email")
            i_Email.Delete(); // POP3: mark for deletion, IMAP: Delete now
    } // i_Email.Dispose()
}

i_Service.Close(); // POP3: Expunge all emails marked for deletion

C++

// i_Service = POP3 or IMAP
int s32_EmailCount = i_Service.GetEmailCount();

// Run the loop reverse to avoid M indexing an already deleted email
for (int M=s32_EmailCount-1; M>=0; M--)
{
    GuardPtr<cEmailParser> i_Email = i_Service.FetchEmailAt(M);

    if (i_Email->GetSubject() == L"vmime.NET Test Email")
        i_Email->Delete(); // POP3: mark for deletion, IMAP: Delete now
}

i_Service.Close(); // POP3: Expunge all emails marked for deletion

重要提示
POP3: 当您调用 i_Pop3.Close() 时,所有标记为删除的电子邮件将被永久清除。如果您不调用 Close(),它们将不会被删除!
IMAP: 当您调用 i_Email.Delete() 时,电子邮件会立即被删除。


下载 URL

以下是我下载用于构建项目的各种组件的 URL。
您无需下载它们。这仅供您参考

vmime

https://github.com/kisli/vmime
我使用 GitHub 工具检出了版本 0.9.2。

openssl
下载页:http://www.openssl.org/source (源代码)
下载页:http://slproweb.com/products/Win32OpenSSL.html (二进制文件)
源代码:http://www.openssl.org/source/openssl-1.0.1e.tar.gz
Win32 二进制文件:http://slproweb.com/download/Win32OpenSSL-1_0_1e.exe
Win64 二进制文件:http://slproweb.com/download/Win64OpenSSL-1_0_1e.exe

libgsasl
下载页:https://gnu.ac.cn/software/gsasl/#downloading
源代码:ftp://ftp.gnu.org/gnu/gsasl/gsasl-1.6.0.tar.gz
Win32 二进制文件:ftp://ftp.gnu.org/gnu/gsasl/gsasl-1.6.0-x86.zip
Win64 二进制文件:ftp://ftp.gnu.org/gnu/gsasl/gsasl-1.6.0-x64.zip

注意
ftp 服务器上的二进制文件 gsasl-1.6.1-x64.zipgsasl-1.8.0-x64.zip 编译错误(它们是 32 位,不是 64 位!)。

Mime 类型
http://svn.apache.org/repos/asf/tomcat/trunk/conf/web.xml

Visual Studio 2005

如果您使用 Visual Studio 2005,则必须安装VS Service Pack 1 才能使用此项目。否则,Intellisense 将永远挂起。此 bug 在 SP1 中已修复。请参阅MSDN

编译和依赖项

vmime.NET 可以编译为 x86 (Win32) 或 x64 (Win64),以及 Debug 或 Release。
有两个依赖项:GNU Sasl 和 OpenSsl。

文件 cCommon.cpp 包含 #pragma comment 命令,该命令根据当前项目设置告诉链接器包含哪些 Lib 文件。

GSASL 库由 libgsasl-7.dll 和 LIB 文件 libgsasl-7_32.lib 以及 libgsasl-7_64.lib 组成。
如果您要用另一个版本替换 DLL,则必须同时替换 Def 文件并运行 CreateLIB.bat 脚本重新创建 Lib 文件。此外,头文件也可能已更改。

OpenSSL 库可以动态编译(使用 ssleay32.dlllibeay32.dll)或静态编译(无需 DLL)。
运行 OpenSSL Windows 安装程序时,您会得到很多 LIB 文件。

动态编译(编译器开关 /MD):
OpenSSL-Win32\lib\VC\libeay32MD.lib (800 kB) // Release
OpenSSL-Win32\lib\VC\libeay32MDd.lib (800 kB) // Debug
OpenSSL-Win32\lib\VC\ssleay32MD.lib (70 kB) // Release
OpenSSL-Win32\lib\VC\ssleay32MDd.lib (70 kB) // Debug

静态编译(编译器开关 /MD):
OpenSSL-Win32\lib\VC\static\libeay32MD.lib (20 MB) // Release
OpenSSL-Win32\lib\VC\static\libeay32MDd.lib (20 MB) // Debug
OpenSSL-Win32\lib\VC\static\ssleay32MD.lib (4 MB) // Release
OpenSSL-Win32\lib\VC\static\ssleay32MDd.lib (4 MB) // Debug

对于编译器开关 /MT,也存在相同数量的 LIB 文件,但此开关不能在托管 C++ 项目 (vmime.NET.dll) 中使用。
并且还存在相同数量的64 位 LIB 文件。
因此,总共有 32 个 LIB 文件。

重要提示
如果您要用另一个版本替换 openssl,您必须验证头文件是否已更改。

编译 vmime.NET.dll

强烈建议静态链接 vmime.NET.dll,因为

  1. 您的软件安装程序需要少提供 2 个 DLL。
  2. openssl DLL 本身依赖于 VC++ 运行时 2008 (Msvcr90.dll)。

如果您在Visual Studio 2008 上编译,这无关紧要,因为 openssl 也是在 VS2008 上编译的,但在任何其他 Visual Studio 版本上都会有问题。

vmime 代码本身也依赖于 VC++ 运行时,但 C++ 运行时的版本将取决于您的 Visual Studio 版本。
例如,VS 2005 会创建对 Msvcp80.dll、Msvcr80.dll 和 Msvcm80.dll 的依赖。

那么,如果您在 VS 2005 上编译并动态链接到 ssleay32.dll 和 libeay32.dll,会发生什么?
那么您的软件将依赖于 Msvcp80.dll、Msvcr80.dll 和 Msvcm80.dll(由于 vmime),并且另外依赖于 ssleay32.dll 和 libeay32.dll 所需的 Msvcr90.dll。

这意味着您的软件用户必须安装两个 VC++ 运行时!

编译纯 C++ 项目

以上所有内容适用于 vmime.NET.dll,其中不能使用 /MT 编译器开关(Visual Studio 限制)。
但是,如果您在纯 C++ 项目中使用 vmime,您可以编译使用 /MT,并使用 openssl 库libeay32MT.libssleay32MT.lib
这将把 C++ 运行时静态链接到您的应用程序中,并且您不需要任何 MsvcXX.dll。

DependencyWalker

如果您不确定编译后的二进制文件依赖于哪些 DLL,请使用DependencyWalker 进行检查。

此屏幕截图显示了使用 VS2005 以 Release 模式、32 位编译,并使用 openssl 静态 LIB 文件时的依赖关系。

依赖项

项目
必需的 DLL
C++ 可再发行组件
纯 C++ 项目,使用 /MT,
openssl 静态 LIB 文件 xxxMT.lib
libgsasl-7.dll none
纯 C++ 项目,使用 /MD,
openssl 静态 LIB 文件 xxxMD.lib
libgsasl-7.dll 针对您的 VS 版本
纯 C++ 项目,使用 /MT,
openssl 动态 LIB 文件 xxxMT.lib
libgsasl-7.dll, ssleay32.dll, libeay32.dll 2008
纯 C++ 项目,使用 /MD,
openssl 动态 LIB 文件 xxxMD.lib
libgsasl-7.dll, ssleay32.dll, libeay32.dll 2008 和
针对您的 VS 版本

vmime.NET 项目,使用 /MD,
openssl 静态 LIB 文件 xxxMD.lib
libgsasl-7.dll 针对您的 VS 版本
vmime.NET 项目,使用 /MD,
openssl 动态 LIB 文件 xxxMD.lib
libgsasl-7.dll, ssleay32.dll, libeay32.dll 2008 和
针对您的 VS 版本

加粗的文件包含在 Codeproject 的下载中。

VC++ 运行时安装程序

编译器
已安装的 DLL
下载
VS 2005 - 32 位 MsVcr80.DLL, MsVcp80.DLL, MsVcm80.DLL 下载
VS 2005 - 64 位 下载
VS 2008 - 32 位 MsVcr90.DLL, MsVcp90.DLL, MsVcm90.DLL 下载
VS 2008 - 64 位 下载
VS 2010 - 32 位 MsVcr100.DLL, MsVcp100.DLL, MsVcm100.DLL 下载
VS 2010 - 64 位 下载
VS 2012 - 32 位 MsVcr110.DLL, MsVcp110.DLL, MsVcm110.DLL 下载
VS 2012 - 64 位

重要提示
Microsoft 的 VC++ 运行时安装程序不安装这些 DLL 的 Debug 版本。
在交付您的软件之前,您必须编译为Release 模式!

注意
如果目标计算机上没有安装这些 DLL,用户将不会收到一个说明问题的智能错误消息。相反,Microsoft .NET 会抛出最愚蠢的异常,例如:“vmime.NET.dll 或其依赖项之一无法加载”或“System.IO.FileLoadException:....由于应用程序配置不正确,应用程序启动失败。重新安装应用程序可能可以解决此问题”。您的应用程序用户永远不会怀疑原因是一个缺失的 MsVcXX.dll!

有两种方法可以确保 MsVcr/MsVcp/MsVcm DLL 在目标计算机上存在:

  1. 您的应用程序用户必须下载并安装正确版本的Visual C++ Redistributable Package(参见上表中的下载链接)。这些文件将“并排”安装到 C:\Windows\WinSxS\Cryptic Folder。
  2. 或者,您可以将以下文件直接安装到应用程序文件夹中作为私有程序集:Microsoft.VCXX.CRT.manifest, msvcrXX.dll, msvcpXX.dll, msvcmXX.dll。您可以在 Visual Studio 文件夹下的VC\Redist 中找到这些文件。这些 DLL 的版本必须与 vmime.NET.dll 中编译的清单中的版本完全相同。您可以阅读 MSDN 中的以下文章:Private AssembliesAssembly Searching Sequence,但您可能会感到非常困惑。

尝试这个工具,它可以检查已安装的 VC++ 运行时。

Elmü

© . All rights reserved.