C#处理VC++动态库回调函数的字符串指针参数(传递二进制流)

来源:互联网 发布:小知不及大知怎么读 编辑:程序博客网 时间:2024/05/22 00:42

副标题:C#中数据类型转换时用到的SizeParamIndex参数的含义


由于C#在调用VC++的库文件时,其代码为托管代码,所以C#和VC++的库之间数据在流转时就需要进行托管和非托管的转换,再加上C#中没有地址的概念,导致在VC++中的地址管理,在C#中根本用不上。其中的表现之一就是在C#中处理VC++库的内存指针相当麻烦,这不,今天就不得不面临这个问题了


库文件A.dll使用VC++生成,其中定义了一个回调函数,定义如下:

typedefUINT32 (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    {    floatx;floaty;    floatz;    }    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<M; i++)   {     N[i]=n[i]+10;   } } 





0 0