Creating a MinGW DLL for Use with Visual Basic
Below are two examples of techniques which facilitate the use of use of MinGW to create DLLs, exporting functions which may be called from Visual Basic. The first of these uses C or C++ as the implementation language; the second uses GNAT/GCC.
Example 1: A DLL for Visual Basic, Programmed in C or C++
NOTE : This description is only for Visual Basic 6, not DotNet.
- Step 1: Create your DLL.
VB can only call __stdcall functions, not __cdecl functions. So we must export all our functions as __stdcall. Create a DLL with the following code template:
extern "C" // only required if using g++
{
__declspec (dllexport) void __stdcall FooBar (void)
{
return;
}
__declspec (dllexport) DWORD __stdcall FooBarRet (DWORD MyValue)
{
return MyValue;
}
__declspec (dllexport) BOOL __stdcall DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
} // extern "C"
When compiling, add "--add-stdcall-alias" to your linker flags. If you're passing linker flags via g++, it should be "-Wl,--add-stdcall-alias". This adds an undecorated alias for the exported function names that is simply the name of the function. By default, the functions are exported with an @nn appended to the function name.
The DllMain function is called by the system when your dll is loaded and unloaded from a process, as well as when threads attach and detach (I'm not sure what that means, but it's in the case statement). If you have anything important to do in your dll when these events occur, this is the place to do it. Initializing WinSock is a popular thing to do here so that the WinSock function calls don't all return 10093 -- WSANOTINITIALISED. If you don't export this function yourself, it won't be automatically exported as in Visual Studio.
- Step 2: Call DLL from VB
Under VB, create the library call such as:
Private Declare Sub FooBar Lib "mydll" () Private Declare Sub FooBarRet Lib "mydll" (ByVal MyValue As Long) As Long
That's all. As an alternative, you can create a TypeLib, but that's another story.
- Variables
| w32api type | VB type | Automation type |
|---|---|---|
| BOOL | Long | BOOL |
| unsigned long | Long | Long |
| LPSTR | String (1) | LPSTR |
| short int | Boolean | VARIANT_BOOL |
(1) : Internally (under VB), a string is a BSTR: a pointer to an array of WCHAR with a 32 bit prefix that declares the number of elements of the array. When calling an API, VB passes a LPSTR (converted from the WCHAR array). A WCHAR array is not a LPWSTR since it can contain embedded NULL WCHARs.
Example 2: Programming a GNAT/GCC + MinGW DLL for Visual Basic
This is an extension of Roger Pearse's
The Noddy Guide to using ADA code with Visual Basic. Roger effectively gives up on the possibility of a Windows DLL under GNAT/GCC. Windows has improved somewhat since his original article. So has gcc. The following outlines my attempt.
The spec, API.ADS:
- function Factorial, which accepts a Win32.LONG and returns a Win32.LONG (I gave up on C.int as it was too small for a Factorial of 16)
- function GetCount, which returns the value of the global
Count(another Win32.LONG) - procedure Mangle, which accepts a Win32.LPWSTR and attempts to modify it in situ
- function Pattern, which returns a long pointer to some string data
- procedures Initialize_API, and Finalize_API, which I understand are mandatory. (If someone knows better, please tell me.)
with Interfaces.C; use Interfaces; with Win32; use Win32; package API is Count : Win32.LONG := 0; function Factorial (Val : Win32.LONG) return Win32.LONG; function GetCount return Win32.LONG; procedure Mangle( Ptr : Win32.LPWSTR ); function Pattern return Win32.LPWSTR; procedure Initialize_API; procedure Finalize_API; private pragma Export (DLL, Initialize_API); pragma Export (DLL, Finalize_API); --pragma Export (StdCall, Count); pragma Export (DLL, GetCount ); pragma Export (DLL, Factorial ); pragma Export (DLL, Mangle ); pragma Export (DLL, Pattern ); end API;
the base, API.ADB:
- I'm still working through the issue of what to do with Mangle. Perhaps two routines are needed. The first to ask the DLL how much space is needed, with VB the allocating it, and the second then actually poking the data into the allocated space.
- Pattern returns a pointer to the string data. I rediscovered the technique for reading it out and that's in the VB code below. It also seems that, for as long as the DLL is loaded, the data stored in
foois available for transfer.
with Interfaces.C;
use Interfaces.C;
with Win32; use Win32;
with Win32.Winmain;
with Win32.Winbase;
with System;
package body API is
function Factorial (Val : Win32.LONG) return Win32.LONG is
Fact : Win32.LONG := 1;
begin
Count := Count + 1;
for K in 1 .. Val loop
Fact := Fact * K;
end loop;
return Fact;
end Factorial;
function GetCount return Win32.LONG is
begin
return Count;
end GetCount;
procedure Mangle( Ptr : Win32.LPWSTR ) is
lp : Win32.lpwstr;
foo : constant Win32.WCHAR_Array := "foo man chew" & Win32.Wide_Nul;
begin
lp := Win32.Winbase.lstrcpyW( Ptr, Win32.Addr(foo) );
end Mangle;
function Pattern return Win32.LPWSTR is
foo : constant Win32.WCHAR_Array := "martinet mongoose manticore" & Win32.Wide_Nul;
begin
return Win32.Addr(foo);
end Pattern;
procedure Initialize_API is
procedure Adainit;
pragma Import (C, Adainit);
begin
Adainit;
end Initialize_API;
procedure Finalize_API is
procedure Adafinal;
pragma Import (C, Adafinal);
begin
Adafinal;
end Finalize_API;
end API;
api.def lists the symbols exported, and is needed by gnatdll.
EXPORTS
factorial
mangle
initialize_api
finalize_api
pattern
getcount
This was compiled in Windows XP's CMD. The batch file, build.bat, is as follows. It is assumed that the debug and release subdirectories already exist:
@echo off set target=%1 if %1@==@ set target=debug if not %1@==debug@ if not %1@==release@ set target=debug echo Building %target% version of dll gnat make -Pbuild -XSTYLE=%target% gnatbind -n -x %target%\api.ali -IC:\MinGW\lib\gcc\mingw32\4.2.1\stdarg -IC:\MinGW\lib\gcc\mingw32\4.2.1\win32ada gnatlink -g -mdll -s -o %target%\api.dll %target%\api.ali -Xlinker --base-file=%target%\api.base dlltool --dllname api.dll --base-file %target%\api.base --output-exp %target%\api.exp --def api.def gnatlink -g -mdll -s -o %target%\api.dll %target%\api.ali -Xlinker --base-file=%target%\api.base %target%\api.exp if %target%==release upx -9 %target%\api.dll
The project file used by gnat make is as follows:
project Build is
for Source_Dirs use ( "./", "c:\mingw\lib\gcc\mingw32\4.2.1\**" );
for Object_Dir use "debug";
for Exec_Dir use "debug";
for Main use ("api");
type Style_Type is ("debug", "release");
Style : Style_Type := external ("STYLE", "debug");
case Style is
when "debug" =>
for Object_Dir use "debug";
when "release" =>
for Object_Dir use "release";
for Exec_Dir use ".";
end case;
package Builder is
case Style is
when "debug" =>
for Default_Switches ("Ada")
use ("-g");
when others =>
null;
end case;
end Builder;
package Compiler is
case Style is
when "debug" =>
for Default_Switches ("Ada")
use ( "-s",
"-gnata",
"-gnato",
"-gnatE");
when "release" =>
for Default_Switches ("Ada")
use ("-O2", "-fno-strict-aliasing");
end case;
end Compiler;
end Build;
Now the VB code. Take note of a few things:
- Exporting as
DLLgenerates decorated names, but dlltool was able to resolve the decorated ones with the undecorated ones specified in api.def</LI> - For reasons I remember reading somewhere, initialize_api and finalize_api are mandatory, and must be the first and last calls to the DLL
- Seeing as woof woof woof woof is longer than foo man chew,
sends up containing foo man chew f woof - As mentioned before, I rediscovered some code for getting data from a pointer and put that in. It's the PointerToString function at the bottom.
Declare Sub initialize_api Lib "api.dll" ()
Declare Function factorial Lib "api.dll" (ByVal x As Long) As Long
Declare Function getcount Lib "api.dll" () As Long
Declare Function pattern Lib "api.dll" () As Long
Declare Sub mangle Lib "api.dll" (ByVal x As Long)
Declare Sub finalize_api Lib "api.dll" ()
' Declaration of the function which copies the character string
Private Declare Function lstrcpy Lib "Kernel32.Dll " Alias "lstrcpyW" (LpszString1 As Any, LpszString2 As Any) As Long
' Declaration of the function which returns the length in the character string
Private Declare Function lstrlen Lib "Kernel32.Dll " Alias "lstrlenW" (ByVal lpszString As Long) As Long
Sub main()
Dim s As String
s = "woof woof woof woof"
initialize_api
Dim i As Long
For i = 1 To 16
Debug.Print factorial(i);
Next
Debug.Print
mangle StrPtr(s)
Debug.Print Len(s), s
Dim ptr As Long
ptr = pattern()
Debug.Print PointerToString(ptr)
finalize_api
End Sub
' Converting the pointer to the character string
Function PointerToString(lngPointer As Long) As String
Dim bytBuffer(255) As Byte
' Copying the character string which the pointer points to byte array
lstrcpy bytBuffer(0), ByVal lngPointer
' Cutoff after the null character
PointerToString = Left(bytBuffer, lstrlen(lngPointer))
End Function
Finally, a sample run under VB6
1 2 6 24 120 720 5040 40320 362880 3628800 39916800 479001600 1932053504 1278945280 2004310016 2004189184 19 foo man chew f woof martinet mongoose manticore
- based on
Creating DLLs for VB6 and
Better build automation by BruceAxtens.


