【问题标题】:C# method's type signature is not pinvoke compatibleC# 方法的类型签名不兼容 pinvoke
【发布时间】:2015-02-18 08:33:06
【问题描述】:

我想使用 Omron V4KU 的 API,文档描述如下:

原始c#代码:

    const string DllLocation = @"..\..\Libs\Omron\OMCR.dll";

    [DllImport(DllLocation)]
    public static extern LPCOMCR OMCR_OpenDevice(string lpcszDevice, LPCOMCR_OPTION lpcOption);

    public void Start()
    {
        var lpcOption = new LPCOMCR_OPTION();
        var result = OMCR_OpenDevice(null, lpcOption); // error method's type signature is not pinvoke compatible
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LPCOMCR
    {
        public string lpcszDevice;
        public IntPtr hDevice;
        public uint lpcDevice;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LPCOMCR_OPTION
    {
        public uint dwReserved0;
        public uint dwReserved1;
        public uint dwReserved2;
        public uint dwReserved3;
    }

如果我在编写代码时遗漏或错误? 对不起,我的英语不好。感谢您的帮助。

【问题讨论】:

  • OMCR_OPTION 是一个联合体,您只在 C# 代码中声明了最后一个 (USB) 部分。
  • 是的,我只声明了 USB,但在文档结构中只描述了 1 个结构。我应该同时使用吗? @格鲁
  • 当然应该。如果您查看 C 中的 sizeof(OMCR_OPTION),您会看到它有 7 个 DWORDS 长(假设您的 LPVOID 也是 32 位的)。使用 [StructLayout(LayoutKind.Explicit)]FieldOffset 属性来排列 C# 结构以匹配原生结构。
  • 我来自这个链接social.msdn.microsoft.com/Forums/en-US/…,但是有些属性具有相同的名称,例如“dwReserved1”“dwReserved2”“dwReserved3”。怎么做才能避免出错?你能给我举个例子吗@Groo :)
  • 字段名称并不重要,您可以将它们更改为您喜欢的任何名称(但您也可以嵌套结构以避免命名冲突,并且在编组时仍会获得类似联合的行为)。我在下面的答案中添加了一个示例。

标签: c# interop dllimport


【解决方案1】:

从正确定义联合结构开始:

// OMCR_OPTION.COM
[StructLayout(LayoutKind.Sequential)]
public struct OmcrCom
{
    public IntPtr Reserved0;
    public uint BaudRate;
    public uint Reserved1;
    public uint Reserved2;
    public uint Reserved3;
    public IntPtr Reserved1;
    public IntPtr Reserved2;
}

// OMCR_OPTION.USB
[StructLayout(LayoutKind.Sequential)]
public struct OmcrUsb
{
    public uint Reserved0;
    public uint Reserved1;
    public uint Reserved2;
    public uint Reserved3;
}

// OMCR_OPTION (union of COM and USB)
[StructLayout(LayoutKind.Explicit)]
public struct OmcrOptions
{
    [FieldOffset(0)]
    public OmcrCom Com;

    [FieldOffset(0)]
    public OmcrUsb Usb;
}

// OMCR
[StructLayout(LayoutKind.Sequential)]
public struct OmcrDevice
{
    public string Device;
    public IntPtr DeviceHandle;
    public IntPtr DevicePointer;
}

[DllImport(dllName: DllLocation, EntryPoint = "OMCR_OpenDevice"]
public static extern IntPtr OmcrOpenDevice(string type, ref OmcrOptions options);

然后调用方法,类似:

var options = new OmcrOptions();
options.Com.BaudRate = 115200; // or whatever you need to set

var type = "COM"; // is this USB/COM? not sure

OmcrDevice device;

var devicePtr = OmcrOpenDevice(type, ref options);
if (devicePtr == IntPtr.Zero)
    device = (OmcrDevice)Marshal.PtrToStructure(devicePtr, typeof(OmcrDevice));

【讨论】:

  • 哦,字段名称并不重要,我认为应该相同:D 好的,我尝试像您的示例一样编写代码,但使用相同的字段名称:D 我在编辑部分附加代码。当我运行它时,出现错误“无法在 DLL '..\\..\\Libs\\Omron\\OMCR.dll' 中找到名为 'OMCR_OpenDevice' 的入口点”:( @Groo
  • @yovierayz:哦,好的,我也改了函数名,你需要在DllImport属性中指定入口点,我一会儿再编辑。
  • 等等,它说找不到'OMCR_OpenDevice'?那么函数名不一样呢?发布带有函数签名的整个头文件。
  • 是这样的错误消息。我在接口部分@Groo 中添加了功能
  • @yovierayz:使用dumpbinundname 来检查dll 名称是否被损坏,您需要指定确切的修饰名称作为入口点。
【解决方案2】:

首先,文档要求您将 LPCOMCR_OPTION 作为指针传递 - 您将其作为值传递。使用ref 应该会有所帮助。但是,还有另一个问题,那就是返回值 - 同样,您试图将其解释为一个值,而文档说它是一个指针。但是,这比第一个错误要棘手得多——据我所知,您唯一的选择是使用 C++/CLI 互操作库,或者期望 IntPtr 作为返回值。在任何情况下,您都需要处理以这种方式获得的内存的正确释放。

【讨论】:

  • 我正在编辑c#代码,这是什么意思?我在 omcr.dll @Luaan 中添加了代码结构
  • @yovierayz 是的,Groo 是对的——这是一个联合,所以你需要在 C# 中实现它。不幸的是,C# 并不真正支持联合,因此您唯一的选择就是使用带有LayoutKind.Explicit 的结构并手动管理不同的字段,正如 Groo 建议的那样。不过,您仍然需要返回 IntPtr - David 的代码应该可以正常工作。如果您需要更详细的信息,请包含头文件的方法声明。
  • 我添加了标题声明@Luaan
【解决方案3】:

你需要这样做:

[StructLayout(LayoutKind.Sequential)]
public struct OMCR
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string lpcszDevice;
    public IntPtr hDevice;
    public IntPtr lpcDevice;
}

[StructLayout(LayoutKind.Sequential)]
public struct OMCR_OPTION
{
    public uint dwReserved0;
    public uint dwReserved1;
    public uint dwReserved2;
    public uint dwReserved3;
}

[DllImport(DllLocation, CallingConvention = CallingConvention.???,
    SetLastError = true)]
public static extern IntPtr OMCR_OpenDevice(string lpcszDevice, 
    ref OMCR_OPTION lpcOption);

您需要将CallingConvention.??? 替换为适当的调用约定。我们无法从问题中看出那是什么。您将不得不通过阅读头文件来找出答案。

返回值是一个指向OMCR 的指针。你需要抓住这个指针并在完成后将它传递给OMCR_CloseDevice

为了获得OMCR 值,您需要执行以下操作:

OMCR_OPTION Option = new OMCR_OPTION(); // not sure how to initialize this
IntPtr DevicePtr = OMCR_OpenDevice(DeviceType, ref Option);
if (DevicePtr == IntPtr.Zero)
    throw new Win32Exception();
OMCR Device = (OMCR)Marshal.PtrToStructure(DevicePtr, typeof(OMCR));

【讨论】:

  • 我在代码中添加了标头声明。是 CallingConvention.Cdecl 吗? @大卫赫弗南
  • 问题不断变化。我不再有兴趣尝试帮助你。对我来说这里没有乐趣。对不起。
  • 好的,没问题,谢谢你的帮助 :) 抱歉,如果这让你不开心@David Heffernan
  • 你为什么总是换问题?你不知道我们在给你帮助吗?期望我们跟上您的整个调试会话是忘恩负义的。您为什么不提出问题并从答案中学习?
  • 对不起,我换个问题还是不明白?或者我可能把英语误解为我的语言。如果是这样,我再次道歉。 @大卫赫弗南
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-15
  • 2020-02-26
  • 2015-01-28
  • 1970-01-01
  • 2017-07-25
  • 2011-07-07
相关资源
最近更新 更多