c++ 的DLL 文件中定义了一个 函数:
int RunService(char *msgType, char *version, char *in, size_t inLen, char *out, size_t *outLen);
参数说明:
msgType:输入参数
version:输入参数
in:输入参数
inLen:输入参数
out:是输出参数
outLen:是输出参数
我在 c# 如下定义:
[DllImport("XXX.dll")]
public static extern int RunService(string msgType, string version, string inp , UInt32 inLen, StringBuilder outp, out UInt32 outLen);
请问这样定义 可以吗?
我在测试这个函数时报如下错误:
对 PInvoke 函数“HISShell!HISShell.CallYBDll::RunService”的调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配。
求帮助!
这样定义是不行的,.net对PInvoke调用的参数需要使用结构类型(struct),string是类类型(class)。
(c/c++)函数中的指针类型参数可用结构指针对应,如本题可这样定义:
[DllImport("XXX.dll")] public static extern unsafe int RunService(byte* msgType, byte* version, byte* inp , UInt32 inLen, byte* outp, UInt32* outLen);
(注意unsafe,编译时要打开unsafe选项)
.net的对指针操作时值要在fixed域下执行,所以本题调用时可以这样:
public unsafe string CallRunService(string msgType, string version, string inp , UInt32 inLen, StringBuilder outp, out UInt32 outLen) { byte[] msgTypeBytes=Encoding.ASCII.GetBytes(msgType); byte[] versionBytes=Encoding.ASCII.GetBytes(version); byte[] inpBytes=Encoding.ASCII.GetBytes(inp); string result=null; fixed(byte* _msg=msgTypeBytes,_ver=versionBytes,_inp=inpBytes,_out=new byte[0],_outLen=new byte[0]) {
//call PInvoke RunService(_msg,_ver,_inp,inLen,_out,_outLen); UInt32 * _outLenUInt=(UInt32*)_outLen; outLen=_outLenUInt[0]; List<byte> outpList=new List<byte>(outLen); for(int i=0;i<outLen;i++) { outpList.add(_out[i]); } result=Encoding.ASCII.GetBytes(outpList.ToArray()); } outp.Append(result); return result; }
(这是根据以往我的写法写的,没经过debug,不保证能直接运行;但大体思路就是这样)
封装上这一层以后,其他地方就可以直接调用在CallRunService(都是常用类型),不用在进行类型转换了。
希望能对你有帮助 ;)
感谢楼主的帮助,我测试了一下,还是错误:
fixed(byte* _msg=msgTypeBytes,_ver=versionBytes,_inp=inpBytes,_out=new byte[0],_outLen=new byte[0])
public static extern unsafe int RunService(byte* msgType, byte* version, byte* inp , UInt32 inLen, byte* outp, UInt32* outLen);
红色的地方 定义不一致,编译通不过,我把它 改成 byte* 了,运行后,还是原来的错误。
求楼主 帮忙!
@james.dong:
你把定义改成这样试试:
public static extern unsafe int RunService(byte* msgType, byte* version, byte* inp , UInt32 inLen, byte* outp, byte* outLen);
@james.dong:
汗,上面那种你已经试过了。
那就按原来的定义使用这个调用方式:
public unsafe string CallRunService(string msgType, string version, string inp , UInt32 inLen, StringBuilder outp, out UInt32 outLen) { byte[] msgTypeBytes=Encoding.ASCII.GetBytes(msgType); byte[] versionBytes=Encoding.ASCII.GetBytes(version); byte[] inpBytes=Encoding.ASCII.GetBytes(inp); string result=null; fixed(byte* _msg=msgTypeBytes,_ver=versionBytes,_inp=inpBytes,_out=new byte[0]) { fixed(UInt32 * _outLenUInt=new UInt32[0]) { //call PInvoke RunService(_msg,_ver,_inp,inLen,_out,_outLenUInt); outLen=_outLenUInt[0]; } List<byte> outpList=new List<byte>(outLen); for(int i=0;i<outLen;i++) { outpList.add(_out[i]); } result=Encoding.ASCII.GetBytes(outpList.ToArray()); } outp.Append(result); return result; }
(如果还不行麻烦和我说一声,我后面调通后再发一份可用的代码上来)
@拾玄:
我用原来的定义测试了一下,还是错误:我截了个图,你看看:
@拾玄:
测试了很多方法;最后我在定义的参数中加了这个 属性 ,就成功了。
[DllImport("HisYbDll.dll", CallingConvention= CallingConvention.Cdecl)]
public static extern int RunService(string msgType, string version, string inp, UInt32 inLen, StringBuilder outp, ref UInt32 outLen);
不知道这个 属性 是什么意思。????
@james.dong:
你也测成功啦!
我刚用c/c++模拟了一下这个函数,然后用PInvoke调用。
extern "C" _declspec(dllexport) int RunService(char *msgType, char *version, char *in, size_t inLen, char *out, size_t *outLen) { out[0]='h'; out[1]='e'; out[2]='l'; out[3]='l'; out[4]='o'; *outLen=5; return 0; }
[DllImport("XXX.dll")] public static extern unsafe int RunService(byte* msgType, byte* version, byte* inp, UInt32 inLen, byte* outp, UInt32* outLen); public static unsafe string CallRunService(string msgType, string version, string inp, UInt32 inLen, StringBuilder outp, out UInt32 outLen) { byte[] msgTypeBytes = Encoding.ASCII.GetBytes(msgType); byte[] versionBytes = Encoding.ASCII.GetBytes(version); byte[] inpBytes = Encoding.ASCII.GetBytes(inp); string result = null; fixed (byte* _msg = msgTypeBytes, _ver = versionBytes, _inp = inpBytes, _out = new byte[1024]) { fixed (UInt32* _outLenUInt = new UInt32[1]) { //call PInvoke RunService(_msg, _ver, _inp, inLen, _out, _outLenUInt); outLen = _outLenUInt[0]; } List<byte> outpList = new List<byte>((int)outLen); for (int i = 0; i < outLen; i++) { outpList.Add(_out[i]); } result = Encoding.ASCII.GetString(outpList.ToArray()); } outp.Append(result); return result; }
结果发现在2.0中调用PInvoke没问题,在4.0中就会出现你截图显示的那个错误;然后又试了一下4.0掉用2.0所封装好的 CallRunService 也没问题。
结合你所得的结果来推论的话,应该是 4.0中PInvoke对参数的默认处理和2.0的不一样 ,CallingConvention= CallingConvention.Cdecl 这个参数应该就是使用2.0的参数处理的方式进行调用PInvoke.
@拾玄:
但是 用了这个属性后,发现 对参数的检测不是很严格了(比如:char*型的,我int型的也不报错)。不知道是好还是坏。
非常感谢 拾玄兄的帮助啊!