使用.NET平台调用服务访问非托管 DLL 中的函数
概述:描述在Microsoft.Net框架下使用Win32API的方法。
关键字:平台调用,Invoke,DllImport。
一.背景
最近公司在做一个面向电信小灵通用户的SP信息服务软件,其中需要和省电信公司的短信网关通信,对方提供了一个开发包SmGwAPI.DLL(win32动态链接库)和一些文档,由于实在不想使用VC或DEPHI来开发该项目,所以最终决定使用dotNET平台调用技术来使用该API。
二.准备知识
先了解一下Win32动态链接库在VC里是如何定义的:
extern "C" __declspec(dllexport) int Validate(const char *);
extern "C" 表示生成的DLL中的函数名称与定义时一致,否则就会生成形如
?MyMessage@@YAHPBDZ@Z 类似的函数名称。
导入上面的函数:
using System.Runtime.InteropServices;
[DllImport("Register.dll")]
public extern static int Validate(string str1);
导入系统函数:
using System.Runtime.InteropServices;
[DllImport("user32.dll")]
public static extern int MessageBox(int hWnd, String text, String caption, uint type);
三.平台调用
1. DllImport
先看一个示例:
[DllImport("KERNEL32.DLL", EntryPoint="MoveFileW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern bool MoveFile(String src, String dst);
EntryPoint:指定函数入口,可以用来重新命名函数。(DumpBin.EXE)
[DllImport("Register.dll" EntryPoint="?MyMessage@@YAHPBDZ@Z ")]
public extern static int MyMessage (string str1);
ExactSpelling与CharSet:名称匹配。
在 DllImportAttribute.ExactSpelling 字段为 true 时(Visual Basic .NET 中默认值为true),平台调用将只搜索您指定的名称。例如,如果指定 MessageBox,则平台调用将搜索 MessageBox,如果它找不到完全相同的拼写则失败。
当 ExactSpelling 字段为 false(它是 C++ 托管扩展和 C# 中的默认值),平台调用将首先搜索指定的别名 (MessageBox),如果没有找到未处理的别名,则将搜索已处理的名称。当CharSet=CharSet.Ansi时搜索MessageBoxA,找不到则失败;当CharSet= CharSet.Unicode时搜索MessageBoxW,找不到则失败;当CharSet=CharSet.Auto 平台调用在运行时根据目标平台在 ANSI 和 Unicode 格式之间进行选择。
2. 参数传递
先看原函数的定义(已简化)
int __stdcall SMGPSendSingle(
const int nNeedReply,
const char *sServiceID,
char *sMsgID,
int *nErrorCode);
(1)
[DllImport("SmGwAPI.Dll")]
public extern static int SMGPSendSingle(
int nNeedReply,
string sServiceID,
StringBuilder sMsgID,
ref int nErrorCode );
(2)
[DllImport("SmGwAPI.Dll")]
public extern static int SMGPSendSingle(
int nNeedReply,
string sServiceID,
[MarshalAs(UnmanagedType.LPArray)] byte[] sMsgID,
ref int nErrorCode );
如果sMsgID返回的内容是ASCII码或UNICODE码的字符串,那么使用以上两种定义均可获得正确结果,如果sMsgID返回的内容是二进制码、BCD码等,就必须使用第二种定义方式,才能正确获得返回信息。
3. 结构
原定义(已简化)
typedef struct
{
unsigned int nIsReport;
char sMsgID[10+1];
char sMsgContent[252+1];
}DeliverResp;
SMGPAPI_EXPORTS SMGPDeliver(const int nTimeoutIn, DeliverResp *pDeliverResp);
封装处理:
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Ansi)]
public struct DeliverResp
{
public uint nIsReport;
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=11)]
public string sMsgID;
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=253)]
public string sMsgContent;
}
[DllImport("SmGwAPI.Dll")]
public extern static int SMGPDeliver(int nTimeoutIn, ref DeliverResp pDeliverResp);
如果sMsgID返回的内容是ASCII码或UNICODE码的字符串,那么使用上面定义可获得正确结果,如果sMsgID返回的内容是二进制码、BCD码等,就必须使用下面的定义方式,才能正确获得返回信息。
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Ansi)]
public struct DeliverResp
{
public uint nIsReport;
[MarshalAs(UnmanagedType.ByValArray,SizeConst=11 )]
public byte[] sMsgID;
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=253)]
public string sMsgContent;
}
[DllImport("SmGwAPI.Dll")]
public extern static int SMGPDeliver(int nTimeoutIn, ref DeliverResp pDeliverResp);
4. 参数对应表
C/C++ |
C# |
Int, Long |
int |
Int * |
Ref int |
LPCSTR, const char * |
[MarshalAs(UnmanagedType.LPSTR)] string |
LPCTSTR, const TCHAR * |
[MarshalAs(UnmanagedType.LPTSTR)] string |
LPSTR, char * |
[MarshalAs(UnmanagedType.LPSTR)] stringBuilder |
LPTSTR, TCHAR * |
[MarshalAs(UnmanagedType.LPTSTR)] stringBuilder |
Byte [n] str |
[MarshalAs(UnmanagedType.LPArray)] byte[]str |
WORD |
uInt16 |
Byte, unsigned char |
byte |
Short |
Int16 |
float |
single |
double |
double |
DWORD, unsigned long, Ulong |
[MarshalAs(UnmanagedType.U4)] UInt32 |
bool |
bool |
HANDLE, LPDWORD, LPVOID, void* |
IntPtr |
NULL pointer |
IntPtr.Zero |