How to call C functions from .NET using a DLL written in C

On this page I describe how I call a C function from VB.Net using a DLL written in C. I did this using Visual Studio 2012.

A little background: I had a program written in C that basically took some binary data files, did some calculations, and produced a text file with around 100,000 rows of output. I wanted to use that output in a VB.Net Windows Form application. At first I called the C program as a process and read the data in from the text file it created, but writing the text file to disk was by far the slowest part. To skip this step I started looking online for how to call C functions from .Net. I didn't have any success getting any of the methods I found to work. Then it occurred to me that I'd already figured out how to call C functions from Excel using a DLL written in C, and since VB.Net is similar enough to VBA (which I view as an indication of the many things you can do from right inside Excel), something similar might work in VB.Net as well. And sure enough, the exact same technique works perfectly. When I typed up the steps for calling C functions from Excel and decided to store those notes on this site, I didn't expect thousands of people from over a hundred countries to look at it. But since those seem to have helped some people, I figured I'd include this here to go along with the notes on how to call a C function from Excel.

A disclaimer: Before describing how I got this to work, a disclaimer is in order. I use what I describe below in a multi-threaded application that interacts with the user via a form that retrieves records from a SQL database (SQL Server 2008 R2) for processing and display (e.g., data tables, graphs), and when needed, inserts new records after the C DLL retrieves and processes data from binary files. So I know this technique can handle millions of rows of data. However, it's important to point out that its user is only me, so while I am familiar with little Bobby Tables and some of the dangers of doing things like this incorrectly, I didn't have security in mind when coming up with this. It's entirely possible that this technique poses major security concerns and is inappropriate for use with sensitive data. So before using this with anything where security is a concern, check with someone who's actually an expert in those things and didn't just figure out how to make it work for his own purposes.

That said, here's how I called a C DLL from VB.Net:

    1. I started by doing everything I explain here about How to call C functions from Excel using a DLL written in C. I'm now use VS12 instead of VS08, but other than the screens looking a bit different, everything works the same way. The "square" example is simple. The "array" example is more general because it uses arrays. Both work when called from VB.Net as well. I did it with an array that was around 100000 x 10 without any problem.

    2. When you get to the part about declaring the C function for use in VBA, what I did in VBA is exactly what I did in VB.NET. The only difference is that you do not use the PtrSafe like you do for in Excel/VBA for the 64-bit DLL. So to use the function that accepts array arguments and "returns" an array, you just include in the VB.NET code, for example, "Declare Function useArray Lib "C:\Users\Jon\Documents\Visual Studio 2008\Projects\arrayExcelCDLL\x64\Debug\arrayExcelCDLL.dll" (ByRef dIn As Double, ByRef dOut As Double, ByRef iSizeIn As Long, ByRef iSizeOut As Long) As Long".

    3. If you do this with a 64-bit DLL using the default settings (at least what was default for me in VS12), you have to change on of the preferences to get it to work. Under the project properties, go to Compile and un-select "Prefer 32-bit." Otherwise you get a "System.BadImageFormatException".

    4. The arguments declared as "ByRef dIn as Double" and "ByRef dOut as Double" are double arrays in VB.Net (e.g., Dim dOut(90600,59) As Double). In the C function, I treat them as one-dimensional arrays and use an index formula. So dOut(r,c) in VB.Net is dOut[r*nCols+c] in C. There's nothing to keep you from going beyond the bounds of the original VB.Net array, so I pass the dimensions as the other arguments.

    5. This is not shown in the example, but I'll add a note on what I did to pass a string. I had trouble with the "official" ways I found. So instead, I just passed an integer array of ASCII values. I started with the a string, sPath, a character array, cPath(), and integer array, iPath(). Then cPath=sPath.ToCharArray. Then loop over the characters in cPath and for each one, do iPath(i) = Asc(cPath(i)). Then in C I looped over the integer array argument and stored each character into a character array, casting each int as (char), a then storing 0 at the end. Definitely a hack and not something you'd want to do if you have lots of strings, but for one string, it worked fine.

This might not be the best or fastest approach, but it works quite well. In my VB.Net Windows Form application, I click a button and it calls the C function using parameters from the form. The C function opens up 7 binary data files, each with about 100,000 rows of data, processes them, calculates what I'm interested in, and "returns" a 100,000 x 10 double array to VB.Net. It does all this in under 2 seconds, which is quick enough for my purposes.