Exporting VB DLL Functions

Everyone knows you can't export VB DLL functions, right? You have to use an ActiveX DLL. Well, with a little help from C++, you can export those VB functions.

First, you need an MFC DLL that maintains a table of function pointers, and has stubs for each VB DLL function you want to export. Then, during its initialization, it creates the VB DLL's class object. The class object's initialization calls the MFC DLL (via an ordinary entry point) to register each exported function. The exported functions have to be in a global class module. Then when a program calls the C++ stub function, it looks up the correct address, and forwards the call.

Not clear? Look at some code:

Here is the MFC DLL:

// vbbridge.cpp : Defines the initialization routines for the DLL.
//

#include "stdafx.h"
#include <afxdisp.h>
#include "vbbridge.h"


#define VBDLLNAME "vbawc.dll"  // PROG ID of VB DLL
#define NRFUNCTIONS 1          // # of function stubs

void *ftable[NRFUNCTIONS];




#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


/////////////////////////////////////////////////////////////////////////////
// CVbbridgeApp

BEGIN_MESSAGE_MAP(CVbbridgeApp, CWinApp)
	//{{AFX_MSG_MAP(CVbbridgeApp)
		// NOTE - the ClassWizard will add and remove mapping macros here.
		//    DO NOT EDIT what you see in these blocks of generated code!
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CVbbridgeApp construction

CVbbridgeApp::CVbbridgeApp()
{
	// TODO: add construction code here,
	// Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CVbbridgeApp object

CVbbridgeApp theApp;

BOOL CVbbridgeApp::InitInstance() 
{
    COleException ex;

// Initialize the VB portion of the DLL
    CoInitialize(NULL);
    COleDispatchDriver vbdll;
    vbdll.CreateDispatch(VBDLLNAME,&ex);	
    return CWinApp::InitInstance();
}


extern "C" {
// This is the function VB calls to register each function
    __declspec(dllexport) void __stdcall setFuncAddress(int n,void *p)
    {
        ftable[n]=p;
    }

// This is the stub that forwards to VB
// This one is simple, but it could handle
// return values, or even translate parameters, if necessary
    __declspec(dllexport) void vbfunc(int n)
    {
        if (ftable[0])
        {
            void (WINAPI *f)(int)=(void (WINAPI *)(int))ftable[0];
            f(n);
        }
    }
}

OK, so what's the VB look like? Here is the class module:

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "dll"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Private Sub Class_Initialize()
setFuncAddress 0, AddressOf vbf  ' register vbf with C++ DLL
End Sub

And here is a global module that contains a declare for setFuncAddress and the function you want to export:

Attribute VB_Name = "Externals"
Declare Sub setFuncAddress Lib "VBBridge" Alias "_setFuncAddress@8" (ByVal n As Long, ByVal f As Long)

Public Sub vbf(ByVal n As Long)
MsgBox "VB got " & n
End Sub

If you had more functions, you'd have multiple calls to setFuncAddress. The first argument is a number that identifies the function. This has to be less than the #NRFUNCTIONS macro, and the same one the stub uses.

Finally, just to finish up, here is a simple console application that calls the DLL, blissfully unaware that it is a VB DLL:

#include <stdio.h>

extern "C" {
  __declspec(dllimport) void vbfunc(int n);
}


void main()
{
    vbfunc(10);
}

Notice that the VB name and the stub name don't need to match, although they could.