struct 的指针作为 c++ dll 函数的返回值

Posted

技术标签:

【中文标题】struct 的指针作为 c++ dll 函数的返回值【英文标题】:Pointer of struct as return value for c++ dll function 【发布时间】:2016-07-24 23:35:27 【问题描述】:

我已经研究了几天,并且阅读了很多问题,这些问题帮助我到达了现在的位置。但我仍然需要一些帮助。

我会解释的。我有一个 C++ DLL,我想包装它以便在 c# 中使用它。我有 DLL 的文档,但我无法更改它的任何内容。很多功能都适用于基本的 dllimport 设置,但我有一些功能无法正常工作,这是其中之一:

DLL documentation
struct stChannel LookForAvailableChannels (const char *dataBaseFolder, int serialNumber, double firstLogTime, double lastLogTime)

我也有这些结构:

struct stChannelInfo

 char ChannelTag[17];
 char ChannelEnabled;


struct stChannel

 int ChannelNumber;
 struct stChannelInfo *ChannelInfo;

所以尝试不同的东西,在阅读了很多之后,我得出了一个“部分”有效的解决方案:

C# Code
    [StructLayout(LayoutKind.Sequential)]
    public struct stChannelInfo
    
        public IntPtr ChannelTag;
        public byte ChannelEnabled;
    ;

    [StructLayout(LayoutKind.Sequential)]
    public struct stChannel 
        public int ChannelNumber;      
        public stChannelInfo ChannelInfo;
    ;

[DllImport("NG.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern stChannel LookForAvailableChannels(string dataBaseFolder, int serialNumber, double firstLogTime, double lastLogTime);

stChannel Estructura = new stChannel();

我有一个调用按钮会触发这段代码:

Estructura = LookForAvailableChannels("C:\\Folder", 12345678, FechaInicio, FechaFinal);

然后我编组 Estructura.ChannelInfo.ChannelTag:

string btListFile = Marshal.PtrToStringAnsi(Estructura.ChannelInfo.ChannelTag);

这确实有效,它返回的数据我知道它是正确的。但是我只接收数组的第一个元素,因为 stChannel 内部的 stChannelInfo 结构是一个指针,我不知道如何在 c# 中处理它。

应该以我现在使用的这段代码的方式完成:

Marshal.PtrToStringAnsi(Estructura.ChannelInfo.ChannelTag);

应该是

Marshal.PtrToStringAnsi(Estructura.ChannelInfo[i].ChannelTag);

但是我现在使用的所有东西都不起作用。我将不胜感激。

谢谢。

编辑:

感谢用户 Adriano Repetti,现在我有了这个:

C# 代码 [StructLayout(LayoutKind.Sequential)] 公共结构 stChannelInfo [MarshalAs(UnmanagedType.LPStr, SizeConst = 17)] 公共字符串 ChannelTag; 公共字节 ChannelEnabled; ;

    [StructLayout(LayoutKind.Sequential)]
    public struct stChannel 
        public int ChannelNumber;
        public IntPtr ChannelInfo;
    ;

[DllImport("NG.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern stChannel LookForAvailableChannels(string dataBaseFolder, int serialNumber, double firstLogTime, double lastLogTime);

stChannel Estructura = new stChannel();

我有一个调用按钮会触发这段代码:

Estructura = LookForAvailableChannels("C:\\Folder", 12345678, FechaInicio, FechaFinal);

var channelinf = (stChannelInfo)Marshal.PtrToStructure(Estructura.ChannelInfo, typeof(stChannelInfo));


        for (int i = 0; i < 4; i++)
        
            var ptr = IntPtr.Add(Estructura.ChannelInfo, Marshal.SizeOf(typeof(stChannelInfo)) * i);
            var channelll =  (stChannelInfo)Marshal.PtrToStructure(ptr, typeof(stChannelInfo));
        

现在的问题是我在这里得到了一个 AccessViolationException:

Estructura = LookForAvailableChannels("C:\\Folder", 12345678, FechaInicio, FechaFinal);

但我真的不知道为什么,我会很感激任何帮助。

【问题讨论】:

【参考方案1】:

不幸的是,您的数组 ChannelInfo 没有固定大小,然后 自动 编组 [MarshalAs(UnamangedType.LPArray)] 不起作用。手动执行封送处理意味着ChannelInfo 必须声明为IntPtr

[StructLayout(LayoutKind.Sequential)]
public struct stChannel 
    public int ChannelNumber;      
    public IntPtr ChannelInfo;
;

当您需要它时,您需要将其转换为结构:

var channelInfo = (stChannelInfo)Marshal.PtrToStructure(
    Estructura.ChannelInfo,
    typeof(stChannelInfo));

现在您正在访问第一个元素,要访问需要偏移量的数组项:

var ptr = IntPtr.Add(Estructura.ChannelInfo,
    Marshal.SizeOf(typeof(stChannelInfo)) * itemIndex);

然后在上面使用Marshal.PtrToStructure()。你可能想写一个辅助方法:

static GetUnmanagedArrayItem<T>(IntPtr baseAddress, int index) 
    var ptr = IntPtr.Add(baseAddress, Marshal.SizeOf(typeof(T)) * index);
    return (T)Marshal.PtrToStructure(ptr, typeof(T));

这样使用:

var channelInfo = GetUnamangedArrayItem<stChannelInfo>(Estructura.ChannelInfo, 1);

您没有明确询问,但请注意,您不能手动编组 unmanged char* 字符串与 IntPtr 固定长度的 char 数组

您可以将其声明为使用 `UnmanagedType.LPStr` 装饰该字段的字符串: [MarshalAs(UnmanagedType.LPStr, SizeConst=17)] 公共字符串 ChannelTag;

编辑:我错了,String 不能用于返回值,因为它不是blittable,当然IntPtr 是错误的,因为你有一个固定长度的数组,我建议使用:

[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 17)]
public byte[] ChannelTag;

您可以使用Encoding.ASCII.GetString(yourStruct.ChannelTag); 简单地解码此数组来获取原始字符串。作为替代方案,您可以遵循 JaredPar 在this post 中的建议。

【讨论】:

感谢您的快速回答,这真的有助于我了解发生了什么。一切正常,但这部分 'Marshal.SizeOf(typeof(T) * index))' 没有编译。这个算子不能应用到这两种类型上,不知道怎么解决。 如果您执行 manual 编组,那么它也应该可以工作。有点烦人,但使用一些辅助函数可能是合理的。我的建议是围绕它们编写一个完整的托管包装器。 编辑了我之前的评论:D 缺少括号,已修复。 与其发布新代码,不如让新问题只是这样——一个新问题。期望有人也调试你所有的相应错误是不现实的。

以上是关于struct 的指针作为 c++ dll 函数的返回值的主要内容,如果未能解决你的问题,请参考以下文章

C++指针问题,请问如何定义一个返回值为结构体指针数组的函数?

如何通过 C# 从 C++ dll 调用 struct 中的函数

C++成员函数指针指向全局函数指针

使用指针调用 C++ DLL 函数

c#调用c++的DLL,接口函数参数有函数指针,在线等解决办法

将 C++ 结构指针从 Perl 传递给任意 dll 函数调用