0

I'm trying to use PInvoke in order to call an unmanaged function from a C dll. Due to the fact that the source of the dll can't be released to developers, some of them have called the function in Delphi using the following declaration. We use SAT.dll with a CDECL calling convention.

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

Based on that structure, I made the following Console Application in C# in order to test the same function from the same DLL. I made some research and found out that the equivalent to Longint in delphi is int in C# and the equivalent of PChar is a pointer to a string (But I used C#'s string).

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);

    }
}

When I call the function, an AccesViolationException is thrown. The code of AssociarAssinatura has some inner prints that show that the code from the function is indeed running well. Due to this I guess the problem is related when the function is returning it's value. My best guess is that somehow I'm having issues with the calling convention. Any thoughts?

Levrak
  • 1
  • 3
  • It crashes when the pinvoke marshaller tries to release the returned string. You'd have to declare the return type as IntPtr and marshal it yourself with Marshal.PtrToStringAnsi(). But you still have no good way to release the string, that will become a memory leak that will ultimately crash your program with OOM. – Hans Passant Jul 04 '13 at 18:03
  • Is there anything I can do about it? – Levrak Jul 04 '13 at 18:06
  • No, it doesn't work. You are still leaking memory. – Hans Passant Jul 04 '13 at 19:27
  • Oh, is there any way to handle that? – Levrak Jul 04 '13 at 20:22

1 Answers1

0

Your problem here is most likely related to your PChar type in Delphi. In C#, strings are Unicode by default, and when calling your func, there will actually be a conversion from a PChar to PWideChar, which means a new block of memory will be allocated to hold this new PWideChar. This interop and difference between how strings are handled in .NET and in Delphi is more than likely causing your AccessViolationException.

You can use the MarshalAs attribute to explicitly tell .NET how to handle the specific type:

[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);

Which will explicitly specify how the string is handled. After that, your code should be fine.

aevitas
  • 3,753
  • 2
  • 28
  • 39
  • Sadly it's still throwing the same exception. Should I try using other unmanaged types for strings? – Levrak Jul 04 '13 at 16:28
  • What about the return type? Should it be marshalled as well? – Levrak Jul 04 '13 at 17:09
  • You could try adding `[return: MarshalAs(UnmanagedType.LPStr)]` to your pinvoke signature. – aevitas Jul 05 '13 at 11:55
  • I tried that and it really did no difference at all... :/ Hans's approach 'works' but it has a memory leak. – Levrak Jul 05 '13 at 17:54
  • You could try allocating a `GCHandle` for the string and forcing GC to collect it after you're done with it. Personally I'm not really a big fan of this approach, and chances are you'll still be left with your memory leak if it's happening outside of the CLR, but it's the best thing I can think of right now. Hans is far more knowledgeable than I am though, so I'm sure he'll come up with some solution sooner or later. – aevitas Jul 05 '13 at 20:28
  • Also, I'm not sure if you have access to the source of the library, but if you do you could consider making it an `StdCall` rather than a `Cdecl`. If you go with an `StdCall`, the callee cleans the stack and you won't have to worry about that in your managed code, rather than a `Cdecl` where the caller cleans the stack. – aevitas Jul 05 '13 at 21:09
  • Sadly, we can't have the source of the library... Thanks for all your input! – Levrak Jul 09 '13 at 16:14