Trying to create a C++ DLL that I can call from Visual Basic

Dec 27, 2011 at 3:46am
I’m trying to create a C++ DLL that I can call from Visual Basic. I need to use inline ASM and C++ seems to be the best option for what I want to do.

I have (I think) the gist of the idea but I am completely unfamiliar with C++. I’ve been programming for years in various flavors of Visual Basic but that isn’t helping me so far.

I’ve attached the code. Can someone point me in the right direction? I’ve been using Google and trying various tutorials all day but something is still amiss.

I need this to return a string to the VB app.

// testdll.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include "stdio.h"
#include <string>
#include "windows.h"
using std::string;

BOOL APIENTRY DllMain( HANDLE /*hModule*/,
DWORD ul_reason_for_call,
LPVOID /*lpReserved*/
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

__declspec(dllexport) char * DodgeCherokee (LONG szMileage){

long Miles;
string ReturnString;
char * Mypointer;

__asm
{

// :00402B74 push 000003EA
// :00402B79 push esi
// :00402B7A call USER32.SendDlgItemMessageA
mov edx, dword ptr [esp+0x1C]
dec eax
dec eax
neg eax
sbb eax, eax
inc eax
push eax
push edx
push esi
// call PRO.004024C0
sub esp, 0x0000021C
push esi
// :004024C7 mov esi, dword ptr [esp+00000228]
mov esi, szMileage
cmp esi, 0x000F423F
// :004024D4 jbe 0040250D
//not going to check max here
// :004024D4 jmp PRO.0040250D

mov eax, esi
xor edx, edx
mov ecx, 0x00000064
push ebx
div ecx
mov eax, 0xD1B71759
mov eax, 0xD1B71759
push edi
mov bl, dl
mul esi
mov eax, edx
xor edx, edx
shr eax, 0x0D
div ecx
mov eax, 0x51EB851F
mov cl, dl
mul esi
mov eax, edx
xor edx, edx
shr eax, 05
mov esi, 0x00000064
mov esi, 0x00000064
mov byte ptr [esp+0x0C], cl
div esi
test dl, dl
mov byte ptr [esp+0x24], dl
// :0040254D je PRO.0040255B

dec dl
mov byte ptr [esp+0x10], cl
mov byte ptr [esp+0x20], dl
// :00402559 jmp PRO.00402571

mov edx, dword ptr [esp+0x0C]
mov esi, dword ptr [0x004080E4]
and edx, 0x000000FF
lea eax, dword ptr [esp+0x14]
push edx
push 0x0040A688
push 0x0040A688
push eax
// call USER32.wsprintfA

mov ecx, dword ptr [esp+0x30]
lea edx, dword ptr [esp+0x28]
and ecx, 0x000000FF
push ecx
push ecx
push 0x0040A688
push edx
// call USER32.wsprintfA
mov eax, dword ptr [esp+0x28]
lea ecx, dword ptr [esp+0x24]
and eax, 0x000000FF
push eax
push eax
push 0x0040A688
push ecx
// call USER32.wsprintfA
mov edx, dword ptr [esp+0x44]
lea eax, dword ptr [esp+0x3C]
and edx, 0x000000FF
push edx
push 0x0040A688
push 0x0040A688
push eax
// call USER32.wsprintfA
mov cl, byte ptr [esp+0x4D]
mov dl, byte ptr [esp+0x4C]
mov al, byte ptr [esp+0x44]
sub cl, 0x30
shl dl, 0x04
or cl, dl
mov dl, byte ptr [esp+0x45]
shr bl, 0x1
movzx di, bl
sub dl, 0x30
add esp, 0x00000030
shl al, 0x04
or dl, al
xor esi, esi
test di, di
mov byte ptr [esp+0x00000128], cl
mov byte ptr [esp+0x00000129], dl
mov eax, 0x00000002
// :00402613 jbe 00402648

mov esi, edi
and esi, 0x0000FFFF
mov edi, esi

PRO_0040261F: mov ebx, eax
and ebx, 0x0000FFFF
inc eax
mov byte ptr [esp+ebx+0x00000128], cl
mov ebx, eax
and ebx, 0x0000FFFF
inc eax
dec edi
mov byte ptr [esp+ebx+0x00000128], dl
jne PRO_0040261F
mov Mypointer,ecx
ret
}

return Mypointer;

}



__declspec(dllexport) char * testme (LPCTSTR test){
char * st;
strcpy(st ,"this is a test");
return st;
}
Dec 27, 2011 at 6:01am
closed account (S6k9GNh0)
Given the nature of C++ to encode every symbol using some sort of mangling system, you need to make sure you wrap any function you wish to export in C functions (and declared as such using extern "C" or it will still be mangled).

However, I'm not familiar with Visual Basic (or any Basic variant for that matter) and I'm unsure of the calling convention for such a language. I'm also no longer familiar with the Windows version of the shared library.
Last edited on Dec 27, 2011 at 6:01am
Dec 27, 2011 at 7:12am
Beware that your dll code has problems, your "testme" function does not allocate memory before strcpy call and will most likely crash.
Also, your switch in dllmain is useless.
Dec 27, 2011 at 10:55am
you need to add extern "C" before functions, or add it in this way:
1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern "C" {
#endif

// you whole code

#ifdef __cplusplus
}
#endif 


or you can save file name with *.C extension so compiler will compile it with C rules.
Dec 27, 2011 at 4:38pm
If you change your c++ dll to a com dll then vb will definelty be able to use it. However, implementing com can be quite tricky and would suggest that you only apply the required com practices if your request is small enough as seems to be the case with your example above.

One such practice that may be applicable to example provided is that you ensure that your parameters / return types are all com compliant. For instance, it appears as if your function DodgeCherokee is returning an old c-string. If I remember correctly, com uses a b-string (BSTR) instead. This is a [short *] in c++ terms. Each character of a com string is 16 bits in length as opposed to 8 bits.

I'm also not sure if returning a BSTR from your function is going to be safe in all cases - if not try passing the BSTR as a reference/pointer paramter and make the return type of your function a simple success or fail.

Try finding an example that demonstrates how to code a very simple com dll by hand - this may help you apply the required principals to your dll.

Alternatively, if you are using VB .NET then I would suggest writing a simple managed wrapper for DodgeCherokee and compile into an assembly. Then your vb .net project can simply reference and use the assembly. Usually oe can wrap native code in a managed singleton very successfully (ie very easily then used from other projects).
Dec 27, 2011 at 4:57pm
All,
Thanks for all of your answers on this. I really appreciate it. I was unaware of the “strcpy” memory allocation requirements. I will take another look at that. Also (majidkamali1370) I did see some online sites that talked about the method you describe. I’m going to check that way out also. If it is easier, I’ll do it that way.

The ASM routine creates a string in memory that I need to (somehow) transfer to C++ and then back to my VB program. I think I can do that by using the ASM memory pointer?

After sending the initial message, I did some more research and found this web site:
http://www.codeproject.com/KB/DLL/XDllPt2.aspx

Using the instructions it contained, I was able to make a working DLL that I could call from VB. I used their test function initially then plugged my ASM code back in. When I did that I started getting errors again so I probably fat fingered something. I pasted a copy of the errors below.

One of the problems I had was that I needed to make a .DEF file and I needed to include a “.h” file with the same name as the .DLL I created. I also had to add “__stdcall” in the procedure/function.

One interesting thing is that I can only (seemingly) create a reference to one routine at a time in the C++ DLL. As it looks right now, I can’t seem to create a reference to the whole thing and call whatever routine I want. So if the DLL has 100 routines, I’ll have to make individual references to each.

I

Deleting intermediate files and output files for project 'testdll - Win32 Debug'.
--------------------Configuration: testdll - Win32 Debug--------------------
Compiling...
StdAfx.cpp
Compiling...
testdll.cpp
C:\VB6 programs\Devstudio 9A\cplus_proj\testdll\testdll.cpp(12) : error C2143: syntax error : missing ';' before 'using'
C:\VB6 programs\Devstudio 9A\cplus_proj\testdll\testdll.cpp(12) : warning C4229: anachronism used : modifiers on data are ignored
C:\VB6 programs\Devstudio 9A\cplus_proj\testdll\testdll.cpp(41) : error C2491: 'GetCpuSpeed' : definition of dllimport function not allowed
C:\VB6 programs\Devstudio 9A\cplus_proj\testdll\testdll.cpp(49) : error C2373: 'DodgeCherokee' : redefinition; different type modifiers
C:\VB6 programs\Devstudio 9A\cplus_proj\testdll\testdll.cpp(12) : see declaration of 'DodgeCherokee'
C:\VB6 programs\Devstudio 9A\cplus_proj\testdll\testdll.cpp(49) : error C2491: 'DodgeCherokee' : definition of dllimport function not allowed
C:\VB6 programs\Devstudio 9A\cplus_proj\testdll\testdll.cpp(185) : error C2440: 'return' : cannot convert from 'char *' to 'int'
This conversion requires a reinterpret_cast, a C-style cast or function-style cast
C:\VB6 programs\Devstudio 9A\cplus_proj\testdll\testdll.cpp(188) : warning C4035: 'DodgeCherokee' : no return value
Error executing cl.exe.

testdll.dll - 5 error(s), 2 warning(s)

The current code:
// testdll.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include "stdio.h"
#include <string>
#include "windows.h"
#include "stdafx.h"
#include "testdll.h"
#define testdll_EXPORTS

using std::string;
//using namespace std;

// testdll.cpp : Defines the entry point for the DLL application.
//


BOOL APIENTRY DllMain( HANDLE /*hModule*/,
DWORD ul_reason_for_call,
LPVOID /*lpReserved*/
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// Example of an exported function
///////////////////////////////////////////////////////////////////////////////
// GetCpuSpeed - returns CPU speed in MHz; for example, ~2193 will be
// returned for a 2.2 GHz CPU.
testdll_API int __stdcall GetCpuSpeed()
{
Sleep(1000);
return 1000;
}



DodgeCherokee_API int __stdcall DodgeCherokee (LONG szMileage)
{

long Miles;
string ReturnString;
char * Mypointer;

__asm
{

// :00402B74 push 000003EA
// :00402B79 push esi
// :00402B7A call USER32.SendDlgItemMessageA
mov edx, dword ptr [esp+0x1C]
dec eax
dec eax
neg eax
sbb eax, eax
inc eax
push eax
push edx
push esi
// call PRO.004024C0
sub esp, 0x0000021C
push esi
// :004024C7 mov esi, dword ptr [esp+00000228]
mov esi, szMileage
cmp esi, 0x000F423F
// :004024D4 jbe 0040250D
//not going to check max here
// :004024D4 jmp PRO.0040250D

mov eax, esi
xor edx, edx
mov ecx, 0x00000064
push ebx
div ecx
mov eax, 0xD1B71759
mov eax, 0xD1B71759
push edi
mov bl, dl
mul esi
mov eax, edx
xor edx, edx
shr eax, 0x0D
div ecx
mov eax, 0x51EB851F
mov cl, dl
mul esi
mov eax, edx
xor edx, edx
shr eax, 05
mov esi, 0x00000064
mov esi, 0x00000064
mov byte ptr [esp+0x0C], cl
div esi
test dl, dl
mov byte ptr [esp+0x24], dl
// :0040254D je PRO.0040255B

dec dl
mov byte ptr [esp+0x10], cl
mov byte ptr [esp+0x20], dl
// :00402559 jmp PRO.00402571

mov edx, dword ptr [esp+0x0C]
mov esi, dword ptr [0x004080E4]
and edx, 0x000000FF
lea eax, dword ptr [esp+0x14]
push edx
push 0x0040A688
push 0x0040A688
push eax
// call USER32.wsprintfA

mov ecx, dword ptr [esp+0x30]
lea edx, dword ptr [esp+0x28]
and ecx, 0x000000FF
push ecx
push ecx
push 0x0040A688
push edx
// call USER32.wsprintfA
mov eax, dword ptr [esp+0x28]
lea ecx, dword ptr [esp+0x24]
and eax, 0x000000FF
push eax
push eax
push 0x0040A688
push ecx
// call USER32.wsprintfA
mov edx, dword ptr [esp+0x44]
lea eax, dword ptr [esp+0x3C]
and edx, 0x000000FF
push edx
push 0x0040A688
push 0x0040A688
push eax
// call USER32.wsprintfA
mov cl, byte ptr [esp+0x4D]
mov dl, byte ptr [esp+0x4C]
mov al, byte ptr [esp+0x44]
sub cl, 0x30
shl dl, 0x04
or cl, dl
mov dl, byte ptr [esp+0x45]
shr bl, 0x1
movzx di, bl
sub dl, 0x30
add esp, 0x00000030
shl al, 0x04
or dl, al
xor esi, esi
test di, di
mov byte ptr [esp+0x00000128], cl
mov byte ptr [esp+0x00000129], dl
mov eax, 0x00000002
// :00402613 jbe 00402648

mov esi, edi
and esi, 0x0000FFFF
mov edi, esi

PRO_0040261F: mov ebx, eax
and ebx, 0x0000FFFF
inc eax
mov byte ptr [esp+ebx+0x00000128], cl
mov ebx, eax
and ebx, 0x0000FFFF
inc eax
dec edi
mov byte ptr [esp+ebx+0x00000128], dl
jne PRO_0040261F
mov Mypointer,ecx
ret
}

return Mypointer;


}



Dec 28, 2011 at 6:04pm
Sik,
Thanks for the info. I know (on the surface) just a very little about what you outline. I do have and program in VB.NET but have never tried anything like this.

I was able to get the DLL working, so for the moment, I’ll stick with it. I started having a problem with the ASM code giving me an error message though. When I called it from visual basic, I got this:
Debug Error!

Program: C:\VB6 programs\Devstudio 9A\DashPros\Test DLL call\Project1.exe
Module:
File: i386\chkesp.c
Line: 42

The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one ca…

This (above) seems to be related to “PUSH” and “POP” ASM instructions. It doesn’t like something I’m doing… I can probably figure that out or post to some ASM code board.

Not knowing how to easily debug ASM in a C++ IDE, I just started backing down the code until I had this simple routine:


long __stdcall DashPros(long szMileage )
{
long Mypointer;
return szMileage;

}

With the code as above, I don’t get the error (of course).

All I wanted to do here (for testing purposes) is to have the routine return the same value that I’m sending to it. I’m sending 100 and getting back 1243712. That doesn’t make any sense to me.

My question now is:
• How do I create a C++ function that will accept a number from 0 to 300,000 and have it return a string to me? Or… have it return a pointer to a string in memory?

Thanks.
Dec 28, 2011 at 8:57pm
Number to string ? There is atoi() CRT function, or if you want use a std::stringstream.
Dec 29, 2011 at 3:37am
Modoran,
My ASM routine will store a long string in memory. I’d like to be able to get it using the ASM memory pointer and return it to the calling VB program as a string. Or… I could just return the memory pointer and have VB get the string (probably more difficult).

I’m just a little frustrated because I don’t understand the C++ functions. I tried creating some simple routines just to see if I could send something in and return it to me. For example for numbers:

long __stdcall YOUR_ROUTINE(long sz)
{
Sleep(1000);
return sz;

}

I’m still having trouble with this. It should return what I send in but I’m sending in 100 and it is returning 1241980. Hmmmm… I dunno what the problem is there.

For strings I tried creating a routine like this:
extern char * __stdcall StringTest (char * inString)
{
char sString [100];
strcpy ( sString, inString);
return sString;
}

I just want to pass a string to the DLL and have it returned to me. VB is returning an error message on this one. Says it can’t find the DLL entry point.

Are these routines in the ball park of how it’s done in C++?

I'm not sure how to use std::stringstream yet. atoi I've seen before and I'm not sure about CRT.
Topic archived. No new replies allowed.