/ Bater de C ++ chamada de retorno para a função de violação de acesso de exceção C #
-
05-07-2019 - |
Pergunta
Então, eu tenho uma festa de 3 C ++ base de código nativo Eu estou trabalhando com (lib e os arquivos .hpp) que eu usei para construir um invólucro em C ++ / CLI para eventual uso em C #.
eu correr em um problema particular quando se muda de depuração para o modo Release, em que eu recebo uma violação de acesso de exceção quando um retorno de chamada de código retorna.
O código a partir dos arquivos HPP originais para o formato de função de retorno:
typedef int (*CallbackFunction) (void *inst, const void *data);
Código do ++ Wrapper C / CLI para o formato de função de retorno: (Eu vou explicar porque eu declarei dois em um momento)
public delegate int ManagedCallbackFunction (IntPtr oInst, const IntPtr oData);
public delegate int UnManagedCallbackFunction (void* inst, const void* data);
- Rapidamente, a razão de eu declarou um segundo "UnManagedCallbackFunction" é que eu tentei criar um callback "intermediário" na embalagem, de modo a cadeia mudou de Native C ++> C # para uma versão do Native C ++> C ++ / CLI Wrapper> C # ... a divulgação completa, o problema ainda vive, é apenas sido empurrado para a C ++ / CLI wrapper agora na mesma linha (o retorno).
E, finalmente, o código bater de C #:
public static int hReceiveLogEvent(IntPtr pInstance, IntPtr pData)
{
Console.WriteLine("in hReceiveLogEvent...");
Console.WriteLine("pInstance: {0}", pInstance);
Console.WriteLine("pData: {0}", pData);
// provide object context for static member function
helloworld hw = (helloworld)GCHandle.FromIntPtr(pInstance).Target;
if (hw == null || pData == null)
{
Console.WriteLine("hReceiveLogEvent: received null instance pointer or null data\n");
return 0;
}
// typecast data to DataLogger object ptr
IntPtr ip2 = GCHandle.ToIntPtr(GCHandle.Alloc(new DataLoggerWrap(pData)));
DataLoggerWrap dlw = (DataLoggerWrap)GCHandle.FromIntPtr(ip2).Target;
//Do Logging Stuff
Console.WriteLine("exiting hReceiveLogEvent...");
Console.WriteLine("pInstance: {0}", pInstance);
Console.WriteLine("pData: {0}", pData);
Console.WriteLine("Setting pData to zero...");
pData = IntPtr.Zero;
pInstance = IntPtr.Zero;
Console.WriteLine("pData: {0}", pData);
Console.WriteLine("pInstance: {0}", pInstance);
return 1;
}
Todas as gravações para o console são feitas e, em seguida, vemos a queda temido no retorno:
Excepção em 0x04d1004c em Helloworld.exe: 0xC0000005: Acesso violação localização leitura 0x04d1004c.
Se eu passo para o depurador a partir daqui, tudo que eu vejo é que a última entrada na pilha de chamadas é:> "04d1004c ()", que é avaliada como um valor decimal: 80805964
O que só é interessante se você olhar para o console que mostra:
entering registerDataLogger
pointer to callback handle: 790848
fp for callback: 2631370
pointer to inst: 790844
in hReceiveLogEvent...
pInstance: 790844
pData: 80805964
exiting hReceiveLogEvent...
pInstance: 790844
pData: 80805964
Setting pData to zero...
pData: 0
pInstance: 0
Agora, eu sei que entre depuração e liberar algumas coisas são bem diferentes no mundo da Microsoft. Estou, naturalmente preocupado com estofo byte e inicialização de variáveis, por isso, se há algo que eu não estou fornecendo aqui, apenas deixe-me saber e eu vou adicionar a (já longa) post. Eu também acho que o código gerenciado não pode estar liberando toda a propriedade e, em seguida, o nativo C coisas ++ (que eu não tenho o código para) pode estar tentando excluir ou matar o objeto pData, falhando assim o aplicativo.
divulgação mais completa, tudo funciona bem (aparentemente) no modo de depuração!
Uma questão zero cabeça real que gostaria de receber qualquer ajuda!
Solução
Eu acho que a pilha foi esmagado por causa de descasamento entre convenções de chamada: tentar colocar o atributo
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
na declaração callback delegado.
Outras dicas
Este não responder diretamente sua pergunta , mas pode levá-lo na direção certa, na medida do modo de liberação vs. bem modo de depuração não está bem:
Uma vez que o depurador acrescenta um lote de manutenção de registos informações para a pilha, geralmente acolchoar o tamanho e layout do meu programa na memória, I foi “ter sorte” no modo de depuração por rabiscar mais de 912 bytes de memória que weren' t muito importante. Sem o depurador, porém, eu estava rabiscando em cima de coisas bastante importantes, eventualmente, andando fora do meu próprio espaço de memória, causando Interop para a memória de exclusão que não possuía.
O que é a definição de DataLoggerWrap? Um campo de char pode ser demasiado pequena para os dados que você está recebendo.
Eu não sei o que você está tentando alcançar.
Alguns pontos:
1) O coletor de lixo é mais agressivo no modo de versão assim com mau posse o comportamento que você descreve não é incomum.
2) Eu não entende o que o código abaixo está a tentar fazer?
IntPtr ip2 = GCHandle.ToIntPtr(GCHandle.Alloc(new DataLoggerWrap(pData)));
DataLoggerWrap dlw = (DataLoggerWrap)GCHandle.FromIntPtr(ip2).Target;
Você usa GCHandle.Alloc para bloquear uma instância de DataLoggerWrap na memória, mas então você nunca passá-lo para fora para não gerenciado - então por que você bloqueá-lo? Você também nunca libertá-lo?
A segunda linha, em seguida, pega de volta uma referência - porque o caminho circular? por isso que a referência - você nunca usá-lo
3) Você definir as IntPtrs para null - por quê? -. Isto não terá qualquer efeito fora do escopo da função
4) Você precisa saber o que o contrato de retorno de chamada é. Quem possui pData o retorno de chamada ou a função chamando?
Eu estou com @jdehaan, exceto CallingConvetion.StdCall poderia ser a resposta, especialmente quando a 3ª lib partido está escrito no BC ++, por exemplo.