使用指针调用非托管代码

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用指针调用非托管代码相关的知识,希望对你有一定的参考价值。

我有一个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> 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;
        }
    }
}
答案

你不能编组List <>,它必须是一个数组。数组已作为指针封送,因此您无需执行任何特殊操作。在Pack上轻松一下,不需要unsafe关键字。

您可以向结构添加构造函数,以便从List <>中轻松初始化它。像这样:

[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);
    }
}
另一答案

要初始化列表,您只需添加行:

myConfig.sconfig = new List<SConfig>()

在开始向其中添加元素之前。

另一答案

我开始了另一个主题,因为我因为经验不足而提出了错误的问题

工作解决方案就在这里

Marshal array of struct and IntPtr

谢谢您的帮助

- 李

以上是关于使用指针调用非托管代码的主要内容,如果未能解决你的问题,请参考以下文章

将指针从非托管代码返回到托管代码

对“xxx”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。 错误解决一例。(代码片段

从非托管代码传递指针

我想从 C++ 非托管代码调用 C# 委托。无参数委托工作正常,但有参数委托使我的程序崩溃

在 C# 中传递 IntPtr 指针后,在非托管 C++ 代码中分配数组,编组类型

C# 托管非托管代码