一个DELPHI程序要调用DLL,而DLL中的函数名,参数个数,参数类型为不定

一个DELPHI程序要调用DLL,而DLL中的函数名,参数个数,参数类型为不定

请求思路,一个DELPHI程序要调用DLL,而DLL中的函数名,参数个数,参数类型为不定,
要调用的DLL中的函数名、参数个数、参数类型要可以在窗口中Edit中动态输入

我的意思就是函数不能在程序中事先定义,因为一切都是不固定的,要根据手工输入的
参数个数、参数类型来调用这个函数,有些函数是3个参数,有些函数是4个参数
手工输入如下:
函数名:abcd
参数1:a1 string型  
参数2:a2 integer型
参数3:a3 boolean型
。。。。。
//参数个数:不定 参数类型:不定

要根据这些东西来调用这个函数

[red]再如:我的程序是先期开发的,我要在程序中留一个调函接口给其它用户,其他用户写好
DLL后,将函数名参数个数参数类型返回值类型(不是固定的) 保存在一个文本文件中,我
的程序通用读这个文本文件调用这个DLL中的函数[/red]


[blue]楼下的各位,我用过一个流程开发工具,调用的DLL就是普通的DLL,而并不是所谓的
array of const 之类的,因为这些被调用的DLL是我自己写的
请看截图 http://www.wansoft.com.cn/aaa.jpg   [/blue]  

来自: springson, 时间:2003-11-23 13:00:00, ID:2311468
好像這樣的動態庫是做不出來的。除非人為設一個很大的數組,要不就用Variants類型,但是dll支持不是很好。  

来自: hongxing_dl, 时间:2003-11-23 13:08:00, ID:2311475
同意楼上的做法~~~~~~~~Variant数组  

来自: 一条明, 时间:2003-11-23 13:15:00, ID:2311493
“要调用DLL,而DLL中的函数名,参数个数,参数类型为不定,“
  只要主要参数对了 就可以 了  


  

来自: porsche, 时间:2003-11-23 17:54:00, ID:2311852
我的意思是  不是固定地调用哪个DLL中的哪个函数,
而是 如:
在Edit1中输入库路径,在EDIT2中输入函数名,在EDIT3输入函数参数,
参数个数、类型不是固定的
  

来自: springson, 时间:2003-11-23 18:16:00, ID:2311871
我明白了,你是要輸入一個函數名,後面的都是參數,然後想得到相應的處理結果。那你可以用查找到那個名字,然後再調用就行了。比如:dllhandle:=loadlibrary(dll文件)(可以輸入的文本),procaddr:=getprocaddress(dllhandle,調用的函數)(也可自己的文本),然後procaddr(你的參數);  

来自: 桦树皮, 时间:2003-11-23 18:50:00, ID:2311891
可以调用windows的rundll32.exe来加载dll中的函数,这个程序完全可以满足你。  

来自: porsche, 时间:2003-11-23 22:37:00, ID:2312146
TO 桦树皮,RUNDLL32 不能带变量参数的,
如 Winexec('rundll32 aaa.dll,abcd(a1,a2,a3)',SW_HIDE)怎么办?

TO springson, procaddr中的参数个数、参数类型也必须要在程序中事先type好啊!?!
我的意思就是函数不能在程序中定义,因为一切都是不固定的,要根据手工输入的
参数个数、参数类型来调用这个函数,有些函数是3个参数,有些函数是4个参数
手工输入如下:
函数名:abcd
参数个数:不定
参数1:a1 string型  
参数2:a2 integer型
参数3:a3 boolean型
。。。。。
要根据这些东西来调用这个函数  

来自: ETimeFly, 时间:2003-11-23 22:56:00, ID:2312174
帮你一把@  

来自: springson, 时间:2003-11-24 7:44:00, ID:2312291
事先type好也行,不type也行,動態加載就可以了。然後在dll中多聲明一些函數,都是override的,這樣什麼參數都可以用。  

来自: wlmmlw, 时间:2003-11-24 8:16:00, ID:2312333
思路:
  把要传入的实参值写到一个Buffer里, 调用你的DLL里的一个前期分析函数如
  PreProcess, 它的参数就是要真正调用的函数的名称,一个Buffer和Buffer的大小.
  因为你要调用的具体函数是知道参数的具体结构的, 所以你可以在
  PreProcess里写入如case (id) of
                     
                    end;
  这样的结构然后来分解参数,再传给具体的函数.  

来自: jxc163, 时间:2003-11-24 16:32:00, ID:2313970
高人  

来自: 惊天动地, 时间:2003-11-24 16:38:00, ID:2313994
传递结构指针行吗  

来自: 草原牧歌, 时间:2003-11-24 16:42:00, ID:2314011
我接触dll不久,具我了解,dll是已经编译好的程序了,怎么能在已经编译好的程序里在做类型声明等的事情呢?即使有办法,也是不可取的!太浪费精神了!  

来自: hstod, 时间:2003-11-24 16:51:00, ID:2314034
关注,我每次写程序总是要多写好多与dll沟通的函数,相当麻烦与楼主一样,关注中  

来自: springson, 时间:2003-11-24 17:02:00, ID:2314060
寫很多與dll溝通的函數是有好處的。動態判斷說不定哪天系統就奔潰了。  

来自: porsche, 时间:2003-11-24 22:21:00, ID:2314552
to springson,你说的办法是很好,但是
假如一个外部函数有10个参数,我是说假如,一般没这么多参数,共使用了 5种变量类型,
每种变量类型位置都不固定,那要要写多少句啊?  

来自: hqiang, 时间:2003-11-24 22:39:00, ID:2314581
用函数重载就可以了,不需要判断
ex:
function abcd(a1:interger);overload;
function abcd(a1:string);overload;
function abcd(a1:boolean);overload;  

来自: porsche, 时间:2003-11-26 0:22:00, ID:2317129
UP者也有分  

来自: vf, 时间:2003-11-26 10:41:00, ID:2317691
完全可以啊,你把库路径及文件名、函数、参数列表放到一个配置文件中,程序调用的时候去读这个配置就行了。其实关键就是参数列表的问题,类型和数量不确定。偶是用以下方法实现的:函数就不要参数,把参数放到配置文件中去,DLL直接去配置文件中取(因偶的只有两个类型int&string相对简单)举个例子:
[Parameters]
P1=123+int //前面是值后面跟类型,折分转换即可
P2=Name+string

如果有很多数据类型就建个类型列表,一一对应转换
  

来自: Rudder, 时间:2003-11-26 12:02:00, ID:2317988
有够难
  

来自: porsche, 时间:2003-11-26 20:56:00, ID:2319283
[red]以上的各位,我用过一个流程开发工具,调用的DLL就是普通的DLL,而并不是所谓的
array of const 之类的,因为这些被调用的DLL是我自己写的
请看截图 http://www.wansoft.com.cn/aaa.jpg  [/red]  

来自: lich, 时间:2003-11-26 21:03:00, ID:2319300
再实现一个封装版的DLL,
其中,调用某个公开的函数,可以获得所有的函数列表,
函数有一个指针列表,传入字符串数组,再有一个参数表示传入的参数的个数,
此DLL进行转化,并调用实际的DLL  

来自: lich, 时间:2003-11-26 21:08:00, ID:2319309
SQLServer中实现extenal stored procedure 用的就是类似的技术,

就连Rundll32 所调用的dll也必须编写特殊的接口函数,才能被rundll32调用
rundll32 是不能掉用普通的DLL的  

来自: lich, 时间:2003-11-26 21:15:00, ID:2319318
我说的没错吧? MSDN 上是这样说的:
它将命令行参数,直接传递给了某个函数,
能被Rundll32调用的函数的形式必须是
void CALLBACK EntryPoint(
  HWND hwnd,        // handle to owner window
  HINSTANCE hinst,  // instance handle for the DLL
  LPTSTR lpCmdLine, // string the DLL will parse
  int nCmdShow      // show state
);


Platform SDK: SDK Tools

Rundll32

The Run DLL utility (Rundll32.exe) included in Windows enables you to call functions exported from a 32-bit DLL. These functions must have the following syntax:


void CALLBACK EntryPoint(
  HWND hwnd,        // handle to owner window
  HINSTANCE hinst,  // instance handle for the DLL
  LPTSTR lpCmdLine, // string the DLL will parse
  int nCmdShow      // show state
);
Note that EntryPoint is a placeholder for the actual function name. For a list of possible show states, see WinMain.

The following is the command-line syntax for Rundll32:

rundll32 DllName,FunctionName [Arguments]

DllName
Specifies the name of the DLL. The name cannot contain spaces, commas, or quotation marks. The utility searches for the DLL using the search criteria documented for the LoadLibrary function. Therefore, it is best to use the short name and provide a full path for the DLL.
FunctionName
Specifies the name of the function to call in DllName. Requires a comma (without no spaces) between DllName and FunctionName.
Arguments
Optional arguments for FunctionName.
Rundll32 loads the specified DLL using LoadLibrary, obtains the address of the function using the GetProcAddress function, and calls the function with the specified arguments, if any. When the function returns, Rundll32 unloads the DLL and exits.

It is possible to create a Unicode version of the function. Rundll32 first tries to find a function namedEntryPointW. If it cannot find this function, it tries EntryPointA, then EntryPoint. To create a DLL that supports ANSI on Windows Me/98/95 and Unicode otherwise, export two functions: EntryPointW and EntryPoint.

  

来自: lich, 时间:2003-11-26 21:19:00, ID:2319321
所以,楼主的要求是很难实现的,
如果能实现的话,就只有一种方法,
那就是,动态的构造调用堆栈,
而且还很危险,参数传递错误,就会Crash

你输入的参数也无法确定它的类型

最好是模拟Rundll32 ,如果要调用普通的DLL,那么需要对这些DLL进行二次封装
将函数参数类型全都封装成上面所说的形式

相关链接
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tools/tools/rundll32.asp

http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/q164/7/87.asp&NoWebContent=1

http://support.microsoft.com/default.aspx?scid=kb;EN-US;135068  

来自: lich, 时间:2003-11-26 21:21:00, ID:2319325
可以根据输入的参数类型,
用汇编语言来将参数压入堆栈,然后再调用,
必须保证你输入的参数和实际的DLL需要的严格一致  

来自: porsche, 时间:2003-11-26 22:03:00, ID:2319382
TO LICH,谢谢你的回答,
不知你看过 http://www.wansoft.com.cn/aaa.jpg  没有?
这是一个广州恒讯达公司CALL CENTER交换机的流程生成器中调用用户自定义函数的
一个截图,流程生成器中共有8种变量类型byte,long,small,string(即pchar),
boolean,struct(结构), Dynamic Memory(既内存流)
我试着写了个DLL,一个函数,定义了30个参数,各种变量类型都杂乱无序地用到了,
如果事先在程序中TYPE好或重截的话,你算算应该type多少行语句?
流程生成器生成的是一些代码,我分析过无非是一些数据,并未生成其他动态库,然后一个
SERVER程序运行时调用这些代码,实实在在是实现了这个功能,而且容错能力还不错,调用
失败不会导致程序崩溃
  

来自: lich, 时间:2003-11-26 22:18:00, ID:2319409
实际上,在Delphi中使用代码块异常保护功能,
调用出错并不会有太大的影响,但是错误可能会导致系统崩溃

我想了一种方法,我先试一下,行的话,我贴出代码:  

来自: lich, 时间:2003-11-26 23:39:00, ID:2319468
我已经实现了一种方法,例子程序在这里:
http://www1.51-51.com/pub/args2.rar

支持三种数据类型
S,I,D
表达式样例:

运行外部程序----
kernel32.dll::WinExec S:notepad.exe I:1
显示消息框-----
user32.dll::MessageBoxA I:0 S:"Hello hahaha" S:"TT01 ok" I:64

大家看看,提点意见  

来自: lich, 时间:2003-11-26 23:41:00, ID:2319472
目前只能处理输入的参数,
也只支持 stdcall 的调用  

来自: lich, 时间:2003-11-27 10:10:00, ID:2319707
例子中的返回值,不对  

来自: cheylin, 时间:2003-11-27 10:13:00, ID:2319715
我觉得可以实现,因为我们用delphi通过webservice调用EJB时实现了类似的功能;主要思路如下:
在DLL中定义一个公共接口函数,带上两个参数:一个是要调用的过程名,另一个是被调用的过程的参数数组,如
procedure iMethod(procName: String; vars Array of String);
begin
  //将所有可能调用的过程进行枚举,当然vars[]中的数据类型在这里要进行转换
  if procName = 'Add' then
     Add(Vars[0],vars[1]...)          //相应的处理过程需另外编写
  else if procName = 'Max' then
     Max(Vars[0],vars[1]...)          //相应的处理过程需另外编写
  ......
end;

在每次更改了DLL中的方法、或新增加了方法,只需在iMethod中再添加一个else if ****就行  

来自: lich, 时间:2003-11-27 10:28:00, ID:2319754
返回值的问题已经修正,
同时增加了几个调用的示例:

--启动记事本
kernel32.dll::WinExec S:notepad.exe I:1
--显示消息框,并获得返回值
user32.dll::MessageBoxA I:0 S:"Hello hahaha" S:"TT01 ok" I:67
--查找当前程序窗口的句柄
user32.dll::FindWindowA I:0 S:Form1
--赋值本程序到C盘根目录下,如果存在,则覆盖,强制成功,返回1表示成功
kernel32.dll::CopyFileA S:./Project1.exe S:C:/test.exe I:0

当然,你自己可以调用更多的API函数,或者自己DLL中的函数

例子程序到这里下载:
http://www1.51-51.com/pub/args2.rar

  

来自: lich, 时间:2003-11-27 16:14:00, ID:2319895
现在也支持传递变参了,
即按地址传递,并可获取传回的字符串参数和整型参数,
譬如: 对API函数 GetComputerName 的调用  

来自: porsche, 时间:2003-11-27 20:14:00, ID:2320296
Lich,太谢谢你了,能不能把代码发给我?再次感谢 :)
4719373@163.com  

来自: lich, 时间:2003-11-27 22:15:00, ID:2320684
这里贴出关键部分的代码:

type
  TArg = record
    ArgType: Integer;
    S: String;
    I: Integer;
    D: Double;
  end;

  TWords = array of String;

  TFunInfo = record
    DllName: String;
    FunName: String;
    Params: array of TArg;
    Ret: Integer;
  end;



..................
function ParseArg(S: String): TArg;
var
  m: Integer;
  t1, t2: String;
  c: Char;
begin
  Result.ArgType := 0;
  Result.S := '';
  Result.I := 0;
  Result.D := 0.0;
  m := Pos(':', S);
  if m > 0 then
  begin
    t1 := UpperCase(Copy(S, 1, m - 1));
    t2 := Copy(S, m + 1, Length(S));
  end;
  if Length(t1) = 1 then
  begin
    c := t1[1];
    case c of
      'S':  //String
      begin
        Result.ArgType := 1;
        Result.S := UnQuoteString(Trim(t2));
      end;
      'I':  //Integer
      begin
        Result.ArgType := 2;
        Result.I := StrToIntDef(t2, 0);
      end;
      'D', 'F':  //Double
      begin
        Result.ArgType := 3;
        Result.D := StrToFloatDef(t2, 0.0);
      end;
    end;
  end
  else
  begin
    if (t1 = 'INT') or (t1 = 'INTEGER') then
    begin
      Result.ArgType := 1;
      Result.S := UnQuoteString(Trim(t2));
    end
    else if (t1 = 'STR') or (t1 = 'STRING') then
    begin
      Result.ArgType := 2;
      Result.I := StrToIntDef(t2, 0);
    end
    else if (t1 = 'FLOAT') or (t1 = 'DOUBLE') then
    begin
      Result.ArgType := 3;
      Result.D := StrToFloatDef(t2, 0.0);
    end
    else if (t1 = 'VI') or (t1 = 'VINTEGER') then
    begin
      Result.ArgType := 12;
      Result.I := StrToIntDef(t2, 0);
    end;
  end;
end;

function ParseFun(S: String): TFunInfo;
var
  m: Integer;
  v: TWords;
  i: Integer;
begin
  Result.DllName := '';
  Result.FunName := '';
  Result.Ret := 0;
  v := SplitWithSpace(S, '"');
  if Length(v) > 0 then
  begin
    m := Pos('::', v[0]);
    if m > 0 then
    begin
      Result.DllName := Copy(v[0], 1, m - 1);
      Result.DllName := UnQuoteString(Result.DllName);
      Result.FunName := Copy(v[0], m + 2, Length(v[0]));
    end;
  end;
  if Result.DllName <> '' then
  begin
    SetLength(Result.Params, Length(v) - 1);
    for i := 1 to Length(v) - 1 do
    begin
      Result.Params[i - 1] := ParseArg(v[i]);
    end;
  end;
end;

function RunDllFun(var fun: TFunInfo): Integer;
var
  i, r, t: Integer;
  d: Double;
  pd: PIntegerArray;
  t1, t2: Integer;
  dll: Integer;
  f: Pointer;
  p: PChar;
begin
  Result := 0;
  dll := LoadLibrary(PChar(fun.DllName));
  try //finally
    try //except
      if dll <> 0 then  //load ok
      begin
        f := GetProcAddress(dll, PChar(fun.FunName));
        if Assigned(f) then
        begin
          for i := Length(fun.Params) - 1 downto 0 do
          begin
            case fun.Params[i].ArgType of
              0:
              begin
                asm
                  push 0
                end;
              end;
              1:
              begin
                SetLength(fun.Params[i].S, 500);
                p := PChar(fun.Params[i].S);
                asm
                  push p
                end;
              end;
              2:
              begin
                t := fun.Params[i].I;
                asm
                  push t
                end;
              end;
              3:
              begin
                d := fun.Params[i].D;
                pd := @d;
                t1 := pd[0];
                t2 := pd[1];
                asm
                  push t2
                  push t1
                end;
              end;
              12: //整数变参
              begin
                t := Integer(@(fun.Params[i].I));
                asm
                  push t
                end;
              end;
            end;
          end;
          // call the function
          asm
            call f;
            mov r, eax
          end;
          fun.Ret := r;
        end
        else
        begin
          Result := -3;
        end;
      end
      else
      begin
        Result := -4;
      end;
    except
      Result := -2;
    end;
  finally
    if dll <> 0 then FreeLibrary(dll);
  end;
end;

function RunDLL2(S: String; var Fun: TFunInfo): Integer;
begin
  Result := -1;
  Fun := ParseFun(S);
  if Fun.DllName <> '' then
  begin
    try
      Result := RunDllFun(Fun);
    except
      ShowMessage('发生了调用异常');
    end;
  end
  else
    raise Exception.Create('解析失败,可能是格式不正确');

end;


.........................................
procedure TForm1.Button2Click(Sender: TObject);
var
  s: String;
  i, r: Integer;
  t: String;
  FunInfo: TFunInfo;
  function GetPaStr(pa: TArg): String;
  begin
    case pa.ArgType of
      0:  Result := 'Null:0';
      1:  Result := 'S:' + PChar(pa.S);
      2:  Result := 'I:' + IntToStr(pa.I);
      3:  Result := 'D:' + FloatToStr(pa.D);
      12: Result := 'VI:' + IntToStr(pa.I);
      else Result := 'UnKnown';
    end;
  end;
begin
  //表达式调用
  s := Edit1.Text;
  if Trim(s) <> '' then
  begin
    r := RunDLL2(s, FunInfo);
    if r = 0 then
    begin
      for i := 0 to Length(FunInfo.Params) - 1 do
      begin
        if i = 0 then t := GetPaStr(FunInfo.Params[i])
        else t := t + #13#10 + GetPaStr(FunInfo.Params[i]);
      end;
      ShowMessage('调用成功,返回值:' + IntToStr(FunInfo.Ret) + #13#10 +
        '参数列表:' + #13#10 + t);
    end
    else
      ShowMessage('调用失败,错误代码:'+ IntToStr(r));
  end
  else
    ShowMessage('请输入函数名称及参数');
end;
  

来自: porsche, 时间:2003-11-28 12:06:00, ID:2321429
Lich,太谢谢你了,能不能把
UnQuoteString  SplitWithSpace 函数的代码也贴出来?谢谢
能不能加我一下? QQ:4719373
[red]还有一个问题就是:
如 function abcd(a1:string;var a2:string):integer; stdcall;
你的这个例子好象还不能取得 a2 的返回值啊[/red]  

来自: lich, 时间:2003-11-28 13:38:00, ID:2322010
动态链接库中不要使用String类型,来做参数,
有很多跟共享内存有关的问题,而且其它的语言无法正常调用,
所以,建议沿用Windows API的接口方式,采用PChar
或者指针类型  

来自: porsche, 时间:2003-11-28 13:41:00, ID:2322024
to lich ,
SplitWithSpace 函数我已写好了,UnQuoteString  是作什么用的呢?  

来自: lich, 时间:2003-11-28 13:43:00, ID:2322030
这两个函数太简单了,
一个是利用空格将字符串分成字符串数组
另一个是删除字符串两端的双引号  

来自: porsche, 时间:2003-11-28 16:26:00, ID:2322492
[red]TO Lich,现在能运行了,
但是,如:
function abcd(a1:pchar;var a2:pchar;var a3:integer):short; stdcall;

如何取得 a2,a3 的返回值?[/red]  

来自: porsche, 时间:2003-11-28 16:51:00, ID:2322561
谢谢lich,我返回PChar类型写错了 应该用 StrPcopy ,给你分了,向你学习  

来自: lich, 时间:2003-11-28 19:30:00, ID:2322843
function SplitWithSpace(const S: String; QuoteChar: Char): TWords;
var
  i, m, n: Integer;
  Len: Integer;
  ct: Integer;
  InQuote: Boolean;
begin
  Len := Length(S);
  i := 1;
  ct := 0;
  InQuote := False;
  while i <= Len do
  begin
    //跳过一到多个空格
    while (i <= Len) and (S[i] = ' ') do i := i + 1;
    m := i;
    while (i <= Len) and ((S[i] <> ' ') or InQuote) do
    begin
      if S[i] = QuoteChar then
        InQuote := not InQuote;
      i := i + 1;
    end;
    n := i;
    if n > m then
    begin
      SetLength(Result, ((ct + 10) div 10) * 10);
      Result[ct] := Copy(S, m, n - m);
      ct := ct + 1;
    end;
  end;
  SetLength(Result, ct);
end;

function UnQuoteString(const S: String): String;
var
  m, n: Integer;
begin
  if Length(s) = 0 then Exit;
  m := 1;
  if S[1] = '"' then m := 2;
  n := Length(S);
  if S[n] = '"' then n := n - 1;
  Result := Copy(S, m, n - m + 1);
end;  

来自: kelisten, 时间:2005-7-8 10:47:37, ID:3127887
实际上,我们在实现的DLL中很多时候还是有回调等功能的,如在DLL函数中进行事件返回,如下定义:
TNotifyEvent = procedure (i: Integer); stdcall;
在DLL中的函数如下:
  fundll(a; TNotifuEvent): Boolean; stdcall;
在上面进行使用的时候是否可以使用指针方式进行呢,如果可能能否给些代码看看。
谢谢!!
  

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值