Samir Abreu
Samir Abreu

Reputation: 19

C# DLLImport convert "const char*" to string

I need to implement this DLLImport in C#

const char* PegaSolicitacao(const char* CNPJ,
                            const char* CPF,
                            const char* CRM,
                            const char* UF_CRM,
                            const char* DT_EMISSAO );

The Dll could be found at this link https://farmaciapopular-portal-homologacao.saude.gov.br/farmaciapopular-portal/gbas/GBASMSB_2-Client.rar

Inside the .RAR \GBASMSB_2-Client\Ofd SDK 0.2 Windows.zip -> gbasmsb_library.dll

The only way that i got a return was with this code:

        [DllImport(@"gbasmsb_library.dll")]
    public static extern char PegaSolicitacao(string CNPJ,
                                              string CPF,
                                              string CRM,
                                              string UF_CRM,
                                              string DT_Emissao);

            var Teste = PegaSolicitacao("31617905000139",
                                    "99999999484",
                                    "30828",
                                    "SP",
                                    DateTime.Today.ToString("d"));

But the return is suposed to be a string not a char. When I tried return a string in the DLLImport the system breaks, if I try to return a char[] I got a exception telling me about Marshaling.

Im, new at C# and never worked with MarshalAs, but looking at the Forum I tried some option like:

                [DllImport(@"gbasmsb_library.dll", CharSet = CharSet.Ansi)]
    [return: MarshalAs(UnmanagedType.LPTStr)]
    public static extern char[] PegaSolicitacao([MarshalAs(UnmanagedType.LPArray)]char[] CNPJ,
                                                [MarshalAs(UnmanagedType.LPArray)]char[] CPF,
                                                [MarshalAs(UnmanagedType.LPArray)]char[] CRM,
                                                [MarshalAs(UnmanagedType.LPArray)]char[] UF_CRM,
                                                [MarshalAs(UnmanagedType.LPArray)]char[] DT_Emissao);

and some other variants too, but I cant find the right option.

Upvotes: 1

Views: 3585

Answers (1)

huysentruitw
huysentruitw

Reputation: 28111

Using the DLL

I've used DLL Export Viewer to see the exported functions. A quick google search resulted in these C export definitions:

const char* IdentificaEstacao();
const char* PegaSolicitacao( const char* CNPJ, const char* CPF, const char* CRM, const char* UF_CRM, const char* DT_EMISSAO );
const char* PegaConfirmacao( const char* CNPJ, const char* NU_AUTORIZACAO, const char* NU_CUPOM_FISCAL );

Because of its simplicity, I decided to start with IdentificaEstacao which should return an identifier that identifies the station.

I've tried all kind of return MarshalAs, CharSet and CallingConvention values, but couldn't get it working with an import that returns a string type. So let's change the return type to IntPtr instead:

[DllImport("gbasmsb_library.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr IdentificaEstacao();

Now, when calling that function, you'll get back an IntPtr that points to a memory address. When checking the content of that memory location (Debug > Windows > Memory > Memory1 while pausing on a breakpoint), you can see a single-byte, null-terminated string (looks like Base64 data).

I tried freeing it with one of the Marshal.Free... methods, but that didn't work. I've called the same method several times and each time we're getting back the same memory address in the IntPtr which makes me guess that they're using a global allocated string that should not be freed by the caller (that might also be the reason why the string return type doesn't work).

With the code below, we're able to get the station identifier:

var ptr = IdentificaEstacao();
var stationIdentifier = Marshal.PtrToStringAnsi(ptr);

Let's change the signature of the other import in the same way:

[DllImport("gbasmsb_library.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr PegaSolicitacao(
    [MarshalAs(UnmanagedType.LPStr)] string CNPJ,
    [MarshalAs(UnmanagedType.LPStr)] string CPF,
    [MarshalAs(UnmanagedType.LPStr)] string CRM,
    [MarshalAs(UnmanagedType.LPStr)] string UF_CRM,
    [MarshalAs(UnmanagedType.LPStr)] string DT_Emissao);

And make this test call:

var ptr = PegaSolicitacao("31617905000139",
                           "99999999484",
                           "30828",
                           "SP",
                           DateTime.Today.ToString("d"));

This again returns a pointer to a static string (calling it multiple times returns the same memory address), so you can just get the result by calling Marshal.PtrToStringAnsi(ptr); again.

As an additional test, I've ran this in a tight loop and there seems to be no memory leak, so this should be a pretty safe way of calling the imported function(s).

Note that I have changed the CallingConvention from StdCall to Cdecl so we can use string as input parameters without getting the unbalanced stack exception.

Using the EXE

I've also noticed that the archive contains an executable gbasmsb_gbas.exe which can perform the same functions.

gbasmsb_gbas.exe --i gives you the station identifier, while gbasmsb_gbas.exe --solicitacao 99999999484 31617905000139 30828 SP 12/03/2019 returns the request info.

Calling the EXE and parsing the output is also a possible integration path that is less prone to breaking changes for future updates of that external library.

Upvotes: 3

Related Questions