首页 新闻 会员 周边

c# 调用 c++函数 问题

0
悬赏园豆:10 [已解决问题] 解决于 2013-05-21 12:27

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 签名的调用约定和参数与非托管的目标签名是否匹配。

 

求帮助!

james.dong的主页 james.dong | 初学一级 | 园豆:91
提问于:2013-05-20 16:09
< >
分享
最佳答案
1

这样定义是不行的,.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(都是常用类型),不用在进行类型转换了。

 

希望能对你有帮助 ;)

收获园豆:10
拾玄 | 菜鸟二级 |园豆:439 | 2013-05-20 20:04

感谢楼主的帮助,我测试了一下,还是错误:

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 | 园豆:91 (初学一级) | 2013-05-20 20:36

@james.dong:

你把定义改成这样试试:

public static extern unsafe int RunService(byte* msgType, byte* version, byte* inp , UInt32 inLen, byte* outp, byte* outLen);
拾玄 | 园豆:439 (菜鸟二级) | 2013-05-20 20:41

@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;
}


(如果还不行麻烦和我说一声,我后面调通后再发一份可用的代码上来)

拾玄 | 园豆:439 (菜鸟二级) | 2013-05-20 21:01

@拾玄: 

我用原来的定义测试了一下,还是错误:我截了个图,你看看:

james.dong | 园豆:91 (初学一级) | 2013-05-21 09:09

@拾玄: 

测试了很多方法;最后我在定义的参数中加了这个 属性 ,就成功了。

[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 | 园豆:91 (初学一级) | 2013-05-21 11:31

@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.

拾玄 | 园豆:439 (菜鸟二级) | 2013-05-21 12:06

@拾玄: 

但是 用了这个属性后,发现 对参数的检测不是很严格了(比如:char*型的,我int型的也不报错)。不知道是好还是坏。

 

非常感谢 拾玄兄的帮助啊!

james.dong | 园豆:91 (初学一级) | 2013-05-21 12:21
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册