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.

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.

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.

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:

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:

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:

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
Valid XHTML 1.0! Valid CSS!
Page Execution took real: 0.151, user: 0.110, sys: 0.010 seconds , Memory: 6085428