... хотел написать предысторию, но стер, решив не тратить на бурду Ваше время...
сразу к делу.
использовав превосходную штуку от Хакера FireNativeDll, создал следующую прогу:
- Код: Выделить всё
'cSimpleInterface.cls
'простенький интерфейс из одного метода
Option Explicit
'0 - QueryInterface
'1 - AddRef
'2 - Release
'3 - ?
'4 - ?
'5 - ?
'6 - ?
'7 - virtual int __stdcall Mult(int x, int y) = 0;
Public Function Mult(ByVal x As Long, ByVal y As Long) as Long 'простой метод: получить два числа, перемножить, вернуть результат умножения
End Function
- Код: Выделить всё
'cSimpleClass.cls
'реализация простенького интерфейса
Option Explicit
Implements cSimpleInterface
'virtual int __stdcall Mult(int x, int y) = 0;
Public Function cSimpleInterface_Mult(ByVal x As Long, ByVal y As Long) as Long
On Error Resume Next
Dim Result As Long
Result = x * y
'MsgBox "x = " + CStr(x) + ", y = " + CStr(y) + "; Result = " + CStr(Result) -- если раскомментарить, то отработает нормально и покажет правильный результат (см. ниже)
cSimpleInterface_Mult = Result
End Function
- Код: Выделить всё
'modLibrary.bas
'описание экспортируемых функций
Option Explicit
Public Declare Function GetMem4 Lib "msvbvm60" (ByVal pSrc As Long, ByVal pDst As Long) As Long
Public Declare Function PutMem4 Lib "msvbvm60" (ByVal pDst As Long, ByVal NewValue As Long) As Long
Private cls As cSimpleInterface
Private Const NCOMMethods As Integer = 7
Private Const DLL_PROCESS_ATTACH As Long = 1
Private Const DLL_PROCESS_DETACH As Long = 0
Private Const DLL_THREAD_ATTACH As Long = 2
Private Const DLL_THREAD_DETACH As Long = 3
Public Function DllEntryPoint(ByVal hInstance As Long, ByVal lReason As Long, ByVal lReserved As Long) As Long
Select Case lReason
Case DLL_PROCESS_ATTACH
DllEntryPoint = 1
Case DLL_PROCESS_DETACH
Case DLL_THREAD_ATTACH
Case DLL_THREAD_DETACH
End Select
End Function
'extern "C" void __stdcall GetClass (void ** p_class)
'выдает указатель на класс, имплементирующий простенький интерфейс, исключая из него методы СОМ-интерфейсов
Public Sub GetClass(ByRef p_class As Long)
Dim pVTbl As Long
Set cls = New cSimpleClass
p_class = ObjPtr(cls) 'берем указатель на класс
GetMem4 p_class, VarPtr(pVTbl) 'получаем указатель на таблицу виртуальных методов
PutMem4 p_class, pVTbl + 4 * NCOMMethods 'смещаем указатель на количество методов, принадлежащих СОМ-интерфейсам, чтобы он указывал на таблицу только нашего простенького интерфейса
End Sub
ну и в CStdDLLInfo.cls -
.export "GetClass", AddressOf GetClass
всё это добро компилится в DLL и используется С++-ой прогой примерно следующего содержания:
- Код: Выделить всё
//main.cpp
#include <windows.h>
#include <iostream>
using namespace std;
class ISimpleInterface //аналог простого интерфейса
{
public:
virtual int __stdcall Mult(int x, int y) = 0;
};
extern "C" void (__stdcall *pGetClass) (void ** p_class) = NULL; //указатель на импортируемую функцию
void main()
{
HMODULE hSrv = NULL;
hSrv = ::LoadLibrary("dll_vb.dll");
if(!hSrv)
{
cout << "Can't load dll\n";
}
else
{
pGetClass = reinterpret_cast<void (__stdcall *) (void ** p_class)> (::GetProcAddress(hSrv,"GetClass"));
if(!pGetClass)
{
cout << "Can't get GetClass address\n";
}
else
{
cout << "GetClass address: " << pGetClass << "\n";
ISimpleInterface * p_intr;
(*pGetClass)((void **)&p_intr); //получаем указатель на объект, имплементирующий интерфейс
cout << "GetClass() finished\n";
cout << "p_intr = " << p_intr << "\n";
int x = 4, y = 5, ret = 0;
ret = p_intr->Mult(x,y); //пытаемся перемножить два числа
cout << "p_intr->Mult(4,5) = " << ret << "\n";
};
};
}
метод Mult вызывается нормально, проверял по-всякому, да хотя бы впихнутый в него MsgBox - отрабатывает и результат умножения показывает корректно.
суть проблемы: метод Mult возвращает своё Long-овое значение не понимаю как (в ret записывается ноль всегда)... Ведь каждый класс в VB - СОМ-объект? а у методов СОМ-объектов stdcall? а в stdcall 4-байтовое целое возвращается в eax? судя по дизассемблированному коду, который показывает студия, прога на С++ так и ожидает получить результат - после вызова метода делает mov dword ptr [ret], eax. Но судя по тому же дизассемблированию, результат функции Mult кладется непойми куда (в какой-то адрес памяти, но не в стек и не в регистр, насколько мне позволяют судить скромные познания асма).
Бился сегодня весь день, честно пытался докопаться до решения самостоятельно через форумы, МСДН, гугль... узнал за день больше чем за всё лето, но как решить проблему так и не понял, более того - даже не понял в чем проблема, почему так выходит, ведь должно быть по-другому...
помогите, пожалуйста, люди добрые, если не решить проблему, то хотя бы понять в чем она заключается)) заранее искренне благодарен всем откликнувшимся!
п.с. кстати, обычные функции (не методы класса) в VB-шных DLL-ях прекрасно возвращают Long-овые значения. В eax.
UPD.
решил все-таки добавить немного дизассемблированного кода, может быть это важно...
- Код: Выделить всё
; ret = p_intr->Mult(x,y);
; вызов метода Mult по самому первому адресу из VTbl
0040403E mov eax,dword ptr [y]
00404044 push eax
00404045 mov ecx,dword ptr [x]
0040404B push ecx
0040404C mov edx,dword ptr [p_intr]
00404052 mov eax,dword ptr [edx]
00404054 mov ecx,dword ptr [p_intr]
0040405A push ecx
0040405B mov edx,dword ptr [eax]
0040405E call edx
00404060 mov dword ptr [ret],eax ; вот тута ждут результат, если я правильно понял
<...>
; собственно, что происходит после call-a...
; здесь быломного совершенно непонятного для меня кода, наверное связанного с инициализацией переменных; я его вырезал
; хотя назначение дальнейшего кода мне тоже непонятна, за исключением нескольких строчек...
11004342 mov edx,dword ptr [ebp+0Ch] ; вот тут начинаются вычисления; [ebp+0Ch] - это адрес ByVal x As Long
11004345 imul edx,dword ptr [ebp+10h] ; [ebp+10h] - адрес ByVal y As Long
11004349 jo 1100438C
1100434B mov dword ptr [ebp-24h],edx ; предполагаю, что [ebp-24h] - это адрес Dim Result As Long
1100434E mov dword ptr [ebp-4],4
11004355 mov eax,dword ptr [ebp-24h]
11004358 mov dword ptr [ebp-28h],eax
1100435B mov dword ptr [ebp-10h],0 ; вот он, этот нолик, который ниже перерастет в eax и в итоге запишется в переменную int ret
11004362 mov ecx,dword ptr [ebp+8]
11004365 mov edx,dword ptr [ecx]
11004367 mov eax,dword ptr [ebp+8]
1100436A push eax
1100436B call dword ptr [edx+8]
1100436E mov ecx,dword ptr [ebp+14h]
11004371 mov edx,dword ptr [ebp-28h]
11004374 mov dword ptr [ecx],edx ; ! а вот тут ! иногда (закономерность установить не смог) вылетает Access violation; а иногда не вылетает...
11004376 mov eax,dword ptr [ebp-10h] ; записывается нолик
11004379 mov ecx,dword ptr [ebp-20h]
1100437C mov dword ptr fs:[0],ecx
11004383 pop edi
11004384 pop esi
11004385 pop ebx
11004386 mov esp,ebp
11004388 pop ebp
11004389 ret 10h