Ponteiros em C# para recuperar referência da função DllImport
-
23-09-2019 - |
Pergunta
Estou referenciando uma DLL em meu projeto C# da seguinte maneira:
[DllImport("FeeCalculation.dll", CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi)]
public static extern void FeeCalculation(string cin, string cout, string flimit,
string frate, string fwindow, string fincrement, string fbird,
string fparameter, string fvalidation, string fcoupon);
A função FeeCalculation é exportada da seguinte forma na DLL:
extern "C" __declspec(dllexport) void __stdcall FeeCalculation(char *cin,
char *cout, char *flimit, char *frate,
char *fwindow, char *fincrement, char *fbird,
char *fparameter, char *fvalidation, char *fcoupon);
A função DLL retorna uma referência às suas estruturas internas na forma de char * portanto, se você referenciasse essa DLL em C++, faria o seguinte para fazer o cálculo e obter as estruturas retornadas:
FeeCalculation(buff, (char *)&fans, (char *)fl, (char *)ft, (char *)fw, (char *)fi, (char *)fe, (char *)&fm, (char *)val, (char *)cpn);
Agora, como recupero os valores retornados por referência usando C#?Ou seja, como faço a mesma coisa em C# para obter as estruturas retornadas para obter meu cálculo retornado?Eu sei que preciso criar um método inseguro, mas não estou claro sobre como lidar com os endereços de memória em C# como você faria em C++.
Editar:Abaixo afirma para usar IntPtr, mas como você coloca em uma estrutura idêntica para que os campos da estrutura possam ser referenciados?
Editar:Aqui está a estrutura retornada na qual estou interessado (cout):
struct feeAnswer {
unsigned int fee;
unsigned int tax1;
unsigned int tax2;
unsigned int tax3;
unsigned int tax4;
unsigned int surcharge1;
unsigned int surcharge2;
unsigned int validationFee;
unsigned int couponFee1;
unsigned int couponFee2;
unsigned int couponFee3;
unsigned int couponFee4;
unsigned short int dstay; //Day Stay
unsigned short int mstay; //Minute Stay
};
Aqui está o (cin) que eu passaria junto com outras estruturas (elas são zero bytes no momento, quero fazer isso funcionar primeiro e depois implementarei o resto):
struct feeRequest {
unsigned char day;
unsigned char month;
unsigned int year; //2000 ~ 2099
unsigned char hour;
unsigned char minute;
unsigned char rate;
unsigned char validation;
unsigned char coupon1;
unsigned char coupon2;
unsigned char coupon3;
unsigned char coupon4;
};
Solução
Editar:agora que temos estruturas com as quais trabalhar, é possível encontrar uma solução melhor.Basta declarar estruturas em C# que correspondam às suas estruturas C++ e usá-las na declaração externa
[StructLayout(LayoutKind.Sequential)]
public struct feeAnswer {
public uint fee;
public uint tax1;
public uint tax2;
public uint tax3;
public uint tax4;
public uint surcharge1;
public uint surcharge2;
public uint validationFee;
public uint couponFee1;
public uint couponFee2;
public uint couponFee3;
public uint couponFee4;
public ushort dstay; //Day Stay
public ushort mstay; //Minute Stay
};
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct feeRequest {
public byte day;
public byte month;
public uint year; //2000 ~ 2099
public byte hour;
public byte minute;
public byte rate;
public byte validation;
public byte coupon1;
public byte coupon2;
public byte coupon3;
public byte coupon4;
};
[DllImport ("FeeCalculation.dll", CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi)]
public static extern void FeeCalculation (
feeRequest cin,
out feeAnswer cout,
...
....
resposta original (antes de termos estruturas) abaixo
Parece-me que estas não são referências a strings internas, mas sim ponteiros para buffers de strings que serão preenchidos pela chamada.Se você fosse retornando ponteiros de string, então estes seriam declarados char**
em vez de char*
.
Então eu acho que esses são apenas parâmetros padrão.Há muitos deles.Então sua interoperabilidade C# ficaria assim
[DllImport("FeeCalculation.dll", CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi)]
public static extern void FeeCalculation(string cin,
[MarshalAs(UnmanagedType.LPStr, SizeConst=100)]
out string cout,
[MarshalAs(UnmanagedType.LPStr, SizeConst=100)]
out string flimit,
ou isto se suas "strings" não forem realmente strings
[DllImport("FeeCalculation.dll", CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi)]
public static extern void FeeCalculation(string cin,
[MarshalAs(UnmanagedType.LPArray, SizeConst=100)]
out byte[] cout,
[MarshalAs(UnmanagedType.LPArray, SizeConst=100)]
out byte[] flimit,
....
Outras dicas
Os parâmetros char* neste caso não são strings, mas ponteiros para pedaços de bytes crus representando os dados. Você deve marcar seus parâmetros como instâncias do tipo intptr, em conjunto com Marshal.alochglobal para criar um pedaço de memória e depois Marshal.ptrtoStructure Para converter esse bloco de memória em um tipo .NET utilizável.
Como um exemplo:
[StructLayout(LayoutKind.Sequential)]
struct MyUnmanagedType
{
public int Foo;
public char C;
}
IntPtr memory = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyUnmanagedType)));
try
{
FeeCalculation(memory);
MyUnmanagedType result = (MyUnmanagedType)Marshal.PtrToStructure(
memory, typeof(MyUnmanagedType));
}
finally
{
Marshal.FreeHGlobal(memory);
}
Para responder à sua edição, você precisa criar uma estrutura e depois usar o StructLayoutattribute Nos campos, a fim de fazer a ordem de bytes e preencher o mesmo que a DLL original.