原理:
Visual Basic 从5.0起就可以象C++那样将程序编译成本地码.VB将程序中的每个form, bas, cls, vbp 编译为obj文件,然后再调用连接程序将它们连接成EXE, 这个过程是自动的,连接完成后它会自动删除中介生成的OBJ文件. 要实现我们的目的就需要在连接程序连接输出EXE之前对OBJ文件进行处理,将C++编译的OBJ文件换进去.
准备:
我们需要自己些一个连接程序,当然这个程序不是要实现连接的功能,只需要实现 预先处理 OBJ 文件, 获取VB传递给连接程序的参数, 对参数进行 必要的修改, 然后 调用原连接程序 完成连接工作. 我们将vb目录下的Link.exe 改名为 vblink.exe, 再将我们写的link.exe 放到这个目录中. 这个Fake Link 程序我已经完成了, 在压缩包包中可以找到. 
先讲讲这个Fake Link 程序是怎么工作的:
首先 程序取得 VB 传递给连接程序的 参数.
通过对参数进行分析 获取 当前所编译的VB工程文件的完整路径.(如:F:\ LinkWithVC\ LinkWithVC.vbp), 然后程序会查找该工程对应的自定义连接配置文件(F:\LinkWithVC\ LinkWithVC_link.ini), 如果找不到 程序什么也不做直接调用vblink.exe.
如果找到了,就读取里面的设置,按照设置进行相应的处理,最后调用 vblink.exe.
InI文件的配置将在 下面实践测试中做介绍.
实践测试:
运行VB6新建一个工程,工程名设为LinkWithVC, 给工程添加一个模块(bas), 名称设为 ModVC.
给窗体添加一个按钮 名称 cmdTest, 标题 Test. 在ModVC中添加
Public Function Test() As Long
Test = 9
End Function
在cmdTest的Click事件中添加 代码 msgbox test, 保存工程,编译运行,点击按钮我们会看到

是9没错就是 9 .
现在我们启动VC6新建一个空的Win32工程名称就叫vcobj,目录就设置为VB工程LinkWithVC所在的目录.
在工程中添加一个Generic Class ,类的名称就叫ModVC, 设置活动配置为 Win32 release. 现在就点击 Build 菜单中的 Compile ModVC.cpp 将它编译成OBJ文件.
然后在VB工程目录下建一个文件 LinkWithVC_link.ini.在文件中输入如下内容:
[Settings]
lib=0
log=1

[OBJ]
F:\LinkWithVC\vcobj\Release\ModVC.obj=F:\LinkWithVC\ModVC.obj

[lib]
注:F:\LinkWithVC是VB工程所在目录,F:\linkWithVC\vcobj是VC工程所在目录.
Ini文件有三个段.Settings段有lib 它表示 需要附加的lib的数量. 相应的lib在lib段中定义.
OBJ段就是OBJ替换定义段可以是 vc的obj=vb的obj. Fake Link程序会自动用vc的obj替换vb的obj文件.
Log为1表示生成连接记录. 有记录便于排错.
好了现在我们切换到VB工程 来生成LinkWithVC.exe
完了我们会发现并没有生成EXE, 打开log文件看看,会看到如下内容:
Microsoft (R) Incremental Linker Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Form1.OBJ : error LNK2001: unresolved external symbol "private: void __stdcall ModVC::Test(void)" (?Test@ModVC@@AAGXXZ)
F:\LinkWithVC\LinkWithVC.exe : fatal error LNK1120: 1 unresolved externals
看到拉,连接出错了 unresolved external symbol private: void __stdcall ModVC::Test(void) ,这个是正常的,我们替换了ModVC.obj,而我们的VC的modvc.obj并没有定义这个函数.接下来就是解决这个问题了.知道怎么做了吧,…现在切换到VC,在类ModVC中定义一个private的函数void __stdcall Test();函数体先空着.再编译它. 然后切换到VB生成EXE.好了生成了EXE文件了,我们运行它,点击按钮,会看到:

是10不是9,显然替换成功了,可是为什么是10呢…我也不清楚….(:P)
Void的函数怎么返回值呢? 在Windows中 函数返回值一般都是存放在eax中的, 我们来试试.在 Test的函数体中 添加代码 _asm mov eax , 123, 再重复前面的操作生成EXE,运行:

看到了123

代码下载
__hxxp://www.bbsftp.net/UploadFile/LinkWithVC01.rar

在上一回我们已经实现VC,VB代码的混合编译,并成功的在VB代码中调用了VC代码中的一个无参数的函数,并取得了函数的返回值. 这一回我们将实践一下如何调用带参数的函数.
打开上一会的两个工程(LinkWithVC, vcobj). 切换到VB在form上添加一个按钮 name: cmdTestLong, 标题 TestLong. 在form上添加一个文本框txtInput 内容填 8. 再在ModVC中添加如下代码:
Public Function TestLong(ByVal lng As Long) As Long
TestLong = -1
End Function
在cmdTestLong的Click事件中添加代码 msgbox Testlong(clng(txtInput.Text))
现在我们来生成EXE. 显然生成会失败,打开log文件看看.
Microsoft (R) Incremental Linker Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Form1.OBJ : error LNK2001: unresolved external symbol "private: void __stdcall ModVC::TestLong(void)" (?TestLong@ModVC@@AAGXXZ)
F:\LinkWithVC\LinkWithVC.exe : fatal error LNK1120: 1 unresolved externals
看到了吗,和第一次看到的错误Log除了函数名,完全是一样的……这时候想到了什么? 参数呢? 在VB代码中明明是有参数申明的啊? ….先不管它, 和上回一样在VC类中添加一个名为TestLong的同类型的空函数. 编译vc的obj,再生成VB的EXE,运行点击 TestLong按钮, 我们看到msgbox提示8, 和我们的参数8 一样!! 是巧合? 试试 改改 txtInput 中的数字. 再点击TestLong按钮. 我们会发现 msgbox提示的和txtInput文本框中的数字是一样的. 有了上回返回值的经验, 而我们的函数什么也没有做, 如是我们会知道 这个参数就 是存放在 eax中的(注:windows中fastcall的函数参数是采用寄存器传递的), 好了到了这里 我们知道怎么在VC代码中获取 vb代码传递的参数了. 可是在传递一个 long 型的数据 有什么实际用途呢?  一个 long 它就意味着 一个长整形数字、一个句柄、一个指针….我们来看一个实际的例子。
在Form上添加一个按钮cmdTestHwnd在按钮 TestHwnd的 Click事件中添加代码 call TestLong(txtInput.hWnd)
这里我们传递的是一个textbox的句柄。
我在VC函数中添加如下代码
void ModVC::TestLong()
{
    long hw;
    _asm mov hw,eax; //取得参数(TextBox的句柄)
    SetWindowText((HWND)hw,"VC: Hello VB");
    
}
重复操作,生成我们的EXE,。。。我们会发现失败了,打开log看看。
Microsoft (R) Incremental Linker Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

ModVC.OBJ : error LNK2001: unresolved external symbol __imp__SetWindowTextA@8
F:\LinkWithVC\LinkWithVC.exe : fatal error LNK1120: 1 unresolved externals
熟悉VC的一看就知道连接的时候没有把User32.lib连接进去。
解决方法有两个1个是在VC代码中使用LoadLibray,GetProcessAddress的方式调用这个api,另一个方法就是 让连接器 把user32.Lib连接进去。 显然如果要大量使用api函数的话第一中方法太繁琐。我们用第二个方法。怎么做了呢?还记得上一回的 自定义连接配置文件吗。
[Settings]
lib=1
log=1

[OBJ]
F:\LinkWithVC\vcobj\Release\ModVC.obj=F:\LinkWithVC\ModVC.obj

[lib]
1=User32.lib
Settings段的lib 表示 [lib]段中的lib数目。 在lib段 我按照顺序1=…,2=… 添加我们要连接的lib即可,当然也可以是obj文件。
好我们再来操作,生成EXE,OK。运行点击TestHwnd。好了我们看到TextBox中的文字变成了 VC: Hello VB 。
我们再来试试字符串。
在form上添加一个按钮TestString 在ModVC中添加函数
Public Function TestString(ByVal s As String) As Long
Rem nothing
End Function
TestString的Click事件中添加如下代码
Private Sub CmdTestString_Click()
Dim s As String
s = Space(256) '为s分配空间
Call TestString(s)
MsgBox s
End Sub
在VC中添加同名函数
void ModVC::TestString()
{
    char* p;
    _asm mov p,eax; //取得参数(TextBox的句柄)
    memset(p,0,256);//我们知道字符串的长度是256
    lstrcpy(p,"VC: Hello VB");
    
}
编译,生成EXE,运行,点击TestString按钮,。。。咦,显示的是乱码!!??这是因为VB中的String用的是Unicode,VC中复制的不是。转一下就可以了。就在vb中转吧vb有一个函数很方便。把msgbox s 改为 MsgBox StrConv(s, vbUnicode),再生成EXE运行,这次OK了。
接下来我们再试试传递结构体。函数参数格式的声明我们可以参考一下VB中API函数的声明。我来个简单点的结构体 POINTAPI, 用VB的 API Viewer 找到
Public Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long

Public Type POINTAPI
        x As Long
        y As Long
End Type
参照这个我们在ModVC中添加函数
Public Function TestStruct(lppt As POINTAPI) As Long
Rem
End Function
在form上添加一个按钮TestStruct,在Click中添加代码
Private Sub cmdTestStruct_Click()
Dim pt As POINTAPI
Call TestStruct(pt)
MsgBox "x= " & pt.x & " y=" & pt.y
End Sub
在VC类中添加同名同类型函数。
void ModVC::TestStruct()
{
    POINT *pt;
    _asm mov pt,eax;//获取 指针参数
    pt->x = 123;
    pt->y = 456;
}
编译运行。。。。我们会发现 非法操作。大概是说引用了0x00000000处的内存。看这个内存地址我们会知道 在vc中引用了一个空指针,那就是说eax的值是0。那则么回事参数没有在eax里面,参数跑哪里去了?在其它寄存器?不太可能。。。那会在哪里呢,我们总不能凭空变出来一个参数吧。想想C++类中有什么可以用的符号?嗯,有一个 this 指针。会是它吗,熟悉VC的人应该知道在调用类成员函数时会先将该类的this指针存放到 eax 中。看来他们有很大的关系。那就动手试试。函数改为如下:
void ModVC::TestStruct()
{
    POINT *pt=(POINT)this;
    pt->x = 123;
    pt->y = 456;
}
再编译,生成EXE运行。OK!成功了,看到了 x= 123 y=456 .
我们再回过头来看看前面的函数 ,都假设把this 当着参数试试,经过测试假设成立。如是我们知道this 就是传递过来的参数,当参数是long是,同时eax中也有这个参数值的一个副本。
到现在看来似乎是大功告成了,还没有!还有一个难题,this就只有一个,如果要传递2个,3个,4个。。。参数怎么办呢?
源码:http://www.bbsftp.com/ArticleShow.asp?ArtID=367

在上一回我们已经实现VB调用VC函数并传递一个long、窗口句柄、字符串指针、结构体指针的参数,并取得了函数的返回值。 这一回我们将实践一下如何传递多个参数。
打开上一回的两个工程(LinkWithVC, vcobj). 切换到VB在form上添加一个按钮 name: cmdTestStruct2, 标题 Test Struct 2。再在ModVC中添加如下代码
Public Function TestArg(lppt1 As POINTAPI, lppt2 As POINTAPI) As Long
Rem
End Function
在cmdTestStruct2的Click事件中
Private Sub cmdTestStruct2_Click()
Dim pt1 As POINTAPI, pt2 As POINTAPI
Call TestArg(pt1, pt2)
MsgBox "x= " & pt1.x & " y=" & pt1.y & vbCrLf & "x= " & pt2.x & " y=" & pt2.y
End Sub
好,现在生成EXE,必然不成功。我们打开log看会是什么错误信息,
Microsoft (R) Incremental Linker Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Form1.OBJ : error LNK2001: unresolved external symbol "private: void __stdcall ModVC::TestArg(void)" (?TestArg@ModVC@@AAGXXZ)
F:\LinkWithVC\LinkWithVC.exe : fatal error LNK1120: 1 unresolved externals
我们看到连接器要求的函数声明和 无参数,1 个参数的相同,经过测试不能在VB模块声明的函数有多少个参数编译后都是 private: void __stdcall FunctionName(void ) 。
好了我们熟练的在VC的类中加上对应的TestArg函数。编译,生成EXE,通过。
下面来解决我们的难题,现在我们从VC程序的角度来说 函数声明是一样的参数个数却是不确定的。这时我们应该会想到C 函数中有一种可变参数的函数,这种函数要求至少有一个确定参数,然后使用一组宏获取其它参数。这里我们先复习一下 C语言中可变参数的用法 
http://redcheek.net/blogview.asp?logID=67
复习完了我们知道函数该怎么写了,如下:
void ModVC::TestArg()
{    
    LPPOINT lppt1,lppt2;
    va_list ap;
    lppt1 = (LPPOINT)this;
    va_start(ap,this);
    lppt2 = va_arg(ap,LPPOINT);
    lppt1->x = 123;
    lppt1->y = 456;
    lppt2->x = 321;
    lppt2->y = 654;

这样子,能行吗,编译试试;
-------------------Configuration: vcobj - Win32 Release--------------------
Compiling...
ModVC.cpp
F:\LinkWithVC\vcobj\ModVC.cpp(72) : error C2102: '&' requires l-value
Error executing cl.exe.

ModVC.obj - 1 error(s), 0 warning(s)
显然编译失败了。
This 是一个 r-value,无法取地址。现在我们知道只要取得了this的地址我们的问题就解决了。那怎么知道this的地址呢。。。不知道大家有什么好方法没有,老实说,我没有,如果谁有更好的方法别忘了告诉我一声哦。
不过我有一个很笨的方法:跟踪调试程序在内存中找答案。
为了便于调试我更改函数内容为如下:
void ModVC::TestArg()
{
    char p[256];
    lstrcpy(p,"break here!");
    _asm mov eax,1;
    _asm mov eax,2;
    _asm mov eax,3;
    _asm mov eax,this;
    _asm mov eax,4;
    _asm mov eax,5;
    _asm mov eax,6;
}
编译,生成EXE。我们现在使用ollydbg来调试 LinkWithVC.exe,打开这个程序,使用od的搜功能,搜索参考字符串,然后在 break here! 哪里设置一个断点。运行程序,点按钮,程序会在下面红色的地方中断。
004012C0  /$ 55             PUSH EBP
004012C1  |. 8BEC           MOV EBP,ESP
004012C3  |. 81EC 00010000  SUB ESP,100
004012C9  |. 8D85 00FFFFFF  LEA EAX,DWORD PTR SS:[EBP-100]
004012CF  |. 68 30604000    PUSH LinkWith.00406030   ; /String2 = "break here!"
004012D4  |. 50             PUSH EAX               ; |String1 = 0012F300
004012D5  |. FF15 C0104000  CALL DWORD PTR DS:[<&KERNEL32.lstrcpyA>] ; \lstrcpyA
004012DB  |. B8 01000000    MOV EAX,1
004012E0  |. B8 02000000    MOV EAX,2
004012E5  |. B8 03000000    MOV EAX,3
004012EA  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
004012ED  |. B8 04000000    MOV EAX,4
004012F2  |. B8 05000000    MOV EAX,5
004012F7  |. B8 06000000    MOV EAX,6
004012FC  |. 8BE5           MOV ESP,EBP
004012FE  |. 5D             POP EBP
004012FF  \. C2 0400        RETN 4
看看上面蓝色的代码,如是我们知道那条绿色的代码对应的源程序就是 mov eax, this;
如是 我们知道了this的地址就是 [ebp+8]