IIS Handler, Delphi to C# conversion - c#

I am currently converting a Delphi application to C# for an ISS Handler.Delphi uses these memory classes (TMemoryStream, TStreamAdapter) to pass to methods, insert string values, and return and retrieve values from memory.
My question here is, do i really need to create memory objects to insert string values and pass them through methods for retrieval, or is this just unique to the Delphi handler. Currently i am just passing and retrieving string values in my ISS handler, will this be a sort of correct conversion.
Ive researched and came up short. Your help is kindly appreciated.
for eg Delphi code:
m := TMemoryStream.Create;
iss := TStreamAdapter.Create(m, soOwned);
iss._AddRef;
try
hr := CAGetPath(cas, cam, cal, iss);
cb := 0;
m.Write(cb, sizeof(WideChar));
s := PWideChar(m.Memory);
finally
iss._Release;
end;
function CAGetPath(SubscriberID, MailshotID, LinkID: integer;
stmPath: ISequentialStream): HRESULT; stdcall;
{
sPath: WideString;
sPath := "\\MYPATH\\TO\\FOLDER"
stmPath._AddRef;
cb := length(sPath) * sizeof(WideChar);
ES := 'stmPath.Write(' + sPath + ')';
Result := stmPath.Write(PWideChar(sPath), cb, nil);
}
This bit of delphi code just returns a string and sets it to variable s. As fas as i can tell. Is this neccessary and why would anyone do it this way?

Do I really need to create memory objects to insert string values and pass them through methods for retrieval, or is this just unique to the Delphi handler?
The author of the Delphi code is best placed to explain why it was done in that particular way. However, it looks to me as though CAGetPath is an external function since it was declared using stdcall. And so the author will have needed to come up with a solid means to marshal text data across a module boundary. The author chose to use the COM ISequentialStream interface which is a perfectly reasonable choice.
In order to interact with that from Delphi, the author needed to use an object that implements ISequentialStream. The simplest way is to use the TStreamAdapter class which wraps a TStream and presents an IStream interface. In order to use that, a concrete stream must be provided. Hence the use of TMemoryStream. Clearly CAGetPath must return the URL somewhere and why not a memory stream?
Anyway, that's my best guess as to why the Delphi code is that way. There's no evidence that memory streams are needed to implement IIS handlers (whatever they are).
I think you are getting all hung up on replicating the Delphi implementation. In your shoes I would simply try to understand what the underlying requirements are. What is your IIS handler required to do? Then implement that using the idiomatic C# techniques and classes. Use the extant Delphi code as a guide of what the requirement is, but not as a guide for how to implement that requirement.

Related

How to pass a stream from Delphi to C#? [duplicate]

This question already has answers here:
How to pass a Delphi Stream to a c/c++ DLL
(2 answers)
Closed 1 year ago.
Streams are very mysterious for me, so please be patient and friendly :) I am trying to create a Stream in Delphi, which I can pass to C# as a parameter. Does anybody know how to do that?
Note that I am able to call the C# function from my Delphi project like down below. I've followed this tutorial to create a DLL from my C# project and to implement the DLL in Delphi.
procedure TForm1.Button1Click(Sender: TObject);
var
Stream : TStream;
Baz : TBaz;
begin
Baz := TBaz.Create(Self);
// Create Stream & call Bar() with the created Stream as parameter
Baz.Bar(?param?);
end;
My C# project looks like this:
public class Baz
{
public void Bar(?param?)
{
// Get stream here
}
}
I highly appreciate any help, sheers!
Edit:
The stream should be a file stream
Both Delphi and C# have support for IStream interface for interoperability purposes. You can wrap any stream with TStreamAdapter. If you are constructing the stream on Delphi side you need to make sure that wrapped stream instance will live longer than IStream wrapper or you need to pass ownership of the stream to the adapter.
uses
Winapi.ActiveX,
System.Classes;
var
FileStream: TFileStream;
Stream: IStream;
begin
FileStream := TFileStream.Create(...);
try
Stream := TStreamAdapter.Create(FileStream);
// use stream
Baz.Bar(Stream);
finally
Stream := nil;
FileStream.Free;
end;
end;
or
var
FileStream: TFileStream;
Stream: IStream;
begin
FileStream := TFileStream.Create(...);
Stream := TStreamAdapter.Create(FileStream, soOwned);
// use stream
Baz.Bar(Stream);
end;
Documentation:
System.Classes.TStreamAdapter
IStream interface
Does a wrapper class for a COM interop IStream already exist?
Of course, you can always pass filename to C# and construct the stream on C# side.
Streams are implementation-specific to each compiler, so you can't pass a (native) Delphi TStream to C# and expect it to work over there. Likewise a C# Stream is not compatible with a (native) Delphi TStream, so you can't go the other way as well.
It may be possible (using a lot of dirty hacks) to do some form of inter-operability between C# classes and Delphi classes, but quite frankly, it'll be a lot of work for very little benefit, in particular (no insult intended) for your current level of experience (as you consider streams "mysterious").
If it is a file you are trying to pass over, pass the file name instead and let the C# side allocate a C# stream to access it.
Edit: You can use the IStream interface to wrap the (native) Delphi TStream instance and pass this interface to C#, which then should be able to access the (native) Delphi TStream from the (managed) C# code. See Dalija Prasnikar's answer.

C# string to Inno Setup

I have a C# DLL in which exposes a method which generates a string.
I want to call this Method from Inno Setup and receive the string then.
function GetInformationEx():String;
external 'GetInformationEx#{src}\data\tools\ZipLib.dll stdcall loadwithalteredsearchpath';
procedure ShowProgress(progress:Integer);
var
information : String;
begin
WriteDebugString('ShowProgress called');
if(progress > pbStateZip.position) then
begin
pbStateZip.position := progress;
lblState2.Caption := IntToStr(progress)+' %';
try
information := GetInformationEx();
except
ShowExceptionMessage;
end;
//Do something with the information
end
if(progress >= 100)then
begin
KillTimer(0,m_timer_ID);
//Inform that the extraction is done
end
WriteDebugString('ShowProgress leave');
end;
Here is my simple C# part
[DllExport("GetInformationEx", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
public static String GetInformationEx()
{
return "Some simple text message || or heavy information";
}
My question is:
What kind of type do I have to send back to Inno Setup so that Inno Setup can handle it correctly?
Until now I get this message
PS: I read this post:
Returning a string from a C# DLL with Unmanaged Exports to Inno Setup script
But I want the C# code to be in charge of the string.
The .NET String type definitely won't marshal to Pascal string type. The .NET does not know anything about Pascal types.
The .NET can marshal strings to character arrays (and Pascal can marshal string from character arrays). But when the string is a return type of a function, there's problem with allocation of the memory for the string (who allocates the memory, who releases it).
That's why the solution in question you pointed to suggests you to use by ref/out parameter, because this way the caller can provide a buffer the .NET can marshal the string to. So there are no problems with allocation.

PInvoke & Delphi

How can i use this dll function in c#? I tried the following but i get error.
"External component has thrown an exception."
First time i am doing this PInvoke stuff with C# and Delphi.
function HTTPGET(location:string):string; stdcall;
var
HTTP:TIdHttp;
begin
HTTP := TidHttp.Create(nil);
try
result := HTTP.Get(location);
finally
FreeAndNil(HTTP);
end;
end;
exports
HTTPGET;
begin
end.
namespace Test
{
class Program
{
[DllImport("project1.dll")]
public static extern string HTTPGET(string location);
static void Main(string[] args)
{
Console.WriteLine(HTTPGET("http://www.reuters.com/"));
}
}
}
You cannot call that function from C#. That's because you cannot use Delphi string for interop. You can use PAnsiChar for strings passed from managed to unmanaged, but in the other direction it's more complex. You'd need to allocate the memory at the caller, or use a shared heap. I prefer the latter approach which is easiest done with the COM BSTR. This is WideString in Delphi.
As has been discussed before, you cannot use WideString as a return value for interop, since Delphi uses a different ABI from MS tools for return values.
The Delphi code needs to look like this:
procedure HTTPGET(URL: PAnsiChar; out result: WideString); stdcall;
On the C# side you write it like this:
[DllImport("project1.dll")]
public static extern void HTTPGET(
string URL,
[MarshalAs(UnmanagedType.BStr)]
out string result
);
If you want Unicode for the URL then use PWideChar and CharSet.Unicode.
procedure HTTPGET(URL: PWideChar; out result: WideString); stdcall;
....
[DllImport("project1.dll", CharSet=CharSet.Unicode)]
public static extern void HTTPGET(
string URL,
[MarshalAs(UnmanagedType.BStr)]
out string result
);
Do not use string type: strings require memory management, and C# and Delphi modules obviously use different memory managers (leave alone that C# passes char* and Delphi expects String). Try changing location type to PChar in your DLL, and also change your result type so it's either PChar (the buffer should be allocated explicitly) or something else, but not string.
As i remember, you can't marshall delphi strings with C#... you have to use a workaround with PChar and manage the memory yourself, or use something like the workaround provided in the last answer here:
Using Delphi's stuct arrays and strings in C#
Try Unmanaged Exports for c# by Robert Giesecke
https://sites.google.com/site/robertgiesecke/Home/uploads/unmanagedexports
we are using encrypting engine from c# in Delphi App due compatibility with php and works well (so, the mentioned strings problem do not exists).
our earlier solution (worse) : register c# dll as com component and use it. with above solution output library have to be placed in exe directory and without registration works well :)

Using Delphi DLL with dynamic array from C#

I have a Delphi DLL that contains the following types:
type
TStepModeType = (smSingle, smMultiStep);
TParameter = record
Number: Integer;
end;
TStruct = record
ModType: PAnsiChar;
ModTypeRev: Integer;
ModTypeID: Integer;
RecipeName: PAnsiChar;
RecipeID: Double;
RootParamCount: Integer;
StepMode: TStepModeType;
ParamCount: Integer;
Parameters: array of TParameter;
end;
I need to call this DLL from C# passing a ref object corresponding to the Delphi types that the DLL will fill and return. I have defined structures in my C# code like this:
enum stepModeType
{
Single,
MultiStep
}
[StructLayout(LayoutKind.Sequential)]
struct parameter
{
public int Number;
}
[StructLayout(LayoutKind.Sequential)]
struct recipe
{
public string modType;
public int modTypeRev;
public int modTypeId;
public string recipeName;
public double recipeId;
public int rootParamCount;
public stepModeType stepMode;
public int paramCount;
public IntPtr parameters;
}
I was doing fine until I ran into the dynamic array (Parameters: array of TParameter) in the Delphi code. I understand that dynamic arrays are a Delphi only construct, so I chose to use an IntPtr in my C# code in the hopes of just getting a pointer to the array and pulling the contents. Unfortunately, I am rather new to this interop stuff and I am not sure how to deal with the IntPtr.
Let's say the Delphi DLL populates the dynamic array with 2 parameter items. Can someone possibly show me the C# code that would get those 2 parameter items out of the array once it gets passed back from the Delphi DLL to my C# calling application?
UPDATE: Well, as it happens the Delphi code I was given was a simplified version. One of our Delphi developers thought it would be easier to get started with the simplified version than the real version, which is substantially more complex containing dynamic arrays of dynamic arrays of dynamic arrays. Anyway, I am now completely over my head. I only know enough about Delphi to be dangerous. Below is the code for the real structures in the Delphi code. Any further guidance on how to deal with these structures from my C# calling application would be greatly appreciated. It may not even be possible with the nesting of dynamic arrays such that they are.
type
TStepModeType = (smSingle, smMultiStep);
TParamValue = record
strVal: String;
fVal: Double;
Changed: Boolean;
end;
TSteps = array of TParamValue;
TRule = record
Value: String;
TargetEnabled: Boolean;
end;
TParamInfo = record
Caption: String;
Units: String;
RuleCount: Integer;
Rules: array of TRule;
end;
TParameter = record
Info: TParamInfo;
Steps: TSteps;
end;
TStruct = record
ModType: PAnsiChar;
ModTypeRev: Integer;
ModTypeID: Integer;
RecipeName: PAnsiChar;
RecipeID: Double;
RootParamCount: Integer;
StepMode: TStepModeType;
ParamCount: Integer;
Parameters: array of TParameter;
end;
I am assuming trust that the DLL has a function that deallocates the recipe struct. That's something that you can't possibly hope to do from C#. More on this point later on.
A Delphi dynamic array is not a valid interop type. It really should only used internally to Delphi code compiled with a single version of the compiler. Exposing it publically is akin to exporting C++ classes from a DLL.
In an ideal world you would re-work the Delphi code so that it exported the array using a proper interop type. However, in this case it is actually relatively easy for you to do the marshalling without adjusting the Delphi code.
Delphi dynamic arrays were introduced way back in Delphi 4 and their implementation has remained unchanged since then. The array of T dynamic array variable is effectively a pointer to the first element. The elements are laid out sequentially in memory. The dynamic array variable also maintains (at negative offsets) a reference count and the size of the array. You can safely ignore these since you are neither modifying the dynamic array nor needing to ascertain its size.
Using IntPtr for the Parameters field is perfect. Because TParameter contains just a single 32 bit integer you can use Marshal.Copy to copy it straight to an int[] array.
So, when the Delphi DLL returns, you can do the final marshalling step using Marshal.Copy.
if (theRecipe.paramCount>0)
{
int[] parameters = new int[theRecipe.paramCount];
Marshal.Copy(theRecipe.parameters, parameters, 0, theRecipe.paramCount);
... do something with parameters
}
That deals with the dynamic array, but as it happens you have another problem with your code as it stands. You are declaring the two strings as string in the C# struct. This means that the marshaller will take responsibility for freeing the memory returned by the Delphi DLL in the two PAnsiChar fields. It will do so by calling CoTaskMemFree. I'm fairly sure that's not going to match the allocation of the PAnsiChar fields made in the Delphi code.
As stated above, I would expect that the contract for this interface is that you call a further DLL function to deallocate the heap memory referenced by the recipe struct. That is, the two strings, and the dynamic array.
To deal with this issue from C# you need to make sure that the marshaller does not attempt to deallocate the PAnsiChar fields. You can achieve that by declaring them as IntPtr in the C# struct. Then call Marshal.PtrToStringAnsi to convert to a C# string.
I've had to make a few assumptions about the contract between the Delphi code and the C# code in order to write the above. If any of my assumptions are incorrect please update the question and I'll try to make this answer match! I hope this helps.
Jargon confusion I suspect, my first thought was simply.
public parameter[] parameters;
Two options: Either you figure out exactly how dynamic arrays are stored and match that on the c# side or better still create a set of basic methods on the Delphi side that can be called from the c# side to manipulate the array and record, eg getItem and setItem etc. That's usually what is done when there are incompatible types across a language barrier. I would use the later approach because you don't know whether at some point in the future the memory structure of a dynamic array might change.
By the way, why have you defined TParameter as a record, you could have used TParameter = integer ?
I found this link which has something to say about the structure of Delphi dynamic arrays:
http://www.programmersheaven.com/mb/delphikylix/262971/262971/dynamic-array-memory-storage/
And this link has even more details. The structure is a bit more complicated than a simple array.
Dynamic Array Structure

What happens between the .NET interop layer and COM?

I am using COM in my C# .NET project.
However one of the methods I call is not acting as expected.
So I am curious to see what is happening between my .NET code, the Interop layer and COM.
I know the tlbimp.exe generates the metadata wrapper for the COM component and I can see these generated methods in the Object browser.
Am I able to see/debug what happens when one of these wrapper methods is called?
I pass an Array to the method below, and expect that this array will be populated, however the Array does not get populated.
I am calling the following tlbimp.exe generated method with unexpected results:
int GetTags(System.Array buffer)
Member of CServer.IUser
Method IDL:
[id(0x000000d5)]
HRESULT GetTags(
[in] SAFEARRAY(long) buffer,
[out, retval] long* retval);
.NET code calling this method:
Array tagsArray = Array.CreateInstance(typeof(int), tagsLength);
userWrapper.GetTags(tagsArray);
Other COM methods I call work fine. However when I call any method which expects an Array as a parameter it does not work as expected.
I am presuming that there is something funny going in with the COM interop marshaller.
So I would like to know if I can see what is happening after I call the GetTags() method.
Also I have read the following here.
"if you are not satisified with the COM Interop marshaller, you can "override" just about every aspect of it through the very large and useful System::Runtime::InteropServices namespace"
How can I achieve the above?
EDIT: Adding a Delphi test script which works
procedure TComTestForm.TestUserBtnClick(Sender: TObject);
var
nCnt :integer;
User :IUser;
Persona :IUserPersona;
ArrayBounds :TSafeArrayBound;
ArrayData :Pointer;
TagList :PSafeArray;
nSize :integer;
begin
User := Session.GetUser;
ArrayBounds.lLbound := 0;
ArrayBounds.cElements := 0;
TagList := SafeArrayCreate( varInteger, 1, ArrayBounds );
User.GetTags( TagList );
if SafeArrayAccessData( TagList, ArrayData ) = S_OK then
begin
nSize := TagList.rgsabound[0].cElements;
OutLine( '----Available Tags, ' + IntToStr(nSize) + ' tags' );
for nCnt := 0 to nSize - 1 do
begin
OutLine( IntToStr( IntegerArray(ArrayData)[nCnt] ) );
end;
OutLine( '----');
SafeArrayUnAccessData( TagList );
SafeArrayDestroy( TagList );
end;
end;
Another update:
I just realize it might be that you mean that the GetTags itself should populate that Array (from the COM code). But this can never work as that parameter is an [in] parameter.
For the COM component to be able to fill up that Array, It should be passed as an [in, out] parameter, and by reference (SAFEARRAY*).
Update: Ok, apparently I was mixing the creation of a COM component in .NET with calling a COM component from .NET.
The CCW (com callable wrapper) indeed takes a .NET Array for COM SafeArray's. I see you create your array in the code in your question, but you don't show how you actually populate it. Maybe something's wrong with that code? Could you share it?
Not sure if this is a solution to your problem, but I've experienced problems with COM-interop and SAFEARRAY's in the past.
One thing I learned from it is that the .NET equivalent of a COM SAFEARRAY should always be object, so try passing your array as an object in stead of as an Array.
I hesitate to suggest this as an answer, but...
If the Delphi test code really does work, as noted elsewhere, this means the GetTags method must not be playing properly by SAFEARRAY rules. If the COM method is always called in-process, you MAY be able to get the .NET code to work by doing some custom marshalling "by hand", following exactly what the unmanaged Delphi test code does.
As a rough outline, I would imagine that this would involve:
allocating an unmanaged buffer to hold the array values
calling Ole Automation SAFEARRAY initialization APIs via P/Invoke to allocate a SAFEARRAY structure and attach the array buffer to it as its pData member
invoking the GetTags method with this SAFEARRAY
marshalling your unamanaged buffer into a managed array, before...
calling the Win32 API to destroy the SAFEARRAY
But much better to get the COM component changed to do things properly, if you can.

Categories