【问题标题】:AccessViolationException when calling C function from dll on C#在 C# 上从 dll 调用 C 函数时出现 AccessViolationException
【发布时间】:2013-07-03 22:48:38
【问题描述】:

我正在尝试使用 PInvoke 从 C dll 调用非托管函数。由于无法向开发人员发布 dll 的源代码,因此他们中的一些人使用以下声明在 Delphi 中调用了该函数。我们使用带有 CDECL 调用约定的 SAT.dll。

function AssociarAssinatura( numeroSessao : Longint; codigoDeAtivacao: PChar; 
               CNPJvalue : PChar; assinaturaCNPJs : PChar ) : PChar ; 
               cdecl; External 'SAT.DLL'; 

基于该结构,我在 C# 中制作了以下控制台应用程序,以便从同一个 DLL 测试相同的功能。我做了一些研究,发现delphi中的Longint等价物是C#中的int,而PChar等价物是指向字符串的指针(但我使用了C#的字符串)。

class Program
{
    [DllImport("SAT.dll", CallingConvention = CallingConvention.Cdecl)]
    private static extern string AssociarAssinatura(int numeroSessao, 
        string codigoDeAtivacao, string CNPJvalue, string assinaturaCNPJs);

    static void Main(string[] args)
    {
        Console.WriteLine("Comienza");
        int numeroSessao = 111111;
        string codigoDeAtivacao = "123123123";
        string cnpJvalue = "2222222222222211111111111111";
        string assinaturaCnpJs = "lrafwegmqcgvpzpbdmcmcgdvf";
        string resposta = AssociarAssinatura(numeroSessao, codigoDeAtivacao, 
                 cnpJvalue, assinaturaCnpJs);

        Console.WriteLine(resposta);

    }
}

当我调用该函数时,会引发 AccesViolationException。 AssociarAssinatura 的代码有一些内部打印,表明该函数的代码确实运行良好。因此,我猜这个问题与函数返回它的值有关。我最好的猜测是,我在调用约定方面遇到了问题。有什么想法吗?

【问题讨论】:

  • 当 pinvoke marshaller 试图释放返回的字符串时它会崩溃。您必须将返回类型声明为 IntPtr 并使用 Marshal.PtrToStringAnsi() 自行编组。但是您仍然没有释放字符串的好方法,这将成为内存泄漏,最终将导致您的程序因OOM而崩溃。
  • 我能做些什么吗?
  • 成功了!谢谢! @HansPassant
  • 不,它不起作用。你还在泄漏内存。
  • 哦,有什么办法可以解决吗?

标签: c# dll access-violation calling-convention cdecl


【解决方案1】:

您的问题很可能与您在 Delphi 中的 PChar 类型有关。在 C# 中,字符串默认是 Unicode,当调用你的 func 时,实际上会有一个从 PCharPWideChar 的转换,这意味着将分配一个新的内存块来保存这个新的 PWideChar。这种互操作性以及 .NET 和 Delphi 中处理字符串的方式之间的差异很可能导致您的 AccessViolationException

您可以使用MarshalAs 属性明确告诉.NET 如何处理特定类型:

[DllImport("SAT.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern string AssociarAssinatura(int numeroSessao, 
    [MarshalAs(UnmanagedType.LPStr)] string codigoDeAtivacao, [MarshalAs(UnmanagedType.LPStr)] string CNPJvalue, [MarshalAs(UnmanagedType.LPStr)] string assinaturaCNPJs);

这将明确指定如何处理字符串。之后,您的代码应该没问题。

【讨论】:

  • 遗憾的是它仍然抛出相同的异常。我应该尝试对字符串使用其他非托管类型吗?
  • 返回类型呢?它也应该被编组吗?
  • 您可以尝试将[return: MarshalAs(UnmanagedType.LPStr)] 添加到您的 pinvoke 签名中。
  • 我试过了,它确实没有任何区别......:/汉斯的方法“有效”,但它有内存泄漏。
  • 您可以尝试为字符串分配一个GCHandle,并在完成后强制GC 收集它。就我个人而言,我并不是这种方法的忠实拥护者,如果它发生在 CLR 之外,你可能仍然会遇到内存泄漏,但这是我现在能想到的最好的事情。汉斯的知识比我多得多,所以我相信他迟早会想出一些解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多