VS2010-C#引用delphi6下编译的dll文件,读出byte[]报错“Cannot marshal 'return value': Invalid managed/unmanaged type combination.”
delphi6下编译的dll文件 源码如下:
library dataoutdll;
uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, DB, ADODB, Grids, DBGrids, DBClient, StdCtrls, Provider;type
   byteBuffer = array[0..1024] of byte;
var
mydataset:tclientdataset;
mydatasource:tdatasource;
myprovider:  tdatasetprovider;
myquery:tadoquery;
sqlcmd:TStringList;ms1,ms2,ms3:TMemoryStream;
buf: bytebuffer;
{$R *.res}
function getbytes():bytebuffer;stdcall;
begin
ms1:=TMemoryStream.Create;
ms2:=TMemoryStream.Create;
ms3:=TMemoryStream.Create;
mydataset:=tclientdataset.Create(nil);
mydatasource:=tdatasource.Create(nil);
myprovider:=tdatasetprovider.Create(nil);
myquery:=tadoquery.Create(nil);
sqlcmd:=tstringlist.Create;sqlcmd.Add('select * from trequestprice');
myquery .ConnectionString :=
      'Provider=SQLNCLI.1;Password=qqqqqq;Persist Security Info=True;Us' +
      'er ID=sa;Initial Catalog=HSEms;Data Source=.'   ;
myquery .CursorType := ctStatic  ;
myquery.SQL.AddStrings(sqlcmd) ;
myquery.SQL.Strings [0];
myquery .Active := True;myprovider.DataSet :=myquery;
mydataset.SetProvider(myprovider)  ;
mydataset.Active := True ;
mydatasource.DataSet := mydataset;mydataset.SaveToStream(ms3);
ms3.Position :=0;FillChar(buf,1025,0);
ms3.Read (buf,sizeof(buf));
ms3.Position :=0;
result:=buf;
end;
Exports
getbytes;begin
end.代码功能:读取表trequestprice到内存buf中,作为dll的返回值吐出给C#。VS2010-C#下调用该dll的代码如下:
C# codeC# code
[DllImport("dataoutdll.dll", EntryPoint = "getbytes", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
 public static extern byte[] getbytes(); 
 private void button1_Click(object sender, EventArgs e)
        {            try
            {
                byte[] mybt = new byte[1024];
                mybt = getbytes();//程序执行到这里报错
            }
            catch (Exception ex)
            { MessageBox.Show(ex .Message ); }
      
        
        }
错误提示为:Cannot marshal 'return value': Invalid managed/unmanaged type combination.请问各位:delphi6编译下的dll中的array[0..1024] of byte 跟C#下的byte[1024],在内存上是等效的吗?
如何解决这个错误?
在线等。
谢谢

解决方案 »

  1.   

    array[0..1024] of byte 是1025个byte
    C#下的byte[1024]是0-1023,共1024个byte
      

  2.   

    问题依然存在,依然是同样的错误提示。
    dll内已改成1023.
      

  3.   

    以前从没注意过,原来我在csdn的等级还这么低,汗。
      

  4.   

    关注,我如果调C写的dll,喜欢function 返回String,处理简单些
      

  5.   

    定义换一下试试?
    function getbytes(var Abuf: bytebuffer): boolean;stdcall;
    把buf传进去
      

  6.   

    试过了。抛出异常:External component has thrown an exception.
      

  7.   


    我也有这个打算,实在不行,我想直接在dll内把数据转成string,再吐出来。
      

  8.   

    array[0..1024] of byte 是一个byte型的数组,数组下标从0到1024
    可以用回调函数及时更新bytebuffer的值
      

  9.   

    拿Delphi调用下试试 如果没问题的话 就看看C#和Delphi的参数差异了
      

  10.   

    如果 akirya 所说,建议将
    function getbytes():bytebuffer;stdcall;
    改为
    function getbytes(const buffer: PByte; len: Integer):Integer;stdcall;Delphi当中的array为特有类型,为非标准类型,内存管理是一个非常严重的问题。
      

  11.   

    对于函数,C#如何处理非标准类型的返回值我不清楚,但是Delphi是采样隐藏参数的方式传递传递非标准数据类型的返回值,也就是说,你所定义的 byteBuffer 数据类型,在
    function getbytes():bytebuffer;stdcall;是采样隐藏参数的方式传递的,
    也就是说,实际上是类似于下面的定义
    procedure getbytes(var buf: bytebuffer);stdcall;
    这种方式不一定会被C#所识别。建议你采样这样的定义方式来处理:
    function getbytes(var Abuf: PByte; Len: Integer): Integer;stdcall;
    其中ABuf就是你要返回的内容,而Len是数组长度(如果彼此约定了长度,这个参数可以省略)
    返回值赋值的方式为:
      Abuf := @buf[0];//把Buf的首地址给Abuf
      

  12.   

    有警察吗?我要进号子改造一下。
    哪位能帮我改造一下?
    delphi的源码和C#的源码都在0楼。
    目的很明确:就是delphi dll 读取数据库中表的数据,并吐给C#。C#这边能正确获取到内存数据就算改造完成。
      

  13.   

    akirya 兄台,这里你的级别最高了,这事非要你出手才能行啊。
    传参的写法,我按照32楼wxieyang 兄台给出的思路,已经编译好。但是在C#下写非托管代码的时候有困难,不能完成,所以 也就没有结果。
    还请akirya兄台不吝赐教。多谢。
      

  14.   

    delphi 修改为:function getbytes(var Abuf: PByte; Len: Integer):Integer ;stdcall  ;
    //function getbytes():bytebuffer;stdcall;
    begin
    ms1:=TMemoryStream.Create;
    ms2:=TMemoryStream.Create;
    ms3:=TMemoryStream.Create;
    mydataset:=tclientdataset.Create(nil);
    mydatasource:=tdatasource.Create(nil);
    myprovider:=tdatasetprovider.Create(nil);
    myquery:=tadoquery.Create(nil);
    sqlcmd:=tstringlist.Create;sqlcmd.Add('select * from trequestprice');
    myquery .ConnectionString :=
          'Provider=SQLNCLI.1;Password=qqqqqq;Persist Security Info=True;Us' +
          'er ID=sa;Initial Catalog=HSEms;Data Source=.'   ;
    myquery .CursorType := ctStatic  ;
    myquery.SQL.AddStrings(sqlcmd) ;
    myquery.SQL.Strings [0];
    myquery .Active := True;myprovider.DataSet :=myquery;
    mydataset.SetProvider(myprovider)  ;
    mydataset.Active := True ;
    mydatasource.DataSet := mydataset;mydataset.SaveToStream(ms3);
    ms3.Position :=0;FillChar(buf,1024,0);
    ms3.Read (buf,sizeof(buf));
    ms3.Position :=0;
    //result:=buf;Abuf := @buf[0];//把Buf的首地址给Abuf
    result:=1024;end;
    Exports
    getbytes;
    C#调用代码:        [DllImport("dataoutdll.dll", EntryPoint = "getbytes", CharSet = CharSet.Unicode , CallingConvention = CallingConvention.StdCall)]
            public static extern int getbytes(IntPtr pchar, int len );
       
     private void button1_Click(object sender, EventArgs e)
            {
                try
                {                             IntPtr p = new IntPtr();
                    int a= getbytes(p, 1024); //这里报错External component has thrown an exception.
              
                   
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
          
            
            }   
    依然是External component has thrown an exception.
      

  15.   

    function getbytes(Abuf: PByte):Integer;stdcall;
    begin
    用内存拷贝的方式对abuf赋值。
    end
    C#那边getbytes( Marshal.AllocHGlobal(1024) );
      

  16.   

    我原来写的代码算什么方式赋值?ms3.Read (buf,sizeof(buf));
    ms3.Position :=0;
    //result:=buf;Abuf := @buf[0];//把Buf的首地址给Abuf
    该怎么写才算“用内存拷贝的方式对abuf赋值。”?
    请教。
      

  17.   


    按照你给出的方式(除了abuf赋值)做了修改,如下:function getbytes(Abuf: PByte):Integer;stdcall;//function getbytes(var Abuf: PByte; Len: Integer):Integer ;stdcall  ;
    //function getbytes():bytebuffer;stdcall;
    begin
    ms1:=TMemoryStream.Create;
    ms2:=TMemoryStream.Create;
    ms3:=TMemoryStream.Create;
    mydataset:=tclientdataset.Create(nil);
    mydatasource:=tdatasource.Create(nil);
    myprovider:=tdatasetprovider.Create(nil);
    myquery:=tadoquery.Create(nil);
    sqlcmd:=tstringlist.Create;sqlcmd.Add('select * from trequestprice');
    myquery .ConnectionString :=
          'Provider=SQLNCLI.1;Password=qqqqqq;Persist Security Info=True;Us' +
          'er ID=sa;Initial Catalog=HSEms;Data Source=.'   ;
    myquery .CursorType := ctStatic  ;
    myquery.SQL.AddStrings(sqlcmd) ;
    myquery.SQL.Strings [0];
    myquery .Active := True;myprovider.DataSet :=myquery;
    mydataset.SetProvider(myprovider)  ;
    mydataset.Active := True ;
    mydatasource.DataSet := mydataset;mydataset.SaveToStream(ms3);
    ms3.Position :=0;FillChar(buf,1024,0);
    ms3.Read (buf,sizeof(buf));
    ms3.Position :=0;
    //result:=buf;Abuf := @buf[0];//把Buf的首地址给Abuf
    result:=1024;end;
    Exports
    getbytes;
    C#代码如下:        [DllImport("dataoutdll.dll", EntryPoint = "getbytes", CharSet = CharSet.Ansi  , CallingConvention = CallingConvention.StdCall)]
            public static extern int getbytes(IntPtr pchar );   private void button1_Click(object sender, EventArgs e)
            {
                try
                {                //IntPtr p = new IntPtr();
                    //int a= getbytes(p, 1024);                 getbytes(Marshal.AllocHGlobal(1024));//抛出异常External component has thrown an exception            }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
          
            
            }
    依然报出问题:External component has thrown an exception继续请教。
      

  18.   

    library Project1;{ Important note about DLL memory management: ShareMem must be the
      first unit in your library's USES clause AND your project's (select
      Project-View Source) USES clause if your DLL exports any procedures or
      functions that pass strings as parameters or function results. This
      applies to all strings passed to and from your DLL--even those that
      are nested in records and classes. ShareMem is the interface unit to
      the BORLNDMM.DLL shared memory manager, which must be deployed along
      with your DLL. To avoid using BORLNDMM.DLL, pass string information
      using PChar or ShortString parameters. }uses
      SysUtils,windows,
      Classes;{$R *.RES}procedure getbytes( buf : PChar )stdcall;
    begin
      strCopy( buf , '123456789'  );end;   Exports
    getbytes;begin
    end.
    C#
    代码
        class Program
        {
            [DllImport( "Project1.dll" , EntryPoint = "getbytes" , CallingConvention = CallingConvention.StdCall )]
            public static extern void getbytes( IntPtr p );
            [DllImport( "kernel32.dll" , EntryPoint = "RtlFillMemory" , CallingConvention = CallingConvention.StdCall )]
            public static extern void RtlFillMemory( IntPtr p , int len , int n );        static void Main( string[] args )
            {
                IntPtr p = Marshal.AllocHGlobal( 100 );
                RtlFillMemory( p , 100 , 0 );
                getbytes( p );
                string str = Marshal.PtrToStringAnsi( p );
                Marshal.FreeHGlobal( p );
            }
        }
      

  19.   

    library Project1;{ Important note about DLL memory management: ShareMem must be the
      first unit in your library's USES clause AND your project's (select
      Project-View Source) USES clause if your DLL exports any procedures or
      functions that pass strings as parameters or function results. This
      applies to all strings passed to and from your DLL--even those that
      are nested in records and classes. ShareMem is the interface unit to
      the BORLNDMM.DLL shared memory manager, which must be deployed along
      with your DLL. To avoid using BORLNDMM.DLL, pass string information
      using PChar or ShortString parameters. }uses
      SysUtils,windows,
      Classes;{$R *.RES}procedure getbytes( buf : PByte )stdcall;
    var
    str:string ;
    begin
      str := '123456789aaaaaaaaaab';
      CopyMemory( buf , PByte( str ), length(str) );end;   Exports
    getbytes;begin
    end.
      

  20.   

    多谢 akirya  兄台。
    我按照你给的试试。
      

  21.   

    报错依旧:procedure getbytes( buff : PByte )stdcall;
    //function getbytes(Abuf: PByte):Integer;stdcall;
    //function getbytes(var Abuf: PByte; Len: Integer):Integer ;stdcall  ;
    //function getbytes():bytebuffer;stdcall;
    begin
    ms1:=TMemoryStream.Create;
    ms2:=TMemoryStream.Create;
    ms3:=TMemoryStream.Create;
    mydataset:=tclientdataset.Create(nil);
    mydatasource:=tdatasource.Create(nil);
    myprovider:=tdatasetprovider.Create(nil);
    myquery:=tadoquery.Create(nil);
    sqlcmd:=tstringlist.Create;sqlcmd.Add('select * from trequestprice');
    myquery .ConnectionString :=
          'Provider=SQLNCLI.1;Password=qqqqqq;Persist Security Info=True;Us' +
          'er ID=sa;Initial Catalog=HSEms;Data Source=.'   ;
    myquery .CursorType := ctStatic  ;
    myquery.SQL.AddStrings(sqlcmd) ;
    myquery.SQL.Strings [0];
    myquery .Active := True;myprovider.DataSet :=myquery;
    mydataset.SetProvider(myprovider)  ;
    mydataset.Active := True ;
    mydatasource.DataSet := mydataset;mydataset.SaveToStream(ms3);
    ms3.Position :=0;FillChar(buf,1024,0);
    ms3.Read (buf,sizeof(buf));
    ms3.Position :=0;
    //result:=buf;//CopyMemory(buff,PByte(buf),length(buf));
    Copymemory(buff,@buf[0] ,sizeof(buf));//COPY内存,从buf到buff
    //Abuf := @buf[0];//把Buf的首地址给Abuf
    //result:=1024;end;
    Exports
    getbytes;
    C#这边的代码:        [DllImport("dataoutdll.dll", EntryPoint = "getbytes", CallingConvention = CallingConvention.StdCall)]
            public static extern void getbytes(IntPtr p);
            [DllImport("kernel32.dll", EntryPoint = "RtlFillMemory", CallingConvention = CallingConvention.StdCall)]
            public static extern void RtlFillMemory(IntPtr p, int len, int n);      private void button1_Click(object sender, EventArgs e)
            {
                try
                {
                    ////IntPtr p = new IntPtr();
                    ////int a= getbytes(p, 1024); 
                    //getbytes(Marshal.AllocHGlobal(1024));                IntPtr p = Marshal.AllocHGlobal(1024);
                    RtlFillMemory(p, 1024, 0);
                    getbytes(p);//这里抛出异常
                 //   string str = Marshal.PtrToStringAnsi(p);
                   
                    Marshal.FreeHGlobal(p);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
          
            
            }
    抛出异常:External component has thrown an exception.偶滴神啊!
      

  22.   

    老大 你先那Delphi调一下你的Dll,先保证Dll没问题 在看C#与Delphi的差异。如果Delphi调用都出错的话 你还搞啥啊
      

  23.   

    delphi 代码的dll 是经过测试的,一直到
     
    .
    .
    .
    FillChar(buf,1024,0);
    ms3.Read (buf,sizeof(buf));
    ms3.Position :=0;
    result:=buf;
    数据都是正常的,在exe下,buf中的数据可以被重新回复到新的gridview中显示,没问题。
      

  24.   


    akirya 兄台 给的代码示例,我全盘验证了,可以通过,运行正常。但是在我这里,需要把buf中的数据重新copy到procedure getbytes( buff : PByte )stdcall;里的buff中,我用的方法就是akirya 兄给的Copymemory(buff,@buf[0] ,sizeof(buf));//COPY内存,从buf到buff,
    其他的都没有改动。在C#调用的时候,也照顾到了长度问题(1024),见47楼 两边的代码。这样写有什么不妥?我看不出问题出在哪里。
    反正是报错了。
    郁闷中。。
      

  25.   

    你先用delphi调用看看能不能获取正常数据,应该是你copymemory用的问题。
    我始终没看到你的buf是怎么定义的。
      

  26.   

    呵呵,不会是差在声明上吧。
    把Delphi中的函数
    procedure getbytes( buff : PByte )stdcall;
    换成
    procedure getbytes( buff : PChar )stdcall;赋值部分move(buf[0], buff^ ,sizeof(buf));
    像这样的情况,一般情况下,是在调用的地方申请内存,然后把申请到的内存的首地址传到dll中,在dll中向这个内存中写数据。
    我不知道在C#中,能否得到动态申请的内存的首地址,如果能得到,那么,你只要把这个首地址当成一个整数传过去就是了,在DLL中,将这个整数强制转换成PByte,呵呵。如果实在不行,那就换个思路好了,通过内存映射文件可以传递数据,呵呵,就是麻烦点。大致思路就是在C#中创建一个命名的内存映射文件,并给出足够的内存空间。
    调用DLL的导出函数,
    在DLL中,通过内存映射文件的名称,打开这个内存映射文件,然后向其中写数据,写完了就关闭它。
    函数返回后,在C#中读出内存映射中的内容,呵呵。在C#中,如何操作内存映射文件我不知道,但是在delphi中,方法如下procedure MoveDataToFM(const AFMName: AnsiString);
    var
      hFileMap: THAndle;
      pView: Pointer;
    begin
      //创建一个内存映射文件,如果已经存在这个名称的内存映射文件,则打开
      hFileMap := CreateFileMappingA(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, 1024, PAnsiChar(AFMName));
      if hFileMap > 0 then
      try
        //映射这个内存映射文件
        pView := MapViewOfFile(hFileMap, FILE_MAP_READ or FILE_MAP_WRITE, 0, 0, 0);
        if pView <> nil then
        try
          ZeroMemory(pView, 1024);      Move(Buf[0], pView^, SizeOf(Buf)); //将 Buf 中的内容写入到内存映射文件中
        finally
          UnmapViewOfFile(pView);
        end;
      finally
        CloseHandle(hFileMap);
      end;
    end;
      

  27.   

    理论上,因为你的DLL是在同一个进程空间,因此,你在C#中通过
     CreateFileMappingA 创建的内存映射文件句柄可以直接传给DLL使用,这样在DLL中就省去了
    CreateFileMappingA 和 CloseHandle
    甚至可以在C#中把调用MapViewOfFile产生的内存地址直接传给DLL中的函数中,
    这样在DLL中连调用MapViewOfFile和UnmapViewOfFile都省了,直接通过Move向内存中写数据就是了
    但是,因为没有用过C#,不知道C#中通过CreateFileMappingA产生的句柄以及通过MapViewOfFile映射的内存指针同Delphi中的是否是一致的,如果一致的,那么直接用没问题,也就不用创建一个命名的内存映射文件了,直接创建一个匿名的内存映射文件就是了;但是如果不一致,那么就不能直接用。不过保险起见,还是创建一个命名的内存映射文件,并分别在C#中和DLL中打开的好。
    如果你有兴起,可以试试看,呵呵
      

  28.   


    我的buf的定义,在0楼有,一开始多出一个byte,后修正为 array[0...1023] of byte.一直沿用。
      

  29.   

    多谢各位,搞定了,善始善终,可以结贴散分了。分不多,请大家抬举。
    最后的代码如下:
    delphi dll 这边:library dataoutdll;
    uses
       Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, DB, ADODB, Grids, DBGrids, DBClient, StdCtrls, Provider;type
       byteBuffer = array[0..1023] of byte;
    var
    mydataset:tclientdataset;
    mydatasource:tdatasource;
    myprovider:  tdatasetprovider;
    myquery:tadoquery;
    sqlcmd:TStringList;ms3:TMemoryStream;
    buf: bytebuffer;
    {$R *.res}procedure getbytes( buff : PByte )stdcall;beginms3:=TMemoryStream.Create;               
    mydataset:=tclientdataset.Create(nil);
    mydatasource:=tdatasource.Create(nil);
    myprovider:=tdatasetprovider.Create(nil);
    myquery:=tadoquery.Create(nil);
    sqlcmd:=tstringlist.Create;sqlcmd.Add('select * from trequestprice');
    myquery .ConnectionString :=
          'Provider=SQLNCLI.1;Password=qqqqqq;Persist Security Info=True;Us' +
          'er ID=sa;Initial Catalog=HSEms;Data Source=.'   ;
    myquery .CursorType := ctStatic  ;
    myquery.SQL.AddStrings(sqlcmd) ;
    myquery.SQL.Strings [0];
    myquery .Active := True;myprovider.DataSet :=myquery;
    mydataset.SetProvider(myprovider)  ;
    mydataset.Active := True ;
    mydatasource.DataSet := mydataset;mydataset.SaveToStream(ms3);
    ms3.Position :=0;FillChar(buf,1024,0);
    ms3.Read (buf,sizeof(buf));
    ms3.Position :=0;Copymemory(buff,@buf[0] ,sizeof(buf));
    end;
    Exports
    getbytes;
    begin
    end.
    C#(VS2010)这边:        [DllImport("dataoutdll.dll", EntryPoint = "getbytes", CallingConvention = CallingConvention.StdCall)]
            public static extern void getbytes(IntPtr p);
            [DllImport("kernel32.dll", EntryPoint = "RtlFillMemory", CallingConvention = CallingConvention.StdCall)]
            public static extern void RtlFillMemory(IntPtr p, int len, int n);        private void button1_Click(object sender, EventArgs e)
            {
                try
                {
                         IntPtr p = Marshal.AllocHGlobal(1024);
                    RtlFillMemory(p, 1024, 0);
                    getbytes(p);
                
                    Marshal.Copy(p, mybt, 0, 1024);
                   
                    Marshal.FreeHGlobal(p);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
          
            
            }
    多谢akirya兄台的代码提示,我只略作了改动(有需要的朋友查看akirya的回帖和本回复的代码)。
    谢谢所有参与讨论的朋友,谢谢顶友。