副标题:C#中数据类型转换时用到的SizeParamIndex参数的含义
由于C#在调用VC++的库文件时,其代码为托管代码,所以C#和VC++的库之间数据在流转时就需要进行托管和非托管的转换,再加上C#中没有地址的概念,导致在VC++中的地址管理,在C#中根本用不上。其中的表现之一就是在C#中处理VC++库的内存指针相当麻烦,这不,今天就不得不面临这个问题了
库文件A.dll使用VC++生成,其中定义了一个回调函数,定义如下:
typedef UINT32 (CALLBACK *CBVideo)( UINT32 lUserData, ULONG flagPar, LONG datatype, LONG cmdLen, char *cmdBuf );
参数很简单,当然是除了最后一个参数
最后一个参数cmdBuf是一个char*,其实就是一个内存地址,cmdLen表示cmdBuf中有效数据的长度。需要注意的是,cmdBuf传递二进制数据,即中间可能存在0的字符串,所以不能采用简单的字符串来操作,否则第一个0后面的部分会被自动丢弃掉
在C#中定义如下:
public delegate uint CallbackDelegate(uint luserData,
uint flagPar,
int datatype,
int cmdLen,
byte[] bodyBytes);
不管回调的实际数据有多少,结果在C#中得到的bodyBytes只有一个字符,如果把bodyBytes的类型改为string:
public delegate uint CallbackDelegate(uint luserData,
uint flagPar,
int datatype,
int cmdLen,
string bodyBytes);
则回调时,cmdBuf中间不出现0时,一切正常,但如果有0出现,则从0开始往后面的数据全被截断!根据网上的一般方法修改如下:
public delegate uint CallbackDelegate(uint luserData,
uint flagPar,
int datatype,
int cmdLen,
[MarshalAs(UnmanagedType.LPArray,SizeParamIndex=1)] byte[] bodyBytes);
但问题依旧,仍然得不到正确的结果。
查看了很多的网页,大家都只有一个简单的说明,SizeParamIndex设置为1,但没有说明为什么,也试了不同的UnmanagedType类型,都不正确。
反复查看msdn,网址如下:
https://msdn.microsoft.com/zh-cn/magazine/system.runtime.interopservices.marshalasattribute.sizeparamindex
MarshalAsAttribute.SizeParamIndex 字段
其中示例的一个片段如下:
namespace SomeNamespace
{
// Force the layout of your fields to the C style struct layout.
// Without this, the .NET Framework will reorder your fields.
[StructLayout(LayoutKind.Sequential)]
public struct Vertex
{
float x;
float y;
float z;
}
class SomeClass
{
// Add [In] or [In, Out] attributes as approppriate.
// Marshal as a C style array of Vertex, where the second (SizeParamIndex is zero-based)
// parameter (size) contains the count of array elements.
[DllImport ("SomeDll.dll")]
public static extern void SomeUnsafeMethod(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] Vertex[] data,
long size );
public void SomeMethod()
{
Vertex[] verts = new Vertex[3];
SomeUnsafeMethod( verts, verts.Length );
}
}
}
仔细查看 public static extern void SomeUnsafeMethod 一行上面的注释:
Marshal as a C style array of Vertex, where the second (SizeParamIndex is zero-based) parameter (size) contains the count of array elements.
反复捉摸,终于看出些门道,SizeParamIndex是指定data数组大小的那个参数的索引号!刚开始看这个属性名字很奇怪,很不顺,现在这样理解,就感觉完全正确了。
于是修改我的代码如下:
public delegate uint CallbackDelegate(uint luserData,
uint flagPar,
int datatype,
int cmdLen,
[MarshalAs(UnmanagedType.LPArray,SizeParamIndex=3)] byte[] bodyBytes);
把SizeParamIndex的值设置为3,即cmdLen在回调函数中以0为基础的索引号,然后再运行程序,一切OK!
至此,终于明白了这个SizeParamIndex参数的意义,以后如果遇到这种数据的传递,就再也不怕了
回顾一下,感觉这文章还是要仔细捉摸其所说的含义还是会有帮助的,虽然有时候确实难懂,但理解了之后,发现其说得确实没错。
希望对还没有弄明白是怎么回事的像我一样的程序猿们有所帮助
根据以上理解,一篇文章:
http://www.cnblogs.com/zeroone/p/3681370.html
的第4部分:
[DllImport("test.dll", EntryPoint = "#1")]
publicstaticexternvoid test(int N, int[] n, [MarshalAs(UnmanagedType.LPArray,SizeParamIndex=1)] int[] Z);
中的SizeParamIndex显然不对,其值应该是0才对,因为N才是n和Z的元素个数。如果值为1,则成了n是个数,显然不符合实际的情况。其例子中的C++代码如下:
void__declspec(dllexport) test(constint M, constint n[], int *N)
{
for (int i=0; i