C# 引用 C++ 链接库
文章目录
最近在尝试用新的方式来写一个新的 Windows 客户端,当作练习。主要是用 C++ 把复杂的算法写成链接库,然后用 C# 做界面前端,从 C# 调用用 C++ 写好的非托管代码。
尝试通过这种方式解决两个问题:
- C# 的计算效率问题,虽然从各种资料来看,好像 C# 的托管代码对效率的影响并没有想象的夸张,但不管怎么样,有些东西用 C++ 写就是方便一些;
- 用 C++ 可以方便地链接现有的代码库、算法库。
其实,这种架构可以看成是一种 C/S 架构的简化版,但是省去了 Socket 通信这一层。
我采用的是最简单的 P/Invokes 的方式来实现 C# 调用 C++ 链接库,详细的教程,可以看一下 Using dumpbin.exe as an Aid for Declaring P/Invokes 这篇文章。
这里先简单说说两点和代码无关的问题
- 如果 C++ 链接库的计算很耗时,一定要在 C# 客户端里开一个线程来处理,否则容易造成死机,这和 MFC 之类的原理一样。
- 为了测试你从 C# 链接 C++ 链接库是否成功,可以用 C# 新建一个命令行工程专门测试,用这种方式来测试更加直接与有效。
再谈几点有关技术实现细节的问题,也是我折腾了很久的困惑之处
1. 有关 C++ 链接库 EntryPoint 的名称
<div class="outline-text-4">
<p>
我在刚开始从 C# 里链接 C++ 链接库的 API 时,想当然地以为就是函数名称。但是这样操作无论如何也调用不成功,<a href="http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx#pinvoke_callingdllexport">需要在 C++ 链接库里添加一个 extern 关键字</a>,否则链接库编译出来的 API 名称,是混淆过的,不方便你在 C# 里作为 EntryPoint 来书写。
</p>
<p>
比如说,有如下 C++ 链接库的 API 函数(建议链接库给外面调用的 API 最好用 C 风格来实现,方便减少头文件依赖关系):
</p>
<pre>
extern “C” __declspec(dllexport) bool Function1(const char* param1, const char* param2, const char* param3);
<p>
翻译成 C# 函数则如下:
</p>
<pre>
[DllImport(“Example.dll”, EntryPoint = “Function1”, ExactSpelling = false] [return: MarshalAs(UnmanagedType.U1)] public static extern bool Function1([MarshalAs(UnmanagedType.LPStr)] String param1, [MarshalAs(UnmanagedType.LPStr)] String param2, [MarshalAs(UnmanagedType.LPStr)] String param3);
<p>
这里注意,找 EntryPoint 一定要准确,否则不容易找到。为了明确地找到 API 函数的 EntryPoint 名称,可以使用 Dumpbin.exe 工具。
</p>
<p>
dumpbin.exe 工具默认在以下目录:
</p>
<pre>
C:\Program Files\Microsoft Visual Studio 9.0\VC\bin
<p>
如果从这个目录里运行 dumpbin.exe 会提示找不到动态链接库 mspdb80.dll 的错误,可以把 dumpbin.exe 拷贝到目录
</p>
<pre>
C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE
<p>
,并从这个目录下运行 dumpbin.exe 来解决这个问题。
</p>
<p>
运行命令
</p>
<pre>
dumpbin.exe /EXPORTS dllname.dll
<p>
后,你会看到很多 ? @ 混合在一起的名称,所以,为了使你在 C# 里的代码可读性比较强,需要改造这些名称。在链接库里,我们可以通过用 extern 关键字来标明,这样生成的链接库 EntryPoint 依然会是原始的名称。
</p></p>
</div></p>
</div>
<div class="outline-4">
<h4 id="sec-1.2.2">
2. C# 程序运行时提示说找不到链接库
</h4>
<div class="outline-text-4">
<p>
如果按上述方法编写好了代码,一运行 C# 程序却提示说:
</p>
<pre>
未处理的"System.DllNotFoundException"类型的异常出现在 example.exe 中。
其他信息: 无法加载 DLL"cppexample.dll": 找不到指定的模块。 (异常来自 HRESULT:0x8007007E)。
<p>
这个时候,你需要找一找你的 dll 是否在可执行目录下,或是你写的 dll 是否依赖于其它第三方dll,一定要确保所有的 dll 都能顺利被找到。
</p></p>
</div></p>
</div>
<div class="outline-4">
<h4 id="sec-1.2.3">
3. 参数的映射办法
</h4>
<div class="outline-text-4">
<p>
你的函数肯定有若干个参数,那这些参数应该和 C# 里的类型如何一一对应呢?在 C# 里,<a href="http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx#pinvoke_defaultmarshaling">这种映射关系叫做 marshal</a>。
</p>
<p>
类型映射,需要仔细检查一下手册,比如说,const char* 就应该这样映射:
</p>
<pre>
[MarshalAs(UnmanagedType.LPStr)]
<p>
<a href="http://msdn.microsoft.com/en-us/library/2x8kf7zx(v=vs.80).aspx">Using C++ Interop</a> 文章的未尾,有列出一大串的类型映射列表。
</p></p>
</div></p>
</div></p>
小结
<div class="outline-text-3">
<p>
初步用 C# 来写界面,感觉更方便、快速,起码比 MFC 来得简单、直接;从 C# 里直接调用 C++ 链接库,也很方便。但这两者结合起来写应用,稳定性还有待进一步测试。
</p></p>
</div></p>
文章作者 cookwhy
上次更新 2012-02-03