Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

What data types to use for parameters in a Delphi DLL?

Status
Not open for further replies.

djjd47130

Programmer
Nov 1, 2010
480
US
I am building an API DLL in Delphi for a system I'm working on. This includes many functions, such as...

Code:
function GetSomeData(Output: PChar; var Size: DWORD): Bool; StdCall;
var
  Str: String;
begin
  Result:= False;
  try
    Str:= 'Some string obtained by the function';
    if assigned(Output) then begin
      if Output <> nil then begin
        Size:= Length(Str);
        StrPCopy(Output, Str);
      end else begin
        Size:= 0;
      end;
    end;
    Result:= True;
  except
    on e: exception do begin
      Result:= False;
    end;
  end;
end;

'Output' is a parameter to get back the result string from the function. 'Size' is the given and returned size of 'Output'. The result Bool identifies if the function completed successfully.

In Delphi implementation, I have it down...

Code:
function GetSomeData(Output: PChar; var Size: DWORD): Bool; StdCall; External 'MyDLL.dll';

function GetData: String;
var
  Buf: String;
  BufLen: DWORD;
begin
  Result:= '';
  Buf:= '';
  BufLen:= 255;
  SetLength(Buf, BufLen);
  if GetSomeData(PChar(Buf), BufLen) then begin
    SetLength(Buf, BufLen);
    Result:= Buf;
  end;
end;

However, in C# I am having some trouble figuring out what types to use.

This is how I thought to do it, but I'm far from familiar with C#...

Code:
  [DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
  public static extern bool GetSomeData(string Output, int Size);

It doesn't seem to like my 'bool' type, so what should I use instead?

And finally how to handle the data like I have to in Delphi?

I know these type relations:
Delphi String - Do not use in DLL's
Delphi PChar - Equivalent to C# string
Delphi DWORD - Equivalent to C# int <-- ?
Delphi Boolean - Do not use in DLL's
Delphi Bool - Equivalent to C# bool

Also, how do I account for Const and Var parameters? Such as...

Code:
function GetSomeData(const Input: PChar; Output: PChar; var Size: DWORD): Bool; StdCall;


JD Solutions
 
are you providing the value of output, or does the function GetSomeData set the value of output?

if it's the first, then the signature looks correct. if it's the latter, then you need to use the out or ref keyword.
Code:
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
  public static extern bool GetSomeData(out string Output, int Size);
Code:
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
  public static extern bool GetSomeData(ref string Output, int Size);
you would then call the function like this
Code:
string output;
if(GetSomeData(out output, 10))
{
   success!
}
Code:
string output = null;
if(GetSomeData(ref output, 10))
{
   success!
}
one last comment. it a good practice to encapsulate external calls. I would make GetSomeData private and create a public accessor.
Code:
class Foo
{
   [DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
   private static extern bool GetSomeData(out string Output, int Size);

   public string GetSomeData(int size)
   {
      string output;
      if(GetSomeData(out output, int size) == false)
      {
          throw new InvalidOperationException("getting data failed!");
      }
      return output;
   }
}
you can then use the class like this
Code:
var thedata = new Foo().GetSomeData(10);
this also opens up more possibilities for inheritance and composition all those good SOLID design principles.

Jason Meckley
Programmer

faq855-7190
faq732-7259
 
Thanks, that helped understand a little, but I still can't get it working. It won't let me call the function if I declare 'out'. It's throwing an exception "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

(PS - Sorry, I'm still new to C#, I'm used to Delphi all the time)

I am calling the function and expecting to get a result back in the string parameters declared.

Let me post the actual real function I'm trying to call...

For example,
cServer: PChar = Server name returned back from function
sServer: DWORD = Size of cServer
Result: Bool = Whether function passed successfully or not

- Prepare 'cServer' to be passed into function by setting 'sServer' to 255 and making the size of 'cServer' to the value of 'sServer'
- Call function passing 'cServer' and 'sServer'
- If function's result is true, then obtain value by reading 'cServer' with the size of 'sServer' (both changed and returned by function)

I think my problem is that i don't know how to set the size of each parameter to 255 before it's passed into the function...


API Function KE_GetConfig
Retrieves database connection information and other locally stored configuration properties for the system.

Code:
function KE_GetConfig(cServer, cDatabase, cUser, cPass, cProvider, cConnStr: PChar;
  var sServer, sDatabase, sUser, sPass, sProvider, sConnStr: DWORD): Bool; StdCall;
var
  S, D, U, P, R, CS: String;
  Reg: TRegistry;
  C: TKEConfig;
begin
  //Set Defaults
  S:= ''; D:= ''; U:= ''; P:= ''; R:= ''; Result:= False;
  //cServer:= ''; cDatabase:= ''; cUser:= ''; cPass:= ''; cProvider:= '';
  Reg:= TRegistry.Create(KEY_READ);
  try
    Reg.RootKey:= HKEY_LOCAL_MACHINE;
    if Reg.KeyExists(KE_REG_KEY) then begin
      try
        if Reg.OpenKey(KE_REG_KEY, False) then begin
          S:= Reg.ReadString('Server');
          D:= Reg.ReadString('Database');
          U:= Reg.ReadString('Login');
          P:= Reg.ReadString('Pass');
          R:= Reg.ReadString('Provider'); 
          C.cServer:= S;
          C.cDatabase:= D;
          C.cUser:= U;
          C.cPass:= P;
          C.cProvider:= R;
          CS:= BuildConnectionString(C);
          Result:= True;
          Reg.CloseKey;
        end;
      except
        on e: exception do begin
          Result:= False;
        end;
      end;
    end;
  finally
    SetBuf(S, cServer,    sServer);
    SetBuf(D, cDatabase,  sDatabase);
    SetBuf(U, cUser,      sUser);
    SetBuf(P, cPass,      sPass);
    SetBuf(R, cProvider,  sProvider);
    SetBuf(CS, cConnStr,  sConnStr);
    if assigned(Reg) then Reg.Free;
  end;
end;


Delphi Implementation
Declaration and Implementation of DLL function call
Code:
function KE_GetConfig(cServer, cDatabase, cUser, cPass, cProvider, cConnStr: PChar;
  var sServer, sDatabase, sUser, sPass, sProvider, sConnStr: DWORD): Bool;
    StdCall; External KEAPI;

type
  TKEConfig = record
    cServer: String;
    cDatabase: String;
    cUser: String;
    cPass: String;
    cProvider: String;
    cConnString: String;
  end;


procedure PrepareBuf(var aBuf: String; var aBufLen: DWORD);
begin
  aBuf:= '';        
  aBufLen:= 255;
  SetLength(aBuf, aBufLen);
end;

function GetBuf(var aBuf: String; var aBufLen: DWORD): String;
begin
  SetLength(aBuf, aBufLen);
  Result:= aBuf;
end;

function GetConfig(var aConfig: TKEConfig): Bool;
var
  S, D, U, P, R, C: String;
  sS, sD, sU, sP, sR, sC: DWORD;
begin
  Result:= False;
  PrepareBuf(S, sS);
  PrepareBuf(D, sD);
  PrepareBuf(U, sU);
  PrepareBuf(P, sP);
  PrepareBuf(R, sR);
  PrepareBuf(C, sC);
  try
    if KE_GetConfig(PChar(S), PChar(D), PChar(U), PChar(P), PChar(R), PChar(C),
      sS, sD, sU, sP, sR, sC) then
    begin
      aConfig.cServer:=     GetBuf(S, sS);
      aConfig.cDatabase:=   GetBuf(D, sD);
      aConfig.cUser:=       GetBuf(U, sU);
      aConfig.cPass:=       GetBuf(P, sP);
      aConfig.cProvider:=   GetBuf(R, sR);
      aConfig.cConnString:= GetBuf(C, sC);
      Result:= True;
    end;
  finally

  end;
end;


C# Declaration and Implementation
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.InteropServices;

namespace DJJD.Classes
{
    public class TKEConfig
    {
        public string cServer = "(local)";
        public string cDatabase = "master";
        public string cUser = "sa";
        public string cPass = "";
        public string cProvider = "SQLOLEDB.1";
        public string cConnStr = "";
        public int cServerID = 0;
        public bool cActive = false;
    }

    public class KEAPI
    {
        [DllImport("KEAPI.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
        private static extern bool KE_GetConfig(
                string cServer,         string cDatabase,       string cUser,       string cPass,       string cProvider,       string cConnStr,
                int sServer,            int sDatabase,          int sUser,          int sPass,          int sProvider,          int sConnStr);

        public bool GetConfig(TKEConfig Config)
        {
            bool Result = false;
            if (Config != null)
            {
                string S = "";
                string D = "";
                string U = "";
                string P = "";
                string R = "";
                string C = "";
                int s = 255;
                int d = 255;
                int u = 255;
                int p = 255;
                int r = 255;
                int c = 255;
                //How to assign size of values?
                i.e. Assigning size of 'S' to value of 's')
                if (KE_GetConfig(S, D, U, P, R, C, s, d, u, p, r, c))
                {
                    Config.cServer = S.Substring(0, s);
                    Config.cDatabase = D.Substring(0, d);
                    Config.cUser = U.Substring(0, u);
                    Config.cPass = P.Substring(0, p);
                    Config.cProvider = R.Substring(0, r);
                    Config.cConnStr = C.Substring(0, c);
                    Result = true;
                }
            }
            return Result;
        }
    }
}


JD Solutions
 
not sure what to make of that. this is one area of c# i don't have experience. you could try refacoring the Delphi calls to simply return the value you need and throw an exception if there is a failure.

once this is working you could refactor to a TryGet... approach.

Jason Meckley
Programmer

faq855-7190
faq732-7259
 
Try the following:

Code:
[DllImport("MyDLL.dll", EntryPoint="GetSomeData", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetSomeData(IntPtr Output, [MarshalAs(UnmanagedType.U4)] ref int Size);

I can't be 100% sure it will work (due to my knowledge of C# linking with Delphi), but it should be a close starting point.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top