convention d'appel personnalisée P / Invoke et C #
-
23-08-2019 - |
Question
J'ai une étude de cas où je dois être en mesure de préciser ma propre convention d'appel lors de l'utilisation P / Invoke. Plus précisément, j'ai un dll héritage qui utilise un ABI non standard, et je dois en mesure de préciser la convention d'appel pour chaque fonction.
Par exemple, une fonction dans ce dll accepte ses deux premiers arguments via EAX et EBX, le reste par pile. Une autre fonction accepte un argument via ECX, avec le reste sur la pile. J'ai quelques centaines de ces fonctions, et je voudrais éviter d'écrire ma propre DLL pont intermédiaire pour accéder à ces fonctions.
Mon autre option serait de la main rouler ma propre P / Invoke, ce qui est indésirable sur mesure pour des raisons évidentes.
Toute aide est appréciée, merci,
La solution
Je ne comprends pas ce que vous entendez avec P / Invoke, mais je ne vois pas coutume comment vous pourriez partir sans C ++ non gérés avec l'assembleur en ligne. Cependant, étant donné que presque tout est passé en tant que valeurs 32 bits, vous pouvez vous en sortir avec l'écriture une seule procuration pour chaque signature de la fonction, par opposition à une par fonction. Ou vous pourriez écrire un générateur de code qui génère des proxys de XML. Je ne vois pas cette version de trop indésirable cependant, puisque toutes les fonctions de proxy seront très simple:
int RealFunction(int param1, const char * param2, char param 3);
int MyFunction(int param1, int param2, int param3) { // argument types do not matter as long as they are not doubles or structures
__asm {
mov eax, param1
mov ebx, param2
push param3
call RealFunction
; depending on calling convention, you might need to do add esp, 12 here
; if RealFunction does not return its result in eax, you will need to do mov eax, <wherever the return value is> here
}
}
Autres conseils
Je suis assez certain qu'il n'y a aucun moyen d'accomplir ce que builtin vous voulez sans dll séparée. Je ne l'ai pas vu un moyen de spécifier une convention d'appel autre que ce qui prend en charge le système d'exécution.
J'ai appris à appeler conventions un certain temps et écrit un code pour convertir les conventions d'appel. Le code est appelé à partir d'une enveloppe spéciale C #, la bibliothèque wrapper utilise l'émission de réflexion (ne pouvait pas faire fonctionner Marshal.getdelegateforfunctionpointer) pour émettre une nouvelle méthode p / Invoke pour la méthode stub nu spécial. Il fixe les paramètres et appelle ensuite la méthode réellement.
Voici le code c. Je n'ai pas le C # partie pratique :( Je suis assembleur apprendre aussi à l'époque, le code peut aspirer:)
typedef struct
{
USHORT ParameterOneOffset; // The offset of the first parameter in dwords starting at one
USHORT ParameterTwoOffset; // The offset of the second parmaeter in dwords starting at one
} FastCallParameterInfo;
__declspec( naked,dllexport ) void __stdcall InvokeFast()
{
FastCallParameterInfo paramInfo;
int functionAddress;
int retAddress;
int paramOne, paramTwo;
__asm
{
// Pop the return address and parameter info. Store in memory.
pop retAddress;
pop paramInfo;
pop functionAddress;
// Check if any parameters should be stored in edx
movzx ecx, paramInfo.ParameterOneOffset;
cmp ecx,0;
je NoRegister;
// Calculate the offset for parameter one.
movzx ecx, paramInfo.ParameterOneOffset; // Move the parameter one offset to ecx
dec ecx; // Decrement by 1
mov eax, 4; // Put 4 in eax
mul ecx; // Multiple offset by 4
// Copy the value from the stack on to the register.
mov ecx, esp; // Move the stack pointer to ecx
add ecx, eax; // Subtract the offset.
mov eax, ecx; // Store in eax for later.
mov ecx, [ecx]; // Derefernce the value
mov paramOne, ecx; // Store the value in memory.
// Fix up stack
add esp,4; // Decrement the stack pointer
movzx edx, paramInfo.ParameterOneOffset; // Move the parameter one offset to edx
dec edx; // Decrement by 1
cmp edx,0; // Compare offset with zero
je ParamOneNoShift; // If first parameter then no shift.
ParamOneShiftLoop:
mov ecx, eax;
sub ecx, 4;
mov ecx, [ecx]
mov [eax], ecx; // Copy value over
sub eax, 4; // Go to next
dec edx; // decrement edx
jnz ParamOneShiftLoop; // Loop
ParamOneNoShift:
// Check if any parameters should be stored in edx
movzx ecx, paramInfo.ParameterTwoOffset;
cmp ecx,0;
je NoRegister;
movzx ecx, paramInfo.ParameterTwoOffset; // Move the parameter two offset to ecx
sub ecx, 2; // Increment the offset by two. One extra for since we already shifted for ecx
mov eax, 4; // Put 4 in eax
mul ecx; // Multiple by 4
// Copy the value from the stack on to the register.
mov ecx, esp; // Move the stack pointer to ecx
add ecx, eax; // Subtract the offset.
mov eax, ecx; // Store in eax for later.
mov ecx, [ecx]; // Derefernce the value
mov paramTwo, ecx; // Store the value in memory.
// Fix up stack
add esp,4; // Decrement the stack pointer
movzx edx, paramInfo.ParameterTwoOffset; // Move the parameter two offset to ecx
dec edx; // Decrement by 1
cmp edx,0; // Compare offset with zero
je NoRegister; // If first parameter then no shift.
ParamTwoShiftLoop:
mov ecx, eax;
sub ecx, 4;
mov ecx, [ecx]
mov [eax], ecx; // Copy value over
sub eax, 4; // Go to next
dec edx; // decrement edx
jnz ParamTwoShiftLoop; // Loop
NoRegister:
mov ecx, paramOne; // Copy value from memory to ecx register
mov edx, paramTwo; //
push retAddress;
jmp functionAddress;
}
}