Link C Obj Files into VB6

Author: Dave
Date: 04.03.14 - 12:54pm

In the last post I covered some tools which allow you to create standard dlls in VB6. Once I got playing with the tool to modify the link process, I thought I would keep going a bit and see what else I could do.

I really wanted a way to link in compiled code written in C, directly into my VB6 projects. This need doesnt come up that often, but it does come up. Maybe you have a decoder written in C, that you dont want to have to port. Or rather do some small operations that C makes easy, but are a mess in VB6. There could also be performance reasons with say a compression routine or similar.

Anyway, the example I went with, was creating 64 bit numbers and showing them as a string. VB6 has the native 8 byte currency type which can house a 64bit number, but it cant manipulate them.

So first up, how can we include code compiled in C into our VB6 binary. This turns out to actually be quite easy, all you have to do is add the compiled .obj file to the linker command line. It also turns out that if your C code defined the functions as exports, the linker will automatically place them in the export table which works for exe or dll the same.

Now to use the functions from VB6, you just access them normally as if it was a dll using the Windows API Declare syntax. So how do you debug it while running code in the IDE you ask? There isnt much point in doing this trick, if you cant debug the other parts of your code in the IDE. Thats easy too. When you write your C code, you compile it as a standard dll and use the Declares to access the library. This lets you debug everything traditionally.

Then when your ready to compile, you use the exact same C object file that made the dll, and just link it into the exe build process.

The trick is, that you wrap your raw API access routines, in another function which tests to see if the code is running in the IDE or not. If it is, then it uses the external API declares. If its running as a compiled code, then it uses a second set of API declares that reference the compiled executable itself.

Since the functions are exported, it all functions as if it was using an external dll, just that no separate file is required for distribution. A sample is below:

unsigned __int64 __stdcall to64(unsigned int hi, unsigned int lo){
#pragma EXPORT
	unsigned __int64 ret=0;
	ret = hi;
	ret = ret << 32;
	ret += lo;  
	return ret;

'for accessing linked in C code
Private Declare Function to64 Lib "project1.exe" _
		(ByVal hi As Long, ByVal lo As Long) As Currency
'for debugging in the IDE 
Private Declare Function dll_to64 Lib "c_obj.dll" Alias "to64" _
		(ByVal hi As Long, ByVal lo As Long) As Currency

Function doto64(hi As Long, lo As Long) As Currency
    If isIde() Then
        doto64 = dll_to64(hi, lo)
        doto64 = to64(hi, lo)
    End If
End Function

Dim a As Currency
a = doto64(&H11223344, &H55667788)
While this trick is most often needed for complex math routines you dont want to port to VB, there are times where you will also need to use some Windows API from your C obj file routines.

Math and stuff runs fine, sub function calls should also run fine. But using the Windows API cause some problems. This is because the obj file will contain details on the import table, which will override the settings the VB6 executable requires to run, leading to a corrupted executable.

Dont worry though, there is a way around it. The way I chose to go, was to access any WinApi i needed through dynamically looked up function pointers. To load the pointers, I included an Init function which took the addresses of LoadLibrary and GetProcAddress as arguments to help me boot strap it.

typedef FARPROC  (__stdcall *GetProc)(HMODULE a0,LPCSTR a1);
typedef HMODULE  (__stdcall *LoadLib)(LPCSTR a0);
typedef int (__stdcall *SysAllocSBL)(void* str, int sz);
typedef int (__cdecl *Sprnf)(char *, const char *, ...);
typedef int (__cdecl *Strlen)(char *);

GetProc getproc;
LoadLib loadlib;
Sprnf sprnf;
Strlen strln;
SysAllocSBL sysAlloc;

int __stdcall init(int lpfnGetProc, int lpfnLoadLib){
#pragma EXPORT 	 
	 int h = 0;
	 int failed = 0;

	 //_asm int 3
	 getproc = (GetProc)lpfnGetProc;
         loadlib = (LoadLib)lpfnLoadLib;

	 h = loadlib("msvcrt.dll");
	 sprnf = (Sprnf)getproc(h,"sprintf");
	 strln = (Strlen)getproc(h,"strlen");
	 h = loadlib("oleaut32");
	 sysAlloc = (SysAllocSBL)getproc(h,"SysAllocStringByteLen");
	 if( (int)sprnf == 0) failed++;
	 if( (int)strln == 0) failed++;
	 if( (int)sysAlloc == 0) failed++;

	 return failed;

Linking in the resulting obj file lead to no further problems. Given that you wont be using this technique for general programming tasks, Limiting the API you use in your C Stubs is a very realistic design constraint.

Bottom line, if you are doing something complex, it would be worthy of its own DLL anyway.

There are also other ways to use C Object files in your VB6 code. With a little bit more experimentation, and some web searching, I was able to find examples of how to replace entire Module files with Cpp counterparts as well as only replacing individual functions found in VB6 modules.

Replacing the entire module is a little bit slicker than the export technique, because you dont have to write all the wrappers and manage two sets of declare statements. When in the IDE, it operates normally through the declare. Once compiled, and the module replaced, all the functions are literally running the same code but compiled in. Note however that the VB6 declare syntax, the run time does some extra processing for you marshaling data from C strings to the COM BSTR type. I do include a string example, but you might have to play around with it some more to get it the way you want it.

As far as replacing individual functions from a module, I took a cheap shot approach, where I opened up the .obj file in binary mode and overwrote the function name declaration in the one you want to override. The linker then uses the implementation you provide in the extra C Obj file automatically.

For entire module replacement, you just replace the vb6 .obj file with the C one.

To help me with these experiments I have extended Jim Whites vb LinkTool. Examples of all of these techniques are available in the source archive I have put on github.

One other nice addition I made while I was in there was to add a POSTBUILD command so that you could automatically run a command line every time your executable was built. This is handy for copying a dll to other project directories, running upx etc.

Comments: (1)

On 05.05.20 - 9:40am Dave wrote:
misc note, you can use the undocumented VBCompiler section of the vbp file to pass commands to the linker such as:
example use

Leave Comment:
Email: (not shown)
Message: (Required)
Math Question: 64 + 10 = ? followed by the letter: U 

About Me
More Blogs
Main Site
Posts: (year)
2024 (2)
     ffmpeg voodoo
     RegJump Vb
2023 (9)
     VB6 Virtual Files
     File handles across dlls
     python abort / script timeout
     VB6 Python embed w/debugger
     python embedding
     VB6 IDE Enhancements
     No Sleep
     A2W no ATL
2022 (4)
     More VB6 - C data passing
     Vb6 Asm listing
     Byte Array C to VB6
     Planet Source Code DVDs
2021 (2)
     Obscure VB
     VB6 IDE SP6
2020 (4)
     BSTR from C Dll to VB
     Cpp Memory Manipulation
     ActiveX Binary Compatability
2019 (5)
     Console tricks
     FireFox temp dir
     OCX License
     Extract substring
     VB6 Console Apps
2018 (6)
     VB6 UDTs
     VB6 Debugger View As Hex tooltips
     VB6 - C Share registry data
     VB6 Addin Missing Menus
     VB6 Class Init Params
     VB6 isIn function
2017 (6)
     Python and VB6
     Python pros and cons
     download web Dir
     vc rand in python
     VB6 Language Enhancement
     Register .NET as COM
2016 (22)
     VB6 CDECL
     UDT Tricks pt2
     Remote Data Extraction
     Collection Extender
     VB6 FindResource
     DirList Single Click
     Reset CheckPoint VPN Policy
     VB6 BSTR Oddities Explained
     SafeArrays in C
     BSTR and Variant in C++
     Property let optional args
     Misc Libs
     Enum Named Pipes
     Vb6 Collection in C++
     VB6 Overloaded Methods
     VB6 Syncronous Socket
     Simple IPC
     VB6 Auto Resize Form Elements
     Mach3 Automation
     Exit For in While
2015 (15)
     C# self register ocx
     VB6 Class Method Pointers
     Duktape Debug Protocol
     QtScript 4 VB
     Vb6 Named Args
     vb6 Addin Part 2
     VB6 Addin vrs Toolbars
     OpenFile Dialog MultiSelect
     Duktape Example
     DukTape JS
     VB6 Unsigned
     .Net version
     TitleBar Height
     .NET again
     VB6 Self Register OCXs
2014 (25)
     Query Last 12 Mos
     Progid from Interface ID
     VB6 to C Array Examples
     Human Readable Variant Type
     ScriptBasic COM Integration
     CodeView Addin
     ScriptBasic - Part 2
     Script Env
     MSCOMCTL Win7 Error
     printf override
     History Combo
     Disable IE
     API Hooking in VB6
     Addin Hook Events
     FastBuild Addin
     VB6 MemoryWindow
     Link C Obj Files into VB6
     Vb6 Standard Dlls
     CStr for Pascal
     Lazarus Review
     asprintf for VS
     VB6 GlobalMultiUse
     Scintilla in VB6
     Dynamic Highlight
     WinVerifyTrust, CryptMsgGetParam VB6
2013 (4)
     MS GLEE Graphing
     printf for VB6
     C# App Config
     Tero DES C# Test
2012 (10)
     VC 2008 Bit Fields
     Speed trap
     C# Db Class Generator
     VB6 vrs .NET (again)
     FireFox Whois Extension
     git and vb6
     Code Additions
     Compiled date to string
     C# ListView Sorter
     VB6 Wish List
2011 (7)
     C# Process Injection
     CAPTCHA Bots
     C# PE Offset Calculator
     VB6 Async Download
     Show Desktop
     coding philosophy
     Code release
2010 (11)
     Dll Not Found in IDE
     Advanced MSScript Control
     random tip
     Clipart / Vector Art
     VB6 Callback from C#
     Binary data from VB6 to C#
     CSharp and MsScriptControl
     HexDumper functions
     Js Beautify From VB6 or C#
     vb6 FormPos
     Inline Asm w VB6
2009 (3)
     The .NET Fiasco
     One rub on computers
     Universal extractor