我需要在C#中实现这个DLLImport
const char* PegaSolicitacao(const char* CNPJ,
const char* CPF,
const char* CRM,
const char* UF_CRM,
const char* DT_EMISSAO );
在.RAR \ GBASMSB_2-Client \ Ofd SDK里面0.2 Windows.zip - > gbasmsb_library.dll
我得到回报的唯一方法是使用以下代码:
[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"));
但是返回应该是字符串而不是字符。当我尝试在DLLImport中返回一个字符串时系统会中断,如果我尝试返回一个char [],我有一个异常告诉我有关Marshaling的事。
我是C#的新人,从未与MarshalAs合作过,但在论坛上我尝试了一些选项:
[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);
和其他一些变种,但我找不到合适的选择。
使用DLL
我使用DLL Export Viewer查看导出的函数。快速谷歌搜索产生了这些C导出定义:
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 );
由于它的简单性,我决定从IdentificaEstacao
开始,它应返回一个标识该站的标识符。
我已经尝试了所有类型的返回MarshalAs
,CharSet
和CallingConvention
值,但无法使用返回string
类型的导入。所以我们将返回类型更改为IntPtr
:
[DllImport("gbasmsb_library.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr IdentificaEstacao();
现在,当调用该函数时,您将返回指向内存地址的IntPtr
。检查该内存位置的内容(在断点处暂停时,调试> Windows>内存>内存1),您可以看到一个单字节,以空字符结尾的字符串(看起来像Base64数据)。
我尝试使用其中一种Marshal.Free...
方法释放它,但这不起作用。我已经多次调用相同的方法,每次我们在IntPtr
中返回相同的内存地址时,我猜他们正在使用全局分配的字符串,不应该被调用者释放(也可能是string
返回类型不起作用的原因)。
使用下面的代码,我们可以获得电台标识符:
var ptr = IdentificaEstacao();
var stationIdentifier = Marshal.PtrToStringAnsi(ptr);
让我们以同样的方式更改其他导入的签名:
[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);
并进行此测试调用:
var ptr = PegaSolicitacao("31617905000139",
"99999999484",
"30828",
"SP",
DateTime.Today.ToString("d"));
这又返回一个指向静态字符串的指针(多次调用它返回相同的内存地址),所以你可以通过再次调用Marshal.PtrToStringAnsi(ptr);
来获得结果。
作为一个额外的测试,我已经在一个紧凑的循环中运行它,似乎没有内存泄漏,所以这应该是一个非常安全的方式来调用导入的函数。
请注意,我已将CallingConvention
从StdCall
更改为Cdecl
,因此我们可以使用string
作为输入参数,而不会获得不平衡的堆栈异常。
使用EXE
我还注意到该存档包含一个可执行的gbasmsb_gbas.exe
,它可以执行相同的功能。
gbasmsb_gbas.exe --i
为您提供电台标识符,而gbasmsb_gbas.exe --solicitacao 99999999484 31617905000139 30828 SP 12/03/2019
返回请求信息。
调用EXE并解析输出也是一种可能的集成路径,不太容易破坏对外部库的未来更新的更改。