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.
Related
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.
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 :)
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.
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.
what is the java DataInputStream readFully method equivalent in C# BinaryReader?
What is the java DataInputStream mark and reset methods equivalent in C# BinaryReader or Stream?
see BinaryReader.Read(byte[], int, int)
There is no object that have this behavior in .NET, you should implement it by yourself.