我有一个C#项目,可以调用非托管的C ++ dll。包装器和大多数调用工作正常,所以我知道我有一个基本结构,如何一切联系在一起好,但有一个特定的调用给我适合。 API调用需要一个指向结构的指针,该结构包含配置数据列表。
这是电话:
m_status = m_XXXXBox.SetConfig(m_channelId, ref SCONFIG_LIST);
其中SCONFIG_LIST是包含数据的结构......
该问题与SCONFIG_LIST
有关以下是直接来自此API规范的文档:
Points to the structure SCONFIG_LIST, which is defined as follows:
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
以下是我在C#中定义的2个结构:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct SConfig
{
public int Parameter;
public int Value;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct SConfig_List
{
public int NumOfParams;
// public List<SConfig> sconfig = new List<SConfig>(); // This throws compile time error
public List<SConfig> sconfig;
}
我知道你不能在结构中使用字段初始化器,但我似乎无法弄清楚如何在结构中外部初始化sconfig ... 这是调用方法的片段
SConfig_List myConfig = new SConfig_List();
SConfig configData = new SConfig();
configData.Parameter = 0x04;
configData.Value = 0x10;
myConfig.NumOfParams = 1;
myConfig.sconfig.Add(configData);
这会在运行时抛出“未将对象引用设置为对象实例”的错误 我理解这个错误,因为sconfig尚未初始化 - 我只是想弄清楚如何做到这一点......
所以我的下一个想法是解决这个问题,我会像这样创建SCONFIG_LIST结构(没有列表内部) - 我的理由是我现在不需要初始化对象,我可以做使用NumOfParams = 1多次调用dll,而不是NumOfParams&gt; 1并让dll遍历struct数据。
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct SConfig_List
{
public int NumOfParams;
public SConfig sconfig;
}
以下是我调用方法的方法
configData.Parameter = 0x04;
configData.Value = 0x10;
myConfig.NumOfParams = 1;
myConfig.sconfig.Parameter = configData.Parameter;
myConfig.sconfig.Value = configData.Value;
m_status = m_XXXXBox.SetConfig(m_channelId, ref myConfig);
这就解决了这一点,现在在调用dll的实际方法上 编组时还有几个问题/问题,但现在是:
public XXXXErr SetConfig(int channelId, ref SConfig_List config)
{
unsafe
{
IntPtr output = IntPtr.Zero;
IntPtr input = Marshal.AllocHGlobal(Marshal.SizeOf(config));
Marshal.StructureToPtr(config, input, true);
XXXXErr returnVal = (XXXXErr)m_wrapper.Ioctl(channelId, (int)Ioctl.SET_CONFIG, input, output);
return returnVal;
}
}
这会在没有错误的情况下通过所有初始设置,但是当我尝试实际调用dll时,我收到错误:尝试读取或写入受保护的内存。这通常表明其他内存已损坏。
我知道这是满口的,我甚至不知道究竟该问什么,因为我确信这篇文章中有多个问题,但有任何关于让我走上正轨的想法吗?
此时我已经尝试了很多东西,我感到很茫然,我只需要一些方向。我不是在寻找一个“为我做这个”类型的答案,而是一个解释,也许是关于完成这个的一些指示。与所有事情一样,我确信有多种方法可以完成任务 - 可能是一种有效的方式,但形式不好,而且更复杂的方式可能是“更好的实践”
我们将非常感谢任何和所有建议/意见。如果我排除了帮助我解决这个问题所需的任何相关数据,请告诉我,我将尽我所能。
我要感谢迄今为止的回复。 我一直在努力尝试解决这个问题,但到目前为止我还没有运气。我找到了很多不起作用的方法,但是: - )
我尝试了各种“不安全”组合 - “MarshalAs”,“StructLayout”以及我在网上发现的其他几件事情,现在我求饶恕。
我已经成功实现了对这个非托管dll的其他几个调用,但是它们都使用简单的整数指针等。我的问题是将指针传递给包含另一个struct的数组的Struct。如果你看一下我原来问题的最顶层,你可以看到dll中的文档以及它是如何构建的。没有返回值,我只是试图通过这个dll将一些配置设置传递给设备。
我将发布我整个项目的框架,以便我可以让某人抓住这个过程,并希望将来帮助其他人尝试解决这类问题。
这是Wrapper的骨架(并非显示所有功能)
using System;
using System.Runtime.InteropServices;
namespace My_Project
{
internal static class NativeMethods
{
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hModule);
}
internal class APIDllWrapper
{
private IntPtr m_pDll;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int APIIoctl(int channelId, int ioctlID, IntPtr input, IntPtr output);
public APIIoctl Ioctl;
//extern “C” long WINAPI APIIoctl
//(
//unsigned long ChannelID,
//unsigned long IoctlID,
//void *pInput,
//void *pOutput
//)
public bool LoadAPILibrary(string path)
{
m_pDll = NativeMethods.LoadLibrary(path);
if (m_pDll == IntPtr.Zero)
return false;
pAddressOfFunctionToCall = NativeMethods.GetProcAddress(m_pDll, "APIIoctl");
if (pAddressOfFunctionToCall != IntPtr.Zero)
Ioctl = (APIIoctl)Marshal.GetDelegateForFunctionPointer(
pAddressOfFunctionToCall,
typeof(APIIoctl));
return true;
}
public bool FreeLibrary()
{
return NativeMethods.FreeLibrary(m_pDll);
}
}
}
And Here is the class that defines the hardware I am trying to communicate with
namespace My_Project
{
public class APIDevice
{
public string Vendor { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
}
Interface
using System.Collections.Generic;
namespace My_Project
{
public interface I_API
{
APIErr SetConfig(int channelId, ref SConfig_List config);
}
}
包含API代码的实际类 - 这是错误的地方,我知道我现在如何拥有IntPtrs是不正确的 - 但是这显示了我想要做的事情
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace My_Project
{
public class API : I_API
{
private APIDevice m_device;
private APIDllWrapper m_wrapper;
public APIErr SetConfig(int channelId, ref SConfig_List config)
{
IntPtr output = IntPtr.Zero;
IntPtr input = Marshal.AllocHGlobal(Marshal.SizeOf(config));
Marshal.StructureToPtr(config, input, true);
APIErr returnVal = (APIErr)m_wrapper.Ioctl(channelId, (int)Ioctl.SET_CONFIG, input, output);
return returnVal;
}
}
}
这是包含我正在使用的结构的定义的类
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace My_Project
{
public enum APIErr
{
STATUS_NOERROR = 0x00,
ERR_BUFFER_EMPTY = 0x10,
ERR_BUFFER_FULL = 0x11,
ERR_BUFFER_OVERFLOW = 0x12
}
public struct SConfig
{
public int Parameter;
public int Value;
}
public struct SConfig_List
{
public int NumOfParams;
public SConfig[] sconfig;
public SConfig_List(List<SConfig> param)
{
this.NumOfParams = param.Count;
this.sconfig = new SConfig[param.Count];
param.CopyTo(this.sconfig);
}
}
}
最后 - 通过包装器调用dll的实际应用程序
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using My_Project;
namespace Test_API
{
public class Comm
{
private I_API m_APIBox;
APIErr m_status;
int m_channelId;
bool m_isConnected;
public Comm(I_API apiInterface)
{
m_APIBox = apiInterface;
m_isConnected = false;
m_status = APIErr.STATUS_NOERROR;
}
public bool ConfigureDevice()
{
SConfig tempConfig = new SConfig();
tempConfig.Parameter = 0x04;
tempConfig.Value = 0x10;
SConfig_List setConfig = new SConfig_List(tempConfig);
m_status = m_APIBox.SetConfig(m_channelId, ref setConfig);
if (m_status != APIErr.STATUS_NOERROR)
{
m_APIBox.Disconnect(m_channelId);
return false;
}
return true;
}
}
}
答案 0 :(得分:3)
你不能编组List&lt;&gt;,它必须是一个数组。数组已作为指针封送,因此您无需执行任何特殊操作。在Pack上轻松一下,不需要 unsafe 关键字。
您可以在结构中添加构造函数,以便从List&lt;&gt;中轻松初始化它。像这样:
[StructLayout(LayoutKind.Sequential)]
public struct SConfig {
public int Parameter;
public int Value;
}
[StructLayout(LayoutKind.Sequential)]
public struct SConfig_List {
public int NumOfParams;
public SConfig[] sconfig;
public SConfig_List(List<SConfig> param) {
this.NumOfParams = param.Count;
this.sconfig = new SConfig[param.Count];
param.CopyTo(this.sconfig);
}
}
答案 1 :(得分:0)
要初始化列表,您只需添加行:
myConfig.sconfig = new List<SConfig>()
在开始向其添加元素之前。
答案 2 :(得分:-1)