How To Connect Your C Code to Visual Basic (or C#) Via A Dll

A client I consult for needed a fix for some legacy code – an old Riff chunk in an audio format, one that used a fixed C struct for the data. Just reading and writing – simple stuff.

My mistake was thinking that VB could handle it. It turns out that a true byte-aligned structure, like the struct types C/C++ uses, are almost impossible to work with easily in VB. Not only are simple items different sizes (char is 2 bytes for example, as this list shows), but there are some things that aren’t allowed because of bugs (VB has a bug where an array in a struct cannot be set to a size until run time!).

Enter the DLL – write all the code in C, stick it in a DLL, and call from VB.

Of course, there are many, many gotchas in doing that – hence this article, so I can refer to the steps the next time –I– want another DLL for VB!

First off, the DLL should be unmanaged – this allows you to create a multipurpose DLL in pure C/C++, one that can be used in other languages (for example, I repurpose mine for both VB .net as well as Visual Basic 6). To do this:

  • Select the DLL project from Visual Studio (in my case, 2008) via “New Project”; “Win32”, then “DLL”.
  • In the project’s Properties, go to “Configuration Properties”, then in “General”; “Common Runtime…” set it to “No Common Runtime…”
  • For “Debugging”; “Debugger Type” is set to “Mixed” – this will let you debug the DLL properly by running it (when you do, you’ll need to add a program to call it of course, but this lets it be a managed code .exe, like VB .net or C#).
  • Also, make sure your Debug and Release configurations for the project are for win32.
  • Next, you need to expose your functions so VB can see them – for example:
    extern "C" __declspec(dllexport) long MyExportedFunction(void) 
    { 
      return 10; 
    }

    To make it easier, I use a macro like this:

    #define EXPORT_VBNET extern "C" __declspec(dllexport)

    Resulting in this:

    EXPORT_VBNET long MyExportedFunction(void) 
    { 
      return 10; 
    }

    Note you only need to do this for externally used functions – internal ones are prototyped as usual.

From there, compile and run – it will ask for a program to debug with, which is where you enter the name of your managed code program, which is also where the magic happens on the VB side of things…

To see how, here’s the DLL side of an equation – a DLL function with no parameters that returns a long:

EXPORT_VBNET long MyExportedFunction(void);

Here’s the way to call it from VB 2008:

Declare Ansi Function DLL_MyDllFunction Lib "C:egmycode.dll" Alias _
  "MyExportedFunction" () As Integer

(Note you can also use relative paths with the lib entry – if the DLL was in the same directory as the .exe, then Lib "mycode.dll" would work, or use Lib "..mycode.dll" for example to move up a directory.

And to use this call in VB code:

Dim result as integer=DLL_MyDllFunction()

(Note the long from the DLL becomes an integer in VB – different names, but the same physical size)

So great, you can get integers back – what about character arrays or other values?

Here’s an example of a function that passes data back and forth – a const char * array, a char * array for results, and a long in, as well as a long result flag returned:

EXPORT_VBNET long MyExportedFunction2(const char *param1,char *param2,long buffLen) 
{ 
  strcpy(param2,"Now=");
  strcat(param2,param1);
  return strlen(param2);
}

This just takes the first string, prefixes it with “Now=” and returns it into the buffer for param2. The resulting long returned is the string length. Note we don’t use buffLen (it’s here just to show the coding) but in reality, you’d use it to avoid buffer overruns when writing bytes out – very nasty business if that happens…

To use this in VB, here’s the prototype:

Declare Ansi Function DLL_MyNextFunction2 Lib "mycode.dll" Alias _
  "MyExportedFunction2" (<MarshalAs(UnmanagedType.LPStr)> ByVal param1 As String, _
                         <MarshalAs(UnmanagedType.LPStr)> ByVal param2 As StringBuilder, _
                         ByVal buffLen As Integer) As Integer

And later in your VB code, here’s how you would call it:

Dim buff As StringBuilder = New StringBuilder(512)
Dim result as integer= DLL_MyNextFunction2( "my string", buff, buff.Capacity() )
Dim resultString as String= buff.ToString

So that does it for DLL conversations between managed and unmanaged code – of course, as always, watch out for gotchas:

  • The VB program calling this must be configured for x86 operation (both debug and release) – otherwise you’ll get weird errors.
  • Remember that data types are different between VB and win32 DLL code – notice the long values everywhere, which in VB are defined ‘as integer‘ Mixing them up will really be annoying!
  • Likewise, you should always return a long from your DLL functions, even if it’s a dummy value – Declaring a VB function without a return will make VB try to return an object (if it runs at all) and you will get another cryptic error.
  • Don’t try to use internal DLL memory outside of the DLL – instead, pass it a StringBuilder array, write to it, and use that result.

One thought on “How To Connect Your C Code to Visual Basic (or C#) Via A Dll