C#调用C++的DLL传递数组问题求教

时间:2021-12-23 17:35:55
C++中DLL代码
extern "C" _declspec(dllexport) void Test1wei(LPTSTR[], int, LPCTSTR&);
void Test1wei(LPTSTR ary[], int size, LPCTSTR& msg)
{
//一维数组传递是引用传递
CString sss = L"送入:";
CString ssss = L"修改:";
for (int i = 0; i < size; i++)
{
sss.Format(L"%s %s", sss, ary[i]);
CString s;
s.Format(L"B%d", i);
ary[i] = (LPTSTR)(LPCTSTR)s;

ssss.Format(L"%s %s", ssss, ary[i]);
}
AfxMessageBox(msg);
msg = L"return msg";
AfxMessageBox(sss);
AfxMessageBox(ssss);
}

c#中代码
[DllImportAttribute("Leo", CharSet = CharSet.Unicode, EntryPoint = "Test1wei")] 
        private static extern int Test1wei([MarshalAs(UnmanagedType.LPArray)] string[] ary, int size, ref string msg);
 
           string[] ary = new string[5];            
            string msg="hello1wei";
            Test1wei(ary, 5, ref msg);
            String tmpsss = "调用后:";
            for (int i = 0; i < 5; i++)
            {
                tmpsss += "\n" + ary[i];
            }
            MessageBox.Show(tmpsss+"\n"+msg);

现在确定C#部分代码有问题,调用后并没有修改到数组 ary[i],并且调试时有错误出现.如下:

托管调试助手“PInvokeStackImbalance”在“D:\temp\test\bin\Debug\test.vshost.exe”中检测到问题。

其他信息: 对 PInvoke 函数“test!test.MainForm::Test1wei”的调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配。

如有适用于此异常的处理程序,该程序便可安全地继续运行。


哪位帮忙解答一下,困惑几天了还没找到方法.这个dll用vc++调用是正常的
希望运行后能报:
调用后:
B0
B1
B2
B3
B4

14 个解决方案

#1


LPCTSTR& msg这里修改的是传入的指针的地址所以第三个参数应该用intptr
C#里面也没有本地指针的数组这个类型,第一个参数要在本地堆上划块内存出来存这些指针,然后用intptr传内存地址。

#2


引用 1 楼 蒋晟的回复:
LPCTSTR& msg这里修改的是传入的指针的地址所以第三个参数应该用intptr
C#里面也没有本地指针的数组这个类型,第一个参数要在本地堆上划块内存出来存这些指针,然后用intptr传内存地址。

测试第三个参数没问题,只有第一个参数出问题
有时间可以帮忙改一下吗?谢谢

#3


目的就是传数组到dll进行处理后再返回,c++和c#部分代码都可以改

#4


如果传入的字符串要修改,使用stringbuilder来对应
这个数组应该是使用者来申请,
所以需要根据字符长短先初始化stringbuilder。

#5


引用 4 楼 xian_wwq 的回复:
如果传入的字符串要修改,使用stringbuilder来对应
这个数组应该是使用者来申请,
所以需要根据字符长短先初始化stringbuilder。


        [DllImportAttribute("Leo", CharSet = CharSet.Unicode, EntryPoint = "Test1wei")]  //, CallingConvention = CallingConvention.Cdecl)]
        private static extern int Test1wei(StringBuilder[] ary, int size, ref string msg);    //[MarshalAs(UnmanagedType.LPTStr)] 
            StringBuilder[] ary = new StringBuilder[5];
            for (int i = 0; i < 5; i++)
            {
                ary[i] = new StringBuilder();
                ary[i].Append(i.ToString());
            }
            string msg="hello1wei";
            Test1wei(ary, 5, ref msg);
            String tmpsss = "调用后:";
            for (int i = 0; i < 5; i++)
            {
                tmpsss += "\n" + ary[i].ToString();
            }
            MessageBox.Show(tmpsss+"\n"+msg);

这样子也没在dll内修改到

#6


ary[i] = (LPTSTR)(LPCTSTR)s;

不要用这种写法
用memcpy/strcpy

#7


引用 6 楼 shingoscar 的回复:
ary[i] = (LPTSTR)(LPCTSTR)s;

不要用这种写法
用memcpy/strcpy



//ary[i] = (LPTSTR)(LPCTSTR)s; //CString类型转换为LPTSTR方式一
ary[i] = new TCHAR[s.GetLength() + 1]; lstrcpy(ary[i], s); //CString类型转换为LPTSTR方式二
//ary[i] = s.GetBuffer(); s.ReleaseBuffer(); //CString类型转换为LPTSTR方式三

这三种方式都一样效果

#8


引用 7 楼 iuspace 的回复:

//ary[i] = (LPTSTR)(LPCTSTR)s; //CString类型转换为LPTSTR方式一
ary[i] = new TCHAR[s.GetLength() + 1]; lstrcpy(ary[i], s); //CString类型转换为LPTSTR方式二
//ary[i] = s.GetBuffer(); s.ReleaseBuffer(); //CString类型转换为LPTSTR方式三

这三种方式都一样效果

智商堪忧啊
直接用lstrcpy(ary[i], s);
你从C#传进来的时候,ary已经包含了一个StringBuilder
这样写ary[i] = xxx等于把传入的buffer给扔了!

#9


引用 8 楼 shingoscar 的回复:
Quote: 引用 7 楼 iuspace 的回复:


//ary[i] = (LPTSTR)(LPCTSTR)s; //CString类型转换为LPTSTR方式一
ary[i] = new TCHAR[s.GetLength() + 1]; lstrcpy(ary[i], s); //CString类型转换为LPTSTR方式二
//ary[i] = s.GetBuffer(); s.ReleaseBuffer(); //CString类型转换为LPTSTR方式三

这三种方式都一样效果

智商堪忧啊
直接用lstrcpy(ary[i], s);
你从C#传进来的时候,ary已经包含了一个StringBuilder
这样写ary[i] = xxx等于把传入的buffer给扔了!


目的就是传入的字符串数组根据情况再重写赋值
比如传入ary[0]="china",则修改为ary[0]="中国"

#10



[DllImportAttribute("Leo", CharSet = CharSet.Unicode, EntryPoint = "Test1wei",  CallingConvention=CallingConvention.Cdecl)] 
统一调用约定

#11


引用 9 楼 iuspace 的回复:
目的就是传入的字符串数组根据情况再重写赋值
比如传入ary[0]="china",则修改为ary[0]="中国"

我的意思就是你直接调用strcpy,不要写ary[x] = xxx

#12


引用 10 楼 lc316546079 的回复:
[DllImportAttribute("Leo", CharSet = CharSet.Unicode, EntryPoint = "Test1wei",  CallingConvention=CallingConvention.Cdecl)] 
统一调用约定


加了也不行

#13


还没解决

#14





引用 12 楼 iuspace 的回复:
Quote: 引用 10 楼 lc316546079 的回复:


[DllImportAttribute("Leo", CharSet = CharSet.Unicode, EntryPoint = "Test1wei",  CallingConvention=CallingConvention.Cdecl)] 
统一调用约定


加了也不行


"请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配。"
1,调用约定错了
2,参数错了

我不是让你直接加那一句,而是让你自己将c#与c++的俩 调用约定统一,该用啥用啥,这样来确定是否是调用约定问题;

至于参数问题,说实话,我写dll的时候,绝对不会弄这样的参数。。。一个数组(性能问题),一个引用,如果可以改建议将那函数的参数改成基本数据结构,有助于方便交互同时性能也更好。。。。
手上没windows就没法帮你调试了。。。只能说多尝试了

#1


LPCTSTR& msg这里修改的是传入的指针的地址所以第三个参数应该用intptr
C#里面也没有本地指针的数组这个类型,第一个参数要在本地堆上划块内存出来存这些指针,然后用intptr传内存地址。

#2


引用 1 楼 蒋晟的回复:
LPCTSTR& msg这里修改的是传入的指针的地址所以第三个参数应该用intptr
C#里面也没有本地指针的数组这个类型,第一个参数要在本地堆上划块内存出来存这些指针,然后用intptr传内存地址。

测试第三个参数没问题,只有第一个参数出问题
有时间可以帮忙改一下吗?谢谢

#3


目的就是传数组到dll进行处理后再返回,c++和c#部分代码都可以改

#4


如果传入的字符串要修改,使用stringbuilder来对应
这个数组应该是使用者来申请,
所以需要根据字符长短先初始化stringbuilder。

#5


引用 4 楼 xian_wwq 的回复:
如果传入的字符串要修改,使用stringbuilder来对应
这个数组应该是使用者来申请,
所以需要根据字符长短先初始化stringbuilder。


        [DllImportAttribute("Leo", CharSet = CharSet.Unicode, EntryPoint = "Test1wei")]  //, CallingConvention = CallingConvention.Cdecl)]
        private static extern int Test1wei(StringBuilder[] ary, int size, ref string msg);    //[MarshalAs(UnmanagedType.LPTStr)] 
            StringBuilder[] ary = new StringBuilder[5];
            for (int i = 0; i < 5; i++)
            {
                ary[i] = new StringBuilder();
                ary[i].Append(i.ToString());
            }
            string msg="hello1wei";
            Test1wei(ary, 5, ref msg);
            String tmpsss = "调用后:";
            for (int i = 0; i < 5; i++)
            {
                tmpsss += "\n" + ary[i].ToString();
            }
            MessageBox.Show(tmpsss+"\n"+msg);

这样子也没在dll内修改到

#6


ary[i] = (LPTSTR)(LPCTSTR)s;

不要用这种写法
用memcpy/strcpy

#7


引用 6 楼 shingoscar 的回复:
ary[i] = (LPTSTR)(LPCTSTR)s;

不要用这种写法
用memcpy/strcpy



//ary[i] = (LPTSTR)(LPCTSTR)s; //CString类型转换为LPTSTR方式一
ary[i] = new TCHAR[s.GetLength() + 1]; lstrcpy(ary[i], s); //CString类型转换为LPTSTR方式二
//ary[i] = s.GetBuffer(); s.ReleaseBuffer(); //CString类型转换为LPTSTR方式三

这三种方式都一样效果

#8


引用 7 楼 iuspace 的回复:

//ary[i] = (LPTSTR)(LPCTSTR)s; //CString类型转换为LPTSTR方式一
ary[i] = new TCHAR[s.GetLength() + 1]; lstrcpy(ary[i], s); //CString类型转换为LPTSTR方式二
//ary[i] = s.GetBuffer(); s.ReleaseBuffer(); //CString类型转换为LPTSTR方式三

这三种方式都一样效果

智商堪忧啊
直接用lstrcpy(ary[i], s);
你从C#传进来的时候,ary已经包含了一个StringBuilder
这样写ary[i] = xxx等于把传入的buffer给扔了!

#9


引用 8 楼 shingoscar 的回复:
Quote: 引用 7 楼 iuspace 的回复:


//ary[i] = (LPTSTR)(LPCTSTR)s; //CString类型转换为LPTSTR方式一
ary[i] = new TCHAR[s.GetLength() + 1]; lstrcpy(ary[i], s); //CString类型转换为LPTSTR方式二
//ary[i] = s.GetBuffer(); s.ReleaseBuffer(); //CString类型转换为LPTSTR方式三

这三种方式都一样效果

智商堪忧啊
直接用lstrcpy(ary[i], s);
你从C#传进来的时候,ary已经包含了一个StringBuilder
这样写ary[i] = xxx等于把传入的buffer给扔了!


目的就是传入的字符串数组根据情况再重写赋值
比如传入ary[0]="china",则修改为ary[0]="中国"

#10



[DllImportAttribute("Leo", CharSet = CharSet.Unicode, EntryPoint = "Test1wei",  CallingConvention=CallingConvention.Cdecl)] 
统一调用约定

#11


引用 9 楼 iuspace 的回复:
目的就是传入的字符串数组根据情况再重写赋值
比如传入ary[0]="china",则修改为ary[0]="中国"

我的意思就是你直接调用strcpy,不要写ary[x] = xxx

#12


引用 10 楼 lc316546079 的回复:
[DllImportAttribute("Leo", CharSet = CharSet.Unicode, EntryPoint = "Test1wei",  CallingConvention=CallingConvention.Cdecl)] 
统一调用约定


加了也不行

#13


还没解决

#14





引用 12 楼 iuspace 的回复:
Quote: 引用 10 楼 lc316546079 的回复:


[DllImportAttribute("Leo", CharSet = CharSet.Unicode, EntryPoint = "Test1wei",  CallingConvention=CallingConvention.Cdecl)] 
统一调用约定


加了也不行


"请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配。"
1,调用约定错了
2,参数错了

我不是让你直接加那一句,而是让你自己将c#与c++的俩 调用约定统一,该用啥用啥,这样来确定是否是调用约定问题;

至于参数问题,说实话,我写dll的时候,绝对不会弄这样的参数。。。一个数组(性能问题),一个引用,如果可以改建议将那函数的参数改成基本数据结构,有助于方便交互同时性能也更好。。。。
手上没windows就没法帮你调试了。。。只能说多尝试了