2013-11-03 91 views
1

对于我的问题,我非常接近解决方案,但我需要一些关于最终结果的指导以使所有工作都成功。在过去的一周中,我学到了很多关于数组结构和IntPtr数组元素

作为一个参考,上周我提了一个类似的问题关于同一个话题,但由于我身边的巨大监督,我提出了错误的问题。

我想使用一个非托管的C++ dll,这是与连接的设备通信的API。我已经成功创建了包装器,以及其他大部分函数调用,但是最后一个让我疯狂。

对于一些背景信息(可能不需要回答这个问题 - 并牢记我的基本思维过程当时有缺陷)是在这里:Calling un-managed code with pointer (Updated)

在我原来的问题,我是问有关创建IntPtr到包含struct(2)的数组的结构(1)....实际上,struct(1)根本不包含数组,它包含指向数组的指针。

这里是我想实现作为参考API文档:

extern “C” long WINAPI PassThruIoctl 
(
    unsigned long ChannelID, 
    unsigned long IoctlID, 
    void *pInput, 
    void *pOutput 
) 


// *pInput Points to the structure SCONFIG_LIST, which is defined as follows: 
// *pOutput is not used in this function and is a null pointer 

typedef struct 
{ 
    unsigned long NumOfParams; /* number of SCONFIG elements */ 
    SCONFIG *ConfigPtr; /* array of SCONFIG */ 
} SCONFIG_LIST 

// Where: 
// NumOfParms is an INPUT, which contains the number of SCONFIG elements in the array pointed to by ConfigPtr. 
// ConfigPtr is a pointer to an array of SCONFIG structures. 

// The structure SCONFIG is defined as follows: 
typedef struct 
{ 
    unsigned long Parameter; /* name of parameter */ 
    unsigned long Value; /* value of the parameter */ 
} SCONFIG 

这里是我的结构定义,因为我现在有他们定义

[StructLayout(LayoutKind.Sequential)] // Also tried with Pack=1 
public struct SConfig 
{ 
    public UInt32 Parameter; 
    public UInt32 Value; 
} 



[StructLayout(LayoutKind.Sequential)] // Also tried with Pack=1 
public struct SConfig_List 
{ 
    public UInt32 NumOfParams; 
    public IntPtr configPtr; 

    public SConfig_List(UInt32 nParams, SConfig[] config) 
    { 
     this.NumOfParams = nParams; 

     // I have tried these 2 lines together 
     IntPtr temp = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyNameSpace.SConfig)) * (int)nParams); 
     this.configPtr = new IntPtr(temp.ToInt32()); 

     // I have tried this by itself 
     // this.configPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyNameSpace.SConfig)) * (int)nParams); 


     // and this line 
     // this.configPtr = Marshal.AllocHGlobal(sizeof(SConfig)*(int)nParams); // this only complies with unsafe struct 
    } 
} 

这里的片段将其设置为变量并调用与API接口的函数的代码

SConfig[] arr_sconfig; 
arr_sconfig = new SConfig[1]; 

arr_sconfig[0].Parameter = 0x04; 
arr_sconfig[0].Value = 0xF1; 
SConfig_List myConfig = new SConfig_List(1, arr_sconfig); 

m_status = m_APIBox.SetConfig(m_channelId, ref myConfig); 

最后,这里是传递到DLL此信息的功能:

public APIErr SetConfig(int channelId, ref SConfig_List config) 
{ 
    unsafe 
    { 
     IntPtr output = IntPtr.Zero; // Output not used, just a null pointer for this function 

     // These 2 lines of code cause API dll to yell about invalid pointer (C# is happy but it doesnt work with dll) 
     // IntPtr temp = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(J_2534_API.SConfig_List))); 
     // IntPtr input = new IntPtr(temp.ToInt32()); 

     // The following 2 lines only compile with unsafe - but API dll "likes" the pointer - but I am not getting desired results 
     // The dll is properly getting the Number of Parameters (NumOfParams), but the data within the array is not being 
     // referenced correctly 
     IntPtr input = Marshal.AllocHGlobal(sizeof(SConfig_List)); // Only works with unsafe 
     Marshal.StructureToPtr(config, input, true); 

     APIErr returnVal = (APIErr)m_wrapper.Ioctl(channelId, (int)Ioctl.SET_CONFIG, input, output); 

     return returnVal; 
    } 
} 

我才意识到我的基本想法巨大的监督,我从来没有甚至可以让C#快乐,无论我有语法错误和代码不会编译,或者它会编译,但给出了运行时错误(甚至从来没有调用外部DLL)

这些问题是在我身后。代码现在编译好了,并且执行时没有任何运行时错误。另外,我使用的dll具有日志记录功能,所以我可以看到我实际上正在调用正确的函数。我甚至将一些数据正确传递给它。函数正在正确读取NumOfParams变量,但是结构数组似乎是垃圾数据。

我一直在读一个非常有用的文章在这里:http://limbioliong.wordpress.com/2012/02/28/marshaling-a-safearray-of-managed-structures-by-pinvoke-part-1/

而且我一直在阅读MSDN,但到目前为止,我还没有能够碰到的代码的神奇组合,使这件事情的工作,所以我我再次伸出援助之手。

我很确定我的问题是我没有正确设置IntPtr变量,并且它们没有指向内存中的正确区域。

我已经尝试过不安全和安全代码的各种组合。另外,我知道在这一点上我没有明确地释放内存,所以对此的指点也会有帮助。在我的研究,这里有一些想法可能的工作,我只是不能似乎得到他们恰到好处

[MarshalAs(UnmanagedType.LPWStr)] 

[MarshalAs(UnmanagedType.ByValArray, SizeConst=...)] 

[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst=100)] 

最后一个问题:我认为,由于C++的声明是无符号长,那么UInt32的是正确的类型在C#中?

回答

4

你的SConfig_List构造函数有很多错误。最大的问题是你为数组分配内存,但完全忘记复制结构。所以本机代码可以获得指针,但是会查看未初始化的内存。解决这个问题是这样的:

public SConfig_List(SConfig[] config) { 
     this.NumOfParams = config.Length; 
     int size = Marshal.SizeOf(config[0]); 
     IntPtr mem = this.configPtr = Marshal.AllocHGlobal(size * config.Length); 
     for (int ix = 0; ix < config.Length; ++ix) { 
      Marshal.StructureToPtr(config[ix], mem, false); 
      mem = new IntPtr((long)mem + size); 
     } 
    } 

忘记调用Marshal.FreeHGlobal()调用完成后再次,否则你会泄漏内存。

避免编组的SConfig_List时,你的函数调用是只提供C函数更好的声明最简单的方法:

[DllImport(...)] 
private static extern ApiErr PassThruIoctl(
    int channelID, 
    uint ioctlID, 
    ref SConfig_List input, 
    IntPtr output); 

这使得一个像样的包装方法是这样的:

public APIErr SetConfig(int channelId, SConfig[] config) { 
    var list = new SConfig_List(config); 
    var retval = PassThruIoctl(channelId, Ioctl.SET_CONFIG, ref list, IntPtr.Zero); 
    Marshal.FreeHGlobal(list.configPtr); 
    return retval; 
} 
+0

非常感谢,这非常有帮助,我可以在你的帮助下完成并运行。由于输入和输出指针与正在传递的实际Ioctl值的关系不同,我还没有实现您的想法来为C函数提供更好的声明。在某些情况下,它们都是无效的(例如只发送一个明确的缓冲区命令,因此两个指针都是空的。在另一个Ioctl场景中,一个指针指向一个Bytes数组。因此,我仍然在Set_Config方法中使用Marshaling - 但它工作100%。 – mleega