【问题标题】:c# p/invoke difficulties marshalling pointersc# p/invoke 编组指针的困难
【发布时间】:2009-06-02 19:25:36
【问题描述】:

我正在尝试使用 p/invoke 从 c# 调用本机 .dll。我能够进行调用(没有崩溃,函数返回一个值),但返回代码指示“指针参数不指向可访问的内存”。为了解决这个问题,我采取了反复试验,但到目前为止我还没有取得任何进展。

这是我正在调用的本机函数的签名:

LONG extern WINAPI MyFunction ( LPSTR lpszLogicalName, //input
                                   HANDLE hApp,           //input  
                                   LPSTR lpszAppID,       //input 
                                   DWORD dwTraceLevel,    //input
                                   DWORD dwTimeOut,       //input
                                   DWORD dwSrvcVersionsRequired, //input
                                   LPWFSVERSION lpSrvcVersion, //WFSVERSION*, output
                                   LPWFSVERSION lpSPIVersion,  //WFSVERSION*, output
                                   LPHSERVICE lphService       //unsigned short*, output
                                 );

这是在 C# 中导入的签名:

 [DllImport("my.dll")]
 public static extern int MyFunction( [MarshalAs(UnmanagedType.LPStr)] 
                                      string logicalName, 
                                      IntPtr hApp, 
                                      [MarshalAs(UnmanagedType.LPStr)] 
                                      string appID, 
                                      int traceLevel, 
                                      int timeout, 
                                      int srvcVersionsRequired, 
                                      [Out] WFSVersion srvcVersion, 
                                      [Out] WFSVersion spiVersion,
                                      [Out] UInt16 hService
                                    );

这是 WFSVERSION 的 C 定义:

typedef struct _wfsversion
{
    WORD            wVersion;
    WORD            wLowVersion;
    WORD            wHighVersion;
    CHAR            szDescription[257];
    CHAR            szSystemStatus[257];
} WFSVERSION, * LPWFSVERSION;

这是 WFSVersion 的 C# 定义:

[StructLayout(LayoutKind.Sequential)]
public class WFSVersion
{
    public Int16 wVersion;
    public Int16 wLowVersion;
    public Int16 wHighVersion;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 257)]
    public char[] szDescription;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 257)]
    public char[] szSystemStatus;
}

这是从 C# 对 MyFunction 的调用:

WFSVersion srvcVersionInfo = new WFSVersion();
WFSVersion spiVersionInfo = new WFSVersion();


 UInt16 hService = 0;
 IntPtr hApp = IntPtr.Zero;
 string logicalServiceName = tbServiceName.Text;
 int openResult = MyFunction(logicalServiceName, hApp, null, 0,  
                              XFSConstants.WFS_INDEFINITE_WAIT,
                              0x00000004, srvcVersionInfo, spiVersionInfo, 
                              hService);

正如我所说,此调用返回,但返回值是一个错误代码,指示“指针参数未指向可访问的内存”。我一定是对参数 1、3、7、8 或 9 做错了什么。但是,我已经成功调用了这个 .dll 中需要 WFSVERSION* 作为参数的其他函数,所以我不认为参数 7 或8是这里的问题。

如果您对这个问题的原因有任何想法,或者对我的代码提出任何建设性的批评,我将不胜感激。这是我第一次使用 P/Invoke,所以我不知道从哪里开始。有什么办法可以缩小问题的范围,还是我唯一的选择是试错?

【问题讨论】:

  • pinvoke.net 可能会有帮助
  • 你在第三个参数中有一个空指针 - 这是正确的代码吗?
  • 是的,空指针作为第三个参数是有效的。

标签: c# pointers interop pinvoke


【解决方案1】:

这里有两个明显的错误。在您的结构定义中,您应该对 szDescription 和 szSystemStatus 使用 byte[] 而不是 char[]。

pInvoke 调用中的最后一个参数也不是指针。当您调用 MyFunction 时,hService 为零,因此就函数而言,它是一个无效的指针。 [Out] 是一个 Marshaling 指令,它告诉运行时何时何地复制数据,而不是指示参数是指针。您需要将 [Out] 更改为 out 或 ref 这告诉运行时 hService 是一个指针:

[DllImport("my.dll")]
public static extern int MyFunction( [MarshalAs(UnmanagedType.LPStr)] 
                                  string logicalName, 
                                  IntPtr hApp, 
                                  [MarshalAs(UnmanagedType.LPStr)] 
                                  string appID, 
                                  int traceLevel, 
                                  int timeout, 
                                  int srvcVersionsRequired, 
                                  [Out] WFSVersion srvcVersion, 
                                  [Out] WFSVersion spiVersion,
                                  out UInt16 hService);

【讨论】:

  • 谢谢,这就是问题所在。不过,了解到这一点后,我想知道 [Out] WFSVersion 参数是否也被错误地编组。
  • 我现在明白了:WFSVersion 是引用类型,而不是值类型。这些参数甚至不需要 [Out] 属性;它只是稍微提高了调用的效率。
【解决方案2】:

一些想法:

  • C# WFSVersion 类可能应该是struct。我不知道 P/Invoke marshaller 是否关心,但我总是看到使用的结构。

  • 字符大小可能是个问题。

    C 的 CHAR 是 8 位宽 (ANSI),而 .Net 的 System.Char 是 16 位宽 (Unicode)。要为编组器提供尽可能多的信息以便它进行正确的转换,请尝试将“CharSet = CharSet.Ansi”添加到 DllImport 和 StructLayout 属性,并更改 WFSVersion 中的字符串声明:

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)]
    public string szDescription;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)]
    public string szSystemStatus;
    
  • 另一个问题可能是结构中的数据对齐。如果在编译 C 结构时未指定对齐,则结构中的数据元素可能在一个或两个字节边界上对齐。尝试在 WFSVersion 的 StructLayout 属性中使用Pack

    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
    // Try pack values of 2, 4 and 8 if 1 doesn't work.
    

还有一些问题:

  • 是否打算从非 C 代码调用 MyFunction?原作者编写的代码可能假设传入的数据是使用 C 运行时内存管理器分配的。

  • C DLL 中的代码是否使用传递给它的指针在 MyFunction 返回后进行后续处理?如果是这样 - 并假设在这种情况下可能/明智/理智 - 可能有必要使用 fixed 关键字“固定”传递给 MyFunction 的结构。另外,可能还有安全问题需要处理。

【讨论】:

  • 感谢您提供有关处理 C 字符串的信息。虽然这不是我报告的问题的根源,但我确实遇到了一些我尚未注意到的问题。
【解决方案3】:

我不确定这个问题到底是什么,但希望这将是一个起点。

尝试查看 DllImport 属性的选项,这可能与字符串的编组有关。

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]

CharSet 选项是我认为您可能需要的选项。

【讨论】:

  • 我尝试了 CharSet.Auto、CharSet.Ansi 和 Charset.Unicode,但问题仍然存在
  • PInvoke 网站上是否有关于您正在使用的 dll 的任何进一步文档,或者它是自定义 dll 吗? pinvoke.net
【解决方案4】:

我发现AppVerifier 以前可用于跟踪 P/Invoke 编组问题。它是免费的,来自 Microsoft。

【讨论】:

    【解决方案5】:

    我的猜测是您的指针之一(lpSrvcVersion、lpSPIVersion 或 lphService)无法从您的 .NET 应用程序访问。你可以尝试修改 DLL(如果它是你的),看看你是否可以让它在没有指针的情况下工作? (我知道您稍后必须将它们添加回来,但至少您可以缩小问题的位置。)

    【讨论】:

    • 很遗憾,我没有修改dll的能力。
    【解决方案6】:

    你确定不是hApp?这看起来像一个输入参数,即请求应用程序的句柄。快速google...是的,有一个创建应用句柄的功能,以及您可以使用的默认参数。

    【讨论】:

      猜你喜欢
      • 2011-03-23
      • 1970-01-01
      • 2013-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-16
      • 1970-01-01
      • 2013-03-04
      相关资源
      最近更新 更多