tags:

views:

1386

answers:

3

Hi. I need to use a DLL (Hardware ID Extractor) made in Delphi 7 in my C# application.

The functions exported by this DLL are:

Exported functions:

// CPU
function GetCPUSpeed: Double;
function CPUFamily: ShortString; { Get cpu identifier from the windows registry }
function GetCpuTheoreticSpeed: Integer; { Get cpu speed (in MHz) }
function IsCPUIDAvailable: Boolean; Register;
function GetCPUID (CpuCore: byte): ShortString;
Function GetCPUVendor: ShortString;

// RAM
function MemoryStatus (MemType: Integer): cardinal; { in Bytes }
function MemoryStatus_MB (MemType: Integer): ShortString; { in MB }

// HDD
function GetPartitionID (Partition : PChar): ShortString; { Get the ID of the specified patition. Example of parameter: 'C:' }
function GetIDESerialNumber(DriveNumber: Byte ): PChar; { DriveNr is from 0 to 4 }


I know (obviously) that string in Delphi are not null terminated and are byte (ASCII). But I have no clue on how to map these Delphi string to C#.

Thanks.

+3  A: 

Here's an example of how you could declare the GetCPUSpeed function in C#:

class Program
{
    [DllImport("the_name_of_the_delphi_library.dll")]
    public static extern double GetCPUSpeed();

    static void Main(string[] args)
    {
        double cpuSpeed = GetCPUSpeed();
        Console.WriteLine("CPU speed: {0}", cpuSpeed);
    }
}

And there are the other declarations you could try:

[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string CPUFamily();

[DllImport("the_name_of_the_delphi_library.dll")]
public static extern int GetCpuTheoreticSpeed();

[DllImport("the_name_of_the_delphi_library.dll")]
public static extern bool IsCPUIDAvailable();

[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string GetCPUID(byte cpuCore);

[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string GetCPUVendor();

[DllImport("the_name_of_the_delphi_library.dll")]
public static extern uint MemoryStatus(int memType);

[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string MemoryStatus_MB(int memType);

[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string GetPartitionID(char partition);

[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string GetIDESerialNumber(byte driveNumber);
Darin Dimitrov
+5  A: 

The problem comes from the way you designed your exported functions. DLLs are (among other things) a mechanism to provide code in a way that it can be used from applications (or other DLLs) written in different programming languages.

Have a look at the Windows API, there are lots of functions returning text. Unfortunately this is done in a lot of different ways, the Windows API has no real standard for it. However, I'm quite sure that you will not find a single function that returns a "string" (either a Pascal or a C (null-terminated) string) in the way you did. The reason is simple: This would make it very difficult or even impossible to use different programming languages for different modules. Your DLL would allocate the memory for the string, and once the caller is done with the string it would need to free the memory. Thus both modules need to share the manager for this slice of memory. How could the C# program use your DLL, seeing that they use completely different memory managers?

The customary way to get a string value from a DLL is to call the function with a preallocated buffer and the buffer length in parameters, let the function fill that buffer with the string contents, and let the function result denote success or failure. Passing a buffer of insufficient size for example would be one cause of failure. Different Windows API functions deal with this in different ways, the easiest one for you would probably to define your maximum string length with a constant, similar to MAX_PATH for example.

To solve your problem you should take one Windows API function that you know how to call from C#, and which returns a string result in a buffer, as the example. Model your exported functions after this example, and it should be easy to call them from the C# program.

Edit:

I remembered that I'd seen a similar question (How to import a function from a DLL made in Delphi?) some time ago, and now that I looked I found it to be yours as well.

You wrote then that you don't have the source code for that DLL, so changing the exported functions is out of the question. What you could do is create a wrapper DLL (or COM object) in Delphi, call the original HardwareIDExtractor.dll and provide a sane API on top of it. That would allow you to provide both AnsiChar and WideChar versions of the exported functions at the same time.

If you want to reduce the clutter (two DLLs necessary instead of one) you could also put the original HardwareIDExtractor.dll as a resource into your wrapper DLL, as described in this blog posting: Using DLLs stored as Resources in Delphi programs

mghie
"You wrote then that you don't have the source code for that DLL" I cannot find where on the path the OP said that......Btw, that thing of including DLLs stored as resources is really cool. ;-)
Fabricio Araujo
In linked previous question: "I don't have the source code for that DLL so I must adapt my C program to that DLL and not the other way around."
mghie
+2  A: 

If you have the source of the D7 dll, you can change the exported function to make the ShortString function to return PChar. You can create new functions that call the original ones and do the typecast - that's the easiest path. If you follow this path do the same to the Integer and Cardinal types (cast them to LongInt and LongWord, respectively). Some code below (I imagine that mages like mr Hausladen have a more elegant approach, but this works :-) )

var
  SInput: ShortString;
  POutput: PAnsiChar;
  Tam: Integer;
  pt: Pointer;
begin
  SInput := 'Alphabet'; //ShortString
  pt := @SInput[1]; // Points to the first char of the string;
  Tam := (Length(SInput))+1; // Size the string
  POutput := StrAlloc(tam); // Allocate;
  CopyMemory(POutput,pt,tam); // Do the copy
  POutput[tam-1] := #0; // Put the null to finish
  MessageBox(0, POutput, 'Algo', MB_ICONWARNING or MB_OK);
  StrDispose(POutput);

end;

This works because internally ShortString is a array [0..255] of Char. I don't have an D2009 at hand to see if the SizeOf(char) must be changed to SizeOf(AnsiChar). BUT the principle is the same: allocate the PAnsiChar, get the Pointer to the first the char of the ShortString and do Copy (in this case I've done with CopyMemory) to the PAnsiChar. And put the null in its' place.

Or you can go the mghie's way and create a wrapper and do the casts there.

Fabricio Araujo
"return PChar" - But what memory should the PChar point to?
mghie
Some code above
Fabricio Araujo