关于BCB和MASM的混合编程

by Fpc rls 2006.06

本文附件下载:asminbcb.zip

最近受了点刺激  ,不过佛曰:不可说,不可说。那只能说点别得,以前想写小程序,研究过BCB与MASM的混合编程,现在没有时间进一步深入了,现在整理一下当时的笔记,仅作个抛砖引玉。




¥为什么?

关于混合编程
masm的优势在于macro的强大,对细节的精确控制;
C/C++的优势在于模块化,GUI容易控制
为什么不用TASm:支持库不全,它的macro不太会用,现在不升级
为什么不是BCB而不是VC:首先不会VC;好像VC也不存在与masm的兼容问题。BCB需要你用OMF方式来编译asm模块,在api的使用上有很大麻烦,本文主要是解决这个问题


¥怎样做?

一、在高级语言中用 asm{ }或宏定义方式,可应用在小范围内,缺点是只能用最初级的asm,C的编译器很难支持masm的高级应用
二、C中使用masm编译的obj或lib,优势明显,缺点是很难保证兼容性,即编译器难以通过
三、使用dll,很方便,但是要多出文件了


具体方法:
1、__cdecl 方式:
masm中类似于:
.386
.model flat
option casemap:none
...
PUBLIC  _Add3

.code
...
_Add3:
...
ret
end

注意开始的.model 中无stdcall开关,所以标号的声明前面需要加个_
然后编译(不可以应用任何系统lib、任何api)
在C中,声明
    #ifdef __cplusplus
    extern "C" {
    #endif

    int __cdecl Add3(int, int);

    #ifdef __cplusplus
    } /* extern "C" */
    #endif
注意声明中无_,因__cdecl的应用会加上_
此方式的缺点就是masm中不能应用api,因masm的lib都是stdcall方式(BCB的lib可不可以用??)




2、__stdcall 方式:
masm中类似于:
.386
.model flat, stdcall
option casemap:none
...
PUBLIC  Add3

.code
...
Add3:
...
ret
end

注意开始的.model 中有stdcall开关,所以标号的声明前面不需要加_,可以应用api
然后编译
在C中,声明
    #ifdef __cplusplus
    extern "C" {
    #endif

    int __stdcall _Add3(int, int);

    #ifdef __cplusplus
    } /* extern "C" */
    #endif
注意声明中需要加_,因__stdcall的应用不会加_
在无api引用时,此方式可以正常使用,但如果masm中使用了api,BCB编译时提示masm的lib不是OMF格式
解决:可以用BCB的lib..下一个问题是masm生成的obj中引入的api前面有_(因stdcall方式),现在是在obj中删除

如果api被声明成syscall方式,则不产生_,但调用者会错误的修复stack(in masm)
如果api被声明成c方式,产生_,而且调用者会错误的修复stack(in masm)
如果api被声明成stdcall方式(应该是默认方式),则产生_,调用者不会修复stack(in masm),但生成的api索引还会加上一个@8之类的尾巴,BCB的lib是非常干净的,_和尾巴导致BCB编译器不认。解决方法是手工在obj清除api名称中的垃圾,这差不多是目前最佳解决方案。



¥例:

in masm:

.386
.model flat, stdcall        ;使用flat, stdcall模式
option casemap:none        ;大小写敏感
includelib kernel32.lib        ;要使用BCB的lib, 它是omf格式,不要用masm带的lib
GetModuleHandleA  proto :dword    ;声明要使用的api,考虑包含masm的inc文件
PUBLIC  c Add3  ; :dword, :dword    ;声明对外的过程,注意我们使用C模式,编译后前面会加上_

.code
Add3  proc  near c aa:dword, bb:dword  ;proc定义部分,编译后会加上push ebp...因在下面我们用到了变量
;Add3:
  ;jmp  @f
  invoke  GetModuleHandleA, 0    ;测试使用api,正常
@@:
  mov  eax, aa        ;测试使用变量
  add  eax, bb        ;
  ret          ;返回,由调用者自行清理stack
Add3  endp          ;过程结束
end            ;汇编结束

注意BCB需要OMF格式的obj,要用bldomf.bat编译asm模块

这样生成的obj中api name前后都有多余的内容,为使之与BCB的lib兼容,必须手动清除。清除办法就是用objfixer清理一下,之后就可以在BCB里面连接了。

in BCB
in *.h file:
#ifdef __cplusplus
extern "C" {          ;声明外部C过程
#endif

int __cdecl Add3(int, int);      ;用C declare 方式,与masm中保持一致

#ifdef __cplusplus
} /* extern "C" */
#endif

in *.cpp file:
...
    ShowMessage(Add3(33,42));
...

这样就可以通过编译




¥现存难点:
变量及过程的相互引用还是很麻烦的,需要很小心的处理。另外不同call模式及编译器的工作方式可能会导致堆栈平衡问题,需要仔细的debug


¥写得比较乱,如遇问题,自己解决,也欢迎QQ交流:6565XXX





¥附:关于不同调用模式(摘自masm、bcb帮助)
Calling Conventions

                              C    SYSCALL STDCALL  BASIC  FORTRAN  PASCAL
                          +-------+-------+-------+-------+-------+-------+
  Leading Underscore      |   X   |       |   X   |       |       |       |
                          |-------+-------+-------+-------+-------+-------|
  Capitalize All          |       |       |       |   X   |   X   |   X   |
                          |-------+-------+-------+-------+-------+-------|

  Arguments Left to Right |       |       |       |   X   |   X   |   X   |
                          |-------+-------+-------+-------+-------+-------|
  Arguments Right to Left |   X   |   X   |   X   |       |       |       |
                          |-------+-------+-------+-------+-------+-------|
  Caller Stack Cleanup    |   X   |       |   *   |       |       |       |
                          |-------+-------+-------+-------+-------+-------|
  BP Saved                |       |       |       |   X   |   X   |   X   |

                          |-------+-------+-------+-------+-------+-------|
  :VARARG Allowed         |   X   |   X   |   X   |       |       |       |
                          +-------+-------+-------+-------+-------+-------+
 
  * The STDCALL language type uses caller stack cleanup if the :VARARG
    parameter is used. Otherwise, the called routine must clean up the
    stack.
 
  The language type (langtype) determines the naming and calling conventions
  used by the assembler. This allows you to share code and data with

  modules written with other languages. Set the language type with the
  .MODEL or OPTION LANGTYPE: directives or with the /G<x> command-line
  option. Several directives allow you to specify a langtype
  parameter to temporarily override the language type.
 
  You can use the /H command-line option to limit the length of names
  sent to the object file. Use this option to work with languages that
  limit the maximum length of identifiers.
                                    -o-


in BCB

Calling Convention options tell the compiler which calling sequences to generate for function calls. The C, Pascal, and Register calling conventions differ in the way each handles stack cleanup, order of parameters, case, and prefix of global identifiers.

You can use the ___cdecl, ___pascal, ___fastcall, or ___stdcall keywords to override the default calling convention on specific functions.

In C++Builder code, you will normally use the default C calling convention.

MSVC __fastcall

(Command-line switch: -pm)

This option tells the compiler to substitute the __msfastcall calling convention for any function without an explicitly declared calling convention.

C

(Command-line switch: -pc, -p-)

This option tells the compiler to generate a C calling sequence for function calls (generate underbars, case sensitive, push parameters right to left). This is the same as declaring all subroutines and functions with the ___cdecl keyword. Functions declared using the C calling convention can take a variable parameter list (the number of parameters does not need to be fixed).

You can use the ___pascal, ___fastcall, or ___stdcall keywords to specifically declare a function or subroutine using another calling convention.

Pascal

(Command-line switch: -p)

This option tells the compiler to generate a Pascal calling sequence for function calls (do not generate underbars, all uppercase, calling function cleans stack, pushes parameters left to right). This is the same as declaring all subroutines and functions with the 
___pascal keyword. The resulting function calls are usually smaller and faster than those made with the C (-pc) calling convention. Functions must pass the correct number and type of arguments.

You can use the ___cdecl, ___fastcall, or ___stdcall keywords to specifically declare a function or subroutine using another calling convention.

Register

(Command-line switch: -pr)

This option forces the compiler to generate all subroutines and all functions using the Register parameter-passing convention, which is equivalent to declaring all subroutine and functions with the ___fastcall keyword. With this option enabled, functions or routines expect parameters to be passed in registers.

You can use the ___pascal, ___cdecl, or ___stdcall keywords to specifically declare a function or subroutine using another calling convention.

Standard Call

(Command-line switch: -ps)

This option tells the compiler to generate a Stdcall calling sequence for function calls (does not generate underscores, preserve case, called function pops the stack, and pushes parameters right to left). This is the same as declaring all subroutines and functions with the ___stdcall keyword. Functions must pass the correct number and type of arguments.

You can use the ___cdecl, ___pascal, ___fastcall keywords to specifically declare a function or subroutine using another calling convention.

Default = C (-pc)



  • 标 题: 答复
  • 作 者:Anskya
  • 时 间:2006-07-01 00:07

学习了~
不过还是要解决两个问题
毕竟BCB是OMF格式的

首先解决的是~函数参数传递方式(这个很好解决一般都可以搞定的~use C)

下面要解决的就是~obj中导出函数的问题了
以前写过那个说明的~COFF格式回在使用的API前面自动连接~__imp_
还有后面增加@X的标号
这个问题要解决不然函数无法进行连接的

不过看了LZ的文章~小弟我深感自己的渺小.....
感谢LZ这么好的文章~谢谢~我要把这篇文章顶上去。。
让全坛子的人都看到~~LZ原谅我的自私吧。。。