C# 如何在C中使用Delphi Dll(带PChar类型)#

C# 如何在C中使用Delphi Dll(带PChar类型)#,c#,.net,delphi,pinvoke,C#,.net,Delphi,Pinvoke,以下是Delphi DLL代码: library Project2; uses SysUtils, Classes; {$R *.res} function SimpleConv(const s: string): string; var i: Integer; begin Result := ''; for i := 1 to Length(s) do if Ord(S[i]) < 91 then Result := Result + S[i

以下是Delphi DLL代码:

library Project2;

uses
  SysUtils,
  Classes;

{$R *.res}

function SimpleConv(const s: string): string;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to Length(s) do
    if Ord(S[i]) < 91 then
      Result := Result + S[i];
end;

function MsgEncode(pIn: pchar; InLen: Integer; var pOut: pchar; var OutLen: Integer): Boolean; stdcall;
var
  sIn: string;
  sOut: string;
begin
  SetLength(sIn, InLen);
  Move(pIn^, sIn[1], InLen);

  sOut := SimpleConv(sIn);   // Do something

  OutLen := Length(sOut);
  GetMem(pOut, OutLen);
  Move(sOut[1], pOut^, OutLen);
  Result := OutLen > 0;
end;

procedure BlockFree(Buf: pchar); stdcall;
begin
  if assigned(Buf) then
     FreeMem(Buf);
end;

exports
  MsgEncode,
  BlockFree;

begin
end.
图书馆项目2;
使用
SysUtils,
班级;
{$R*.res}
函数SimpleConv(consts:string):string;
变量
i:整数;
开始
结果:='';
对于i:=1到长度do
如果作战需求文件小于91,则
结果:=结果+S[i];
结束;
函数MsgEncode(pIn:pchar;InLen:Integer;var-pOut:pchar;var-OutLen:Integer):布尔;stdcall;
变量
罪:弦;
苏特:弦;
开始
设定长度(sIn,InLen);
移动(pIn^,sIn[1],InLen);
sOut:=SimpleConv(sIn);//做点什么
OutLen:=长度(sOut);
GetMem(pOut,OutLen);
移动(sOut[1],pOut^,OutLen);
结果:=OutLen>0;
结束;
无程序块(Buf:pchar);stdcall;
开始
如果已分配(Buf),则
FreeMem(Buf);
结束;
出口
MsgEncode,
无区块;
开始
结束。
Dll函数MsgEncode会将CMEM分配给pOut param,BlockFree用于释放MsgEncode分配的内存


我的问题是:如何在C#中使用此dll?我是C#的新手。编辑

对不起,我没有看到您也导出无块功能

经验法则是:总是在同一个模块中分配和释放内存;如果在Dll中分配内存,则也应在同一Dll中释放内存

因此,如果您使用BlockFree释放内存,则可以在同一模块中分配和释放内存

请注意,Delphi字符串和PChar类型取决于版本-它们是Delphi 2009之前的ANSI和Delphi 2009之后的UNICODE。

请注意,C#字符串数据是UNICODE,因此如果使用PChar继续此Delphi代码,将在PInvoke调用中执行从PChar到PWideChar的隐藏转换。(转换意味着分配另一个内存缓冲区并将所有数据复制到新的缓冲区)如果您打算将此Delphi代码与C#一起使用,并且您关心性能,则应将Delphi代码改为对PWideChar数据进行操作

使用PWideChar而不是PChar还有另一个原因:根据COM要求,Delphi使用Win32 SysAllocString分配器分配OleString类型。这意味着字符串的接收者可以使用Win32 API取消分配字符串

如果您实际上没有在函数中处理文本,而是使用PChar作为任意字节值数组的代理,那么您可以在调用的非托管端(而不是托管端)进行处理。如果是字节数据,则应将其声明为字节数组,以避免字符集或字符大小转换


在房子的C#端,您需要使用PInvoke调用非托管Delphi DLL函数。有关如何在C#中注释调用以使PInvoke自动处理缓冲区分配的详细信息,请参阅。查找一个Win32 API函数,该函数传递与您的函数类似的PChar(或PWideChar)参数,然后在PInvoke.net中搜索要在托管代码中使用的PInvoke声明。

我将从字面上回答您的问题,并附带一些条件:

  • 对于使用
    PChar
    的互操作代码,必须知道是否使用Unicode Delphi,因为
    PChar
    AnsiChar
    WideChar
    之间浮动取决于Delphi的版本。我假设您使用Unicode Delphi。如果没有,那么您需要更改P/Invoke端的字符串编组
  • 我已经修改了你的DLL代码。我已经删除了长度参数,并且假设您只允许受信任的代码调用此DLL。不受信任的代码可能会产生缓冲区溢出,但您不会让不受信任的代码在您的计算机上运行,是吗
  • 我还更改了
    BlockFree
    ,以便它可以接收非类型指针。不需要将其类型设置为
    PChar
    ,它只是调用
    Free
以下是修改后的Delphi代码:

library Project2;

uses
  SysUtils;

{$R *.res}

function SimpleConv(const s: string): string;
begin
  Result := LowerCase(s);
end;

function MsgEncode(pIn: PWideChar; out pOut: PWideChar): LongBool; stdcall;
var
  sOut: string;
  BuffSize: Integer;
begin
  sOut := SimpleConv(pIn);
  BuffSize := SizeOf(Char)*(Length(sOut)+1);//+1 for null-terminator
  GetMem(pOut, BuffSize);
  FillChar(pOut^, BuffSize, 0);
  Result := Length(sOut)>0;
  if Result then
    Move(PChar(sOut)^, pOut^, BuffSize);
end;

procedure BlockFree(p: Pointer); stdcall;
begin
  FreeMem(p);//safe to call when p=nil
end;

exports
  MsgEncode,
  BlockFree;

begin
end.
这是另一边的C代码:

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    class Program
    {
        [DllImport("project2.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool MsgEncode(string pIn, out IntPtr pOut);

        [DllImport("project2.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
        public static extern void BlockFree(IntPtr p);

        static void Main(string[] args)
        {
            IntPtr pOut;
            string msg;
            if (MsgEncode("Hello from C#", out pOut))
                msg = Marshal.PtrToStringAuto(pOut);
                BlockFree(pOut);
        }
    }
}

这应该让你开始。由于您是C#新手,因此需要对P/Invoke进行大量的阅读。享受吧

因为我需要在MsgEncode中实现复杂的计算,上面的代码只是一个简单的示例。如果在主机应用程序中分配内存,分配多少?主机应用程序不知道。是的,主机应用程序可以调用Msgencode两次,第一次用于计算内存大小,第二次用于实际分配内存。但是,你知道MsgEncode非常复杂,需要花费很多时间,从性能上说,我不希望再调用它两次。我希望在dll中分配和释放内存。@David:那么仍然使用通过SysAllocString分配的PWideChar是很重要的。@Jeroen你读过我的避免SysAllocString的P/Invoke答案了吗?@David:wide Delphi:在我的时间之后。@David:你对SysAllocString有什么意见?代码会简单得多,而且您不必导出deallocator。@dthorpe我不反对SysAllocString。我自己也会这么做的。我只是想回答这个问题,但不想把它带离海报的舒适区太远。你能安全地在德尔福一侧分配,在另一侧释放吗?它们不是不同的堆吗?@Warren你不能这样做,而这段代码不能这样做!它在Delphi中使用相同的堆执行GetMem和FreeMem。如果我使用Delphi 6执行相同的功能,该怎么办。尝试了相同的步骤,但“msg”的值将为“桔獩椠⁳整瑳". (am使用Delphi 6和Visual studio 2010、.net framework 4.0)@贝加伦你知道
PAnsiChar
PWideChar
之间的区别吗?
AnsiString
UnicodeString
之间的区别。
PtrToStringAnsi
PtrToStringUni
之间的区别吗?@Heffernan:不,我不熟悉PAnsiChar,PWideChar,AnsiString,UnicodeString..等等,我在学习它。请解释一下ng我必须使用delphi 6执行相同的操作。delphi的哪个版本?(非常重要)