Windows Mobile和Wince(Windows Embedded CE)下如何封装Native DLL提供给.NET Compact Framework进行调用

背景

在Windows Mobile和Wince(Windows Embedded CE)产品开发中,有时候会使用C++封装一些共用代码,例如在我们项目中使用了C++封装了一个对USB通信的公开代码库,这些共用代码编译成静态库,其他模式使用的时候,只需要include头文件,链接lib库就可以了,但是.NET Compact Framework的程序没有办法使用C++编译的静态库,所以产生封装Native DLL提供给.NET Compact Framework程序调用的需求。

 

简介

本文讲述在Windows Mobile和Wince(Windows Embedded CE)下如何封装Native DLL提供给.NET Compact Framework进行调用。

 

可选方案

在.NET framework下可以把C++编译成托管的DLL,这样.NET的程序可以直接调用这个DLL了,但是在.NET Compact Framework下,不支持managed c++,所以只能封装成Native的DLL,然后.NET Compact Framework P/Invoke该Native DLL。 封装Native的DLL有两个可选的方案。

 

方案一: .DEF文件

使用.DEF文件,.DEF文件可以定义输出接口

LIBRARY   NativeLib
EXPORTS
Insert @1
Delete @2
Member @3
Min @4

上面的.def文件定义了四个输出接口。如果使用.def文件,需要在编译选项进行配置,如下图:

native_dll2

使用.def文件麻烦的地方是每次更新接口都要手工更新.def文件,但是使用.def文件有一个好处是当dll发生更新以后,如果定义的ordinal没有发生改变的话,调用方不需要重新链接程序就可以使用新的DLL,Windows Mobile的今日组件就是使用.def文件进行定义的,需要实现ordinal为240和241两个接口:

InitializeCustomItem        @240    NONAME
CustomItemOptionsDlgProc @241 NONAME

这样shell32进程就可以加载实现了这两个接口的DLL,不需要重新连接来支持新的组件。

 

方案二: __declspec(dllexport)

我个人偏向于使用__declspec(dllexport)的方案,因为P/Invoke不存在调用方重新链接的问题,所以我偏向于使用__declspec(dllexport)的方案。

使用__declspec(dllexport)的方案,在需要输出的函数加入__declspec(dllexport)。这样该函数就会生成输出表。

#define EXPORT_API __declspec(dllexport)
EXPORT_API BOOL OpenIndicator()
{
return TRUE;
}

例如上述定义生成以下输出。使用Dumpbin /exports进行查看。

Microsoft (R) COFF/PE Dumper Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved.


Dump of file NativeLib.dll

File Type: DLL

Section contains the following exports for NativeLib.dll

00000000 characteristics
4AF757EA time date stamp Sun Nov 08 10:44:42 2009
0.00 version
1 ordinal base
1 number of functions
1 number of names

ordinal hint RVA name

1 0 0000BA30 ?OpenIndicator@@YAHXZ = ?OpenIndicator@@YAHXZ (int __c
decl OpenIndicator(void))

Summary

1000 .data
2000 .pdata
2000 .rdata
2000 .reloc
19000 .text

有没有发现输出是?OpenIndicator@@YAHXZ 而不是 OpenIndicator。

如果.NET Compact Framework的调用方如下:

[System.Runtime.InteropServices.DllImport("DeviceCommsLib.dll", SetLastError = true]
private static extern bool OpenIndicator();

程序执行的时候会抛出找不到入口点的异常,如下图:

native_dll1

原因是生成输出的时候使用C++的命名输出了?OpenIndicator@@YAHXZ 而不是 OpenIndicator。如果要解决这个问题,有两个办法:方法一,在调用方指定入口点:

[System.Runtime.InteropServices.DllImport("DeviceCommsLib.dll", SetLastError = true, EntryPoint = "?OpenIndicator@@YAHXZ")]
private static extern bool OpenIndicator();
这个办法不好,因为调用方需要使用Dumpbin /exports查看输出入口点。

 

 

 

方法二,增加extern "C",如下:

#define EXPORT_API __declspec(dllexport)
extern "C" {
EXPORT_API bool OpenIndicator()
{
return true;
}
}

这样使用了C的命名,使用Dumpbin /exports查看输出如下:

Microsoft (R) COFF/PE Dumper Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved.


Dump of file NativeLib.dll

File Type: DLL

Section contains the following exports for NativeLib.dll

00000000 characteristics
4AF75999 time date stamp Sun Nov 08 10:51:53 2009
0.00 version
1 ordinal base
1 number of functions
1 number of names

ordinal hint RVA name

1 0 0000BA30 OpenIndicator = OpenIndicator

Summary

1000 .data
2000 .pdata
2000 .rdata
2000 .reloc
19000 .text

输出接口变成OpenIndicator 了。

 

总结

总结一下,如果有Native C++的共用静态库需要提供给.NET Compact Framework调用,首先新建一个win32的DLL,把改静态库链接到这个DLL里面,然后定义DLL通过extern "C" __declspec(dllexport) 来输出出接口,最后在.NET Compact Framework程序就可以通过P/Invoke来调用原先在静态库中的功能模块来。

关于P/Invoke,我原先写过些文章可以参考。

.NET Compact Framework 下Win32 API P/Invoke 的使用

开发P/Invoke的工具与Website

作者:Jake LinJake's Blog on 博客园
出处:http://procoder.cnblogs.com

作品Jake Lin创作,采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。 任何转载必须保留完整文章,在显要地方显示署名以及原文链接。如您有任何疑问或者授权方面的协商,请给我留言
posted @ 2009-11-09 16:23 Jake Lin 阅读(2140) 评论(14) 编辑 收藏

 回复 引用 查看   
#1楼 2009-11-09 14:39 egmkang      
C++工程里面添加一个def文件,文件里面这么写:

LIBRARY "yourDllName"

EXPORTS
FunName @1
FunName1 @2



然后P/Invoke的时候就
[DllImport("yourDllName.dll")]
private extern static 返回类型 FunName(参数);

...

VS有一个工具叫Depends,在VS的安装目录里面一搜就搜出来了,可以看都有那些函数.

 回复 引用 查看   
#2楼 2009-11-09 14:41 egmkang      
我一直搞不明白为啥有一些导出函数的名字非常怪异,那些是咋搞的?
 回复 引用 查看   
#3楼 2009-11-09 15:13 Jack Fan      
很有帮助!
很实用的一篇文章。
 回复 引用   
#5楼 2009-11-09 15:38 dchenlong828[未注册用户]
引用egmkang:我一直搞不明白为啥有一些导出函数的名字非常怪异,那些是咋搞的?


为了防止重名,类比一下C++的函数编译之后改名吧,呵呵。

 回复 引用 查看   
#6楼 2009-11-09 16:41 OwnWaterloo      
引用egmkang:我一直搞不明白为啥有一些导出函数的名字非常怪异,那些是咋搞的?

mangled,为了支持函数重载。


 回复 引用 查看   
#7楼[楼主] 2009-11-09 16:44 Jake Lin      
引用egmkang:
C++工程里面添加一个def文件,文件里面这么写:

LIBRARY "yourDllName"

EXPORTS
FunName @1
FunName1 @2



然后P/Invoke的时候就
[DllImport("yourDllName.dll")]
private extern static 返回类型 FunName(参数);

...

VS有一个工具叫Depends,在VS的安装目录里面一搜就搜出来了,可以看都有那些函数.

depends有些windows mobile下的dll不能看的,例如依赖于Coredll.dll。需要dumpbin

 回复 引用 查看   
#8楼[楼主] 2009-11-09 16:46 Jake Lin      
@dchenlong828
@egmkang
dchenlong828说的对,因为导出C++会有重名的情况,而且不同版本的编译器,编译出来名字都不一样。

 回复 引用 查看   
#9楼 2009-11-09 16:47 egmkang      
@Jake Lin
还有这样的??我还没发现.
依赖Coredll.dll的Dll打开的时候,只是会提示没找到Coredll.dll.

 回复 引用 查看   
#10楼[楼主] 2009-11-09 16:47 Jake Lin      
@别爱上哥,哥只是个传说!
@Jack Fan
谢谢!

 回复 引用 查看   
#11楼[楼主] 2009-11-09 17:36 Jake Lin      
引用egmkang:
@Jake Lin
还有这样的??我还没发现.
依赖Coredll.dll的Dll打开的时候,只是会提示没找到Coredll.dll.

是的,我就是说这个,这样就看不到依赖的库有那些输出了。

 回复 引用 查看   
#12楼 2009-11-09 21:01 施炯      
提供的链接很有用,受教了,谢谢!
 回复 引用 查看   
#13楼 2009-11-10 09:30 peterzb      
很不错, 学习一下!
 回复 引用 查看   
#14楼 2009-12-17 09:13 Jack Fan      
今天又拜读了这篇文章,受益颇深!