1.  在多种语言之间使用dll的主要方法是定义函数和过程接口,一方提供实现,一方提供声明。各语言都能在dll中查到符合声明的实现,条件是参数具有同样规范,包括参数类型相容,数目相同,位置相同,压栈方式相同。返回值类型不在考虑当中,实际返回类型由使用模块中的声明决定。2.  除了通过函数和过程接口,还可以在dll中export类,这样可以直接从dll中引入类。这只在VC中才行,但要求产生dll和使用dll时使用的是同一版本的VC。例如:
// dll.h 文件
class  __declspec(dllexport) dll {
private:
        int t;
public:
int i;
dll();
void  myshow(char* string);
void  seti(int k);
int   geti();
};// dll.cpp 文件
#include <stdio.h>
#include "dll.h"
dll::dll(){
    i=3;
}
void dll::myshow(char* string){
   printf(string);
}
void dll::seti(int k){
   i=k;
}
int dll::geti(){
   return i;
}
编译即可产生该dll。
使用时,只须
// dll.h
class  dll {
private:
        int t;
public:
int i;
dll();
void  myshow(char* string);
void  seti(int k);
int   geti();
};
#pragma comment (lib,"dll.lib")  // 链入dll//  main.cpp
#include "dll.h"
void main(){
  dll dl;
  dl.myshow("Hello DLL\n");
}
这样可以直接使用这个类。3. 对于不能直接export和import类的情况,只能使用过程和函数来传递。(1)将类的功能封装在一个函数中,用函数传递。如用一个TForm获取参数、密码,可提供一个函数getParam,在这个函数中使用该TForm,只把参数、密码返回。(2)将类的实例通过提供方的函数产生并返回,利用dll中忽略返回值的特性让使用方得到该实例指针,只需保证使用方声明与提供方定义一致即可。在该实例中必须传递类的成员变量和成员函数指针。问题只是采用何种机制放置这些变量和指针。a.  通过虚拟表传出。虚拟函数和普通函数的不同之处在于,前者的函数指针登记在类的每个实例中的一个虚拟函数表当中,后者的地址在编译时由编译器填入。有虚拟函数的类的大小是所有成员变量大小与一个虚表指针大小(4)的总和,这点可从sizeof函数中看出。dll提供方和使用方把要传出的函数登记在虚表中,这样双方就可以通过虚表传递函数指针。使用这种方法必须注意参数规范(是否_cdecl),定义成虚拟函数,注意成员函数顺序。实际上,如果对编译器比较熟的话,可以欺骗编译器,把变量伪装成虚拟函数传出,这样也就可以传出变量了。
例如:(这里只讨论函数的export,对变量应该可以采用某种方法同样export)
// dll.h 文件
class  dll {
private:
        int t;
public:
int i;
dll();
void  myshow(char* string);
void  seti(int &k);
int   geti();
};// dll.cpp 文件
#include <stdio.h>
#include "dll.h"
dll::dll(){
    i=3;
}
void dll::myshow(char* string){
printf("the string:%s\n",str);
}
void dll::seti(int &k){
   i=k;
}
int dll::geti(){
   return i;
}// 用类封装 wrap_dll.h
#include "dll.h"
class wrap_dll {
       dll*  thedll;
public :
        wrap_dll();
virtual void  _cdecl myshow(char* string);
virtual void  _cdecl seti(int &k);
virtual int   _cdecl geti();
        virtual void  _cdecl Delete();  // 提供释放空间的函数
};
extern "C" __declspec(dllexport) wrap_dll* newdll();// wrap_dll.cpp ,产生wrap_dll实例
wrap_dll::wrap_dll(){
   thedll = new dll;
}
void _cdecl wrap_dll::Delete(){
   delete thedll;
   ::delete this;
}
void _cdecl wrap_dll::myshow(char* string){
   thedll->myshow(string);
}
void _cdecl wrap_dll::seti(int &k){
   thedll->seti(k);
}
int _cdecl wrap_dll::geti(){
   return thedll->geti();
}wrap_dll* newdll(){
  return new wrap_dll;
}在使用方使用Delphi:
// a.pas
unit a;
interface
//声明类
type 
delphdll = class
public
   procedure myshow(p:pchar) cdecl;virtual;abstract;
   procedure seti(var k:integer) cdecl;virtual;abstract;
   function  geti:integer cdecl;virtual;abstract;
   procedure Delete cdecl:virtual;abstract; // 用abstract可不定义函数体而通过编译
end;
// 链入dll
function newdll:delphdll cdecl;external 'dll.dll';implementation// nothingend.//main.dpr
program main;
uses ..., a in 'a.pas', ...;
var del_dll:delphdll;
    k :integer;
begin
  k := 10;
  del_dll:=newdll;
  del_dll.myshow('hello DLL');
  del_dll.seti(k);
  del_dll.Delete;
end.b. 直接通过类传出。将类定义成包含函数指针,不用虚拟函数表。这提供了更直接的方式,但也更为复杂。把类中每个要export的函数都用一个函数指针存储其位置,在构造函数中为其赋值。其他的成员变量保持不变。在接受方同样声明一个这样的类以便使指针不错位。有以下注意事项:A 保证成员变量和函数指针在定义和使用方声明中顺序相同。B Delphi中的类在每个实例的地址开始处有四个字节的内容,可能是结束后的跳转地址、指向本类中函数定义的指针或其他一些东西(如果是record而不是类,这四个字节填入固定的数0x0000000E)。若由C++ export类,须为这些字节空出空间来,由于自己管理释放而不使用Delphi的机制,这些字节不会被用到。C 编译器(不论是C++还是Delphi)在调用类的成员函数时会将当前实例的指针作为参数压栈,该指针会成为最左边的一个参数,如 p->xx(i,str)会变成xx(p,i,str)。当我们使用函数指针来调用函数以模仿调用类中成员函数时,应当显式地加入此参数。
例如:// dll.h
class  dll {
unsigned nonuse;  // 给Delphi空位
private :
        int kk;
public:
int ii;
dll();
void __cdecl myshow(char* string);
void (__cdecl dll::*myshow1)(char* string);// 准备存储函数指针,dll::是为了赋值时通过编译
void __cdecl setii(int i);
void  (__cdecl dll::*setii1)(int i);
int  __cdecl getii();
int   (__cdecl dll::*getii1)();
void __cdecl Delete();
void (__cdecl dll::*Delete1)();
};
 extern "C" __declspec(dllexport) dll*  newdll();//dll.cpp
#include <stdio.h>
#include "dll.h"void dll::myshow(char* str){
printf("the string:%s\n",str);
}
int dll::getii(){
return ii;
}
void dll::setii(int i){
ii = i;
}
void dll::Delete(){
::delete this;
}
dll::dll(){
ii = 32;
myshow1=myshow;  // 给函数指针赋值
getii1 = getii;
setii1 = setii;
Delete1=Delete;
}dll* newdll(){
     return new dll;
}使用方使用Delphi
//a.pas
unit a;interface
type
 delphdll = class
 private               // 这里会有一个指针
        kk:integer;
 public
        ii:integer;
        myshow1 :procedure (p:mydll;str:pchar);cdecl;  // 过程指针,p为显式参数--实例指针
        setii1  :procedure (p:mydll;i:integer);cdecl;
        getii1  :function (p:mydll):integer;cdecl;  // 函数指针
        Delete1 :procedure (p:mydll); cdecl;
        procedure myshow(str:pchar); cdecl;
        procedure setii(i:integer);cdecl;
        function getii:integer;cdecl;
        procedure Delete; cdecl;
 end; function newdll:delphdll  cdecl;external 'dll.dll';
implementationprocedure delphdll.myshow(str:pchar);cdecl;
begin
  myshow1(self,str);  // 传入实例指针
end;
procedure delphdll.setii(i:integer);cdecl;
begin
   setii1(self,i);
end;
function delphdll.getii:integer;cdecl;
begin
   getii:= getii1(self);
end;
procedure delphdll.Delete;cdecl;
begin
   Delete1(self);
end;end.//main.dpr
program main;
uses ...,a in 'a.pas', ...;
var del_dll:delphdll;
    k :integer;
begin
  k := 10;
  del_dll:=newdll;
  del_dll.myshow('hello DLL');
  del_dll.setii(k);
  k:=del_dll.getii;
  del_dll.Delete;
end.这些仅是几个例子而已,知道了上述原则后,在C,C++,Delphi之间导出和引入类应该没有什么问题了。比较上述方法,在C++与Delphi之间共享类时,用虚表比较好,因为二者的虚表结构完全一样,传参也一样,不会有诸如将实例指针压栈、在实例起始地址处存在四字节等情况,而且,我还没有测试上面直接传类方法中有虚函数即有虚表指针时,以及类似int &k的参数在C风格的函数指针中用int* k 代替时,是否会有影响。但若有时间应该可以完善该方法。它提供了完全的类映射,更具有真实的传递类的性质。

解决方案 »

  1.   

    呵呵,谢谢DWGZ ,
    今天看了两篇这方面的了,
    收了,------------------
    在错的时间遇见错的人 是一种伤痛 
    在错的时间遇见对的人 是一种遗憾 
    在对的时间遇见错的人 是一种心伤 
    只有在对的时间遇见对的人才是一种幸福
      

  2.   

    收藏 hehe^^ 好帖子,多点这样的总结帖子就是好---------
    春困秋乏夏打盹,睡不醒的冬三月 ^^!
      

  3.   

    备用。
    ----------------
    想不到 FrameSniper老大也喜欢她们的歌,如果有空把它们压成MP3传给小弟啊