结构体里的指针问题

冰风漫天 2008-04-18 02:51:59
由于在C#里需要调用dll,传许多比较复杂的结构体进去,并且结构体里又带有数组指针(因为结构体里又有结构数组,所有定义成数组用Marshal去分配数组大小发现也失败了),在对这样的结构体的赋值,具体代码类似如下:
int[] myIntArray = GetMyArray();
fixed(int *p = myIntArray)
{
myStrcut.pValue = p;
}

象这样的赋值放在几个不同的方法,但是最后在调用dll的时候发现有的数组有时应该是会被GC回收,造成在我的dll的结构体里的指针指向的数组有时正确、有时却错误.虽然我还在方法的开始用了GC.KeepAlive(myStrcut)这样的方法,但这却并不能解决这问题,不知道哪位有过这样的经验或有比较好的办法解决这问题,当然希望不是叫我把所有的方法合成一个方法,小弟先在这多谢各位了
...全文
149 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
冰风漫天 2008-04-21
  • 打赏
  • 举报
回复
我参考下,多谢了
gomoku 2008-04-20
  • 打赏
  • 举报
回复
你有一个地方错了:

在托管定义中
struct MyStructB
{
public IntPtr[] pa;
public IntPtr pd;
}

pa指向一系列指针 = pointer to {pointer to A1, Pointer to A2 ...} //A2可能还排在A1前面

在非托管定义中
struct MyStructB
{
MyStructA* pa;
double *pd;
};
pa指向连续机构体的第一个 = pointer to {A1, A2, A3...} // A1, A2, A3 顺序排列

解决方法之一是把托管定义改成
struct MyStructB
{
public IntPtr pa;
public IntPtr pd;
}


如果你不跟COM交互,可以不用Marshal.AllocCoTaskMem

我是这样做的:
简单类型可以直接传入或用GCHandle定住(记得托管内存会被系统随意移动的),
复杂类型用Marshal.AllocHGlobal分配处非托管内存后,用Marshal.Copy来封送托管内存到非托管内存.

以下代码供你参考,我给MyStructB的托管定义加了两个帮助函数,用来封装和销存。


class Program
{
[StructLayout(LayoutKind.Sequential)]
struct MyStructA
{
public int a;
}

[StructLayout(LayoutKind.Sequential)]
struct MyStructB
{
public IntPtr pa; // *MyStructA
public IntPtr pd; // *double

public void Create(MyStructA[] As, double[] Ds)
{
int sizeAs = Marshal.SizeOf(typeof(MyStructA)) * As.Length;

//
// 系列化成byte数组 (可以直接拿AddrOfPinnedObject的指针, 不过那个GCHandle将来还是要释放的,
// 从隐藏的角度, 先拷成byte[], 而byte[]可以直接被Marshal.Copy封送)
//
byte[] tmp = new byte[sizeAs];
GCHandle gch = GCHandle.Alloc(As, GCHandleType.Pinned);
Marshal.Copy(gch.AddrOfPinnedObject(), tmp, 0, sizeAs);
gch.Free();

//
// 封送到非托管内存
//
pa = Marshal.AllocHGlobal(sizeAs);
Marshal.Copy(tmp, 0, pa, sizeAs);

pd = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(double)) * Ds.Length);
Marshal.Copy(Ds, 0, pd, Ds.Length);
}
public void Release()
{
Marshal.FreeHGlobal(pa);
Marshal.FreeHGlobal(pd);
pa = pd = IntPtr.Zero;
}
};

[DllImport("testdll.dll")]
static extern double testFunc(IntPtr pMyStructB);

static void Main(string[] args)
{
MyStructA[] a = new MyStructA[2] ;
a[0].a = 11; a[1].a = 22;

MyStructB[] structBs = new MyStructB[2];
structBs[0].Create(a, new double[] { 444, 555 });
structBs[1].Create(a, new double[] { 666, 777 });

GCHandle gch = GCHandle.Alloc(structBs, GCHandleType.Pinned); //
Console.WriteLine(testFunc( gch.AddrOfPinnedObject() )); // 输出 799
gch.Free(); //

structBs[0].Release();
structBs[1].Release();
Console.Read();
}
}


如果Dll改变了传入的数组,可以用非托管内存中拿到结果。以下的例子可读性较好,也提供了打包和解包的接口。

class MyClassB : IDisposable
{
MyStructA[] structAs;
double[] doubles;
MyStructB b;

public MyStructB Marshal()
{
/*marshal class members into unmanaged memory*/
return b;
}
public void UnMarshal()
{
/*marshal unmanaged memory into class members*/
}

public void Dispose()
{
/* free allocated unmanaged memory */
}
}


冰风漫天 2008-04-19
  • 打赏
  • 举报
回复
Array可以用Marshal.AllocCoTaskMem + Marshal.Copy来封送到非托管内存块,而我在C#端的输出并不太复杂,不需要用Marshal.PtrToStructure就可以拿到我的结构,我现在关心的是:结构数组中的结构数组的封送

可能我的表达能力不行,举个例子吧,在C++将如下一个函数,编译成dll
struct MyStructA
{
int a;
};
struct MyStructB
{
MyStructA* pa;
double *pd;
};

extern "C" __declspec(dllexport) double testFunc(MyStructB* pB)
{
return pB[1].pa[1].a + pB[1].pd[1];
}

我在C#中写了如下的代码进行调用
namespace MyTestCode
{
[StructLayout(LayoutKind.Sequential)]
struct MyStructA
{
public int a;
}

[StructLayout(LayoutKind.Sequential)]
struct MyStructB
{
public IntPtr[] pa;
public IntPtr pd;
}

unsafe class Program
{
[DllImport("testdll.dll", EntryPoint = "testFunc")]
public static extern double SampleMethod([In] MyStructB[] b);

static void Main(string[] args)
{
MyStructB[] structB = new MyStructB[2];
InitMyData(ref structB);
Console.WriteLine(SampleMethod(structB));
}

static void InitMyData(ref MyStructB[] structB)
{
double[] dbArray = new double[3] { 0.1, 0.2, 0.3 };

int arraySize = Marshal.SizeOf(typeof(double)) * dbArray.Length;
structB[1].pd = Marshal.AllocCoTaskMem(arraySize);
Marshal.Copy(dbArray, 0, structB[1].pd, dbArray.Length);

structB[1].pa = new IntPtr[2];

MyStructA structA;
structA.a = 2;
int structASize = Marshal.SizeOf(structA);
structB[1].pa[1] = Marshal.AllocCoTaskMem(structASize);
Marshal.StructureToPtr(structA, structB[1].pa[1], true);
}
}
}

编译和运行都没有问题,但输出结果不对
gomoku 2008-04-18
  • 打赏
  • 举报
回复
函数返回指针的话用IntPtr比较好。

Array类型是不可自动Marshal的(缺乏足够信息)。
拿到IntPtr后再用Marshal.PtrToStructure来转化到你的结构。

110,546

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧