i have a problem to use delphi class from C++ code. delphi dll demo that export a function that return an object.
my delphi Dll code is as follow:
library DelphiTest;
// uses part....
type
IMyObject = interface
procedure DoThis( n: Integer );
function DoThat : PWideChar;
end;
TMyObject = class(TInterfacedObject,IMyObject)
procedure DoThis( n: Integer );
function DoThat: PChar;
end;
// TMyObject implementation go here ...
procedure TMyObject.DoThis( n: Integer );
begin
showmessage('you are calling the DoThis methode with '+intToStr(n) +'parameter');
end;
function TMyObject.DoThat: PChar;
begin
showmessage('you are calling the DoThat function');
Result := Pchar('Hello im Dothat');
end;
// exporting DLL function :
function CreateMyObject : IMyObject; stdcall;export;
var
txt : TextFile;
begin
AssignFile(txt,'C:\log.log');
Reset(txt);
Writeln(txt,'hello');
Result := TMyObject.Create;
end;
exports CreateMyObject;
in my C++ project i declared the IMyObject interface as follow :
class IMyObject
{
public:
IMyObject();
virtual ~IMyObject();
virtual void DoThis(int n) = 0;
virtual char* DoThat() = 0;
};
and my main function as follow :
typedef IMyObject* (__stdcall *CreateFn)();
int main()
{
HMODULE hLib;
hLib = LoadLibrary(L"DelphiTest.dll");
assert(hLib != NULL); // pass !!
CreateFn pfnCreate;
pfnCreate = (CreateFn)GetProcAddress((HINSTANCE)hLib, "CreateMyObject");
if (pfnCreate == NULL)
{
DWORD errc = GetLastError();
printf("%u\n", errc); // it gets error 127
}
else{
printf("success load\n");
}
IMyObject* objptr = pfnCreate();
objptr->DoThis(5);
FreeLibrary(hLib);
int in;
scanf_s("%i", &in);
return 0;
}
with this example i got an error at run time when i try to access the exported function. the errors is at line :
IMyObject* objptr = pfnCreate();
can you tell me what is wrong about my example.
and if possible any working example to access Delphi class (in DLL) from C++ code.
The first problem is calling convention of the methods. The Delphi interface uses register which is a Delphi specific calling convention. Use stdcall, for example, for the methods of the interface.
The next problem is in the C++. Your C++ interface must derive from IUnknown. Further, it should not declare a constructor or destructor.
Beyond that your Delphi code exports PWideChar which does not map to char*. It maps to wchar_t*.
Looking further ahead, returning a PChar works fine here because your implementation returns a literal. But more serious code will want to use a dynamically allocated string presumably and at that point your design is flawed.
Do note that you need to be an elevate administrator to create a file at the root of the system drive. So that's yet another potential failure point.
I expect there are other mistakes, but that's all I've found so far.
Related
I have, on more than one occasion, advised people to use a return value of type WideString for interop purposes.
Accessing Delphi DLL throwing ocasional exception
ASP.NET web app calling Delphi DLL on IIS webserver, locks up when returning PChar string
Why can Delphi DLLs use WideString without using ShareMem?
The idea is that a WideString is the same as a BSTR. Because a BSTR is allocated on the shared COM heap then it is no problem to allocate in one module and deallocate in a different module. This is because all parties have agreed to use the same heap, the COM heap.
However, it seems that WideString cannot be used as a function return value for interop.
Consider the following Delphi DLL.
library WideStringTest;
uses
ActiveX;
function TestWideString: WideString; stdcall;
begin
Result := 'TestWideString';
end;
function TestBSTR: TBstr; stdcall;
begin
Result := SysAllocString('TestBSTR');
end;
procedure TestWideStringOutParam(out str: WideString); stdcall;
begin
str := 'TestWideStringOutParam';
end;
exports
TestWideString, TestBSTR, TestWideStringOutParam;
begin
end.
and the following C++ code:
typedef BSTR (__stdcall *Func)();
typedef void (__stdcall *OutParam)(BSTR &pstr);
HMODULE lib = LoadLibrary(DLLNAME);
Func TestWideString = (Func) GetProcAddress(lib, "TestWideString");
Func TestBSTR = (Func) GetProcAddress(lib, "TestBSTR");
OutParam TestWideStringOutParam = (OutParam) GetProcAddress(lib,
"TestWideStringOutParam");
BSTR str = TestBSTR();
wprintf(L"%s\n", str);
SysFreeString(str);
str = NULL;
TestWideStringOutParam(str);
wprintf(L"%s\n", str);
SysFreeString(str);
str = NULL;
str = TestWideString();//fails here
wprintf(L"%s\n", str);
SysFreeString(str);
The call to TestWideString fails with this error:
Unhandled exception at 0x772015de in BSTRtest.exe: 0xC0000005: Access violation reading location 0x00000000.
Similarly, if we try to call this from C# with p/invoke, we have a failure:
[DllImport(#"path\to\my\dll")]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string TestWideString();
The error is:
An unhandled exception of type 'System.Runtime.InteropServices.SEHException' occurred in ConsoleApplication10.exe
Additional information: External component has thrown an exception.
Calling TestWideString via p/invoke works as expected.
So, use pass-by-reference with WideString parameters and mapping them onto BSTR appears to work perfectly well. But not for function return values. I have tested this on Delphi 5, 2010 and XE2 and observe the same behaviour on all versions.
Execution enters the Delphi and fails almost immediately. The assignment to Result turns into a call to System._WStrAsg, the first line of which reads:
CMP [EAX],EDX
Now, EAX is $00000000 and naturally there is an access violation.
Can anyone explain this? Am I doing something wrong? Am I unreasonable in expecting WideString function values to be viable BSTRs? Or is it just a Delphi defect?
In regular Delphi functions, the function return is actually a parameter passed by reference, even though syntactically it looks and feels like an 'out' parameter. You can test this out like so (this may be version dependent):
function DoNothing: IInterface;
begin
if Assigned(Result) then
ShowMessage('result assigned before invocation')
else
ShowMessage('result NOT assigned before invocation');
end;
procedure TestParameterPassingMechanismOfFunctions;
var
X: IInterface;
begin
X := TInterfaceObject.Create;
X := DoNothing;
end;
To demonstrate call TestParameterPassingMechanismOfFunctions()
Your code is failing because of a mismatch between Delphi and C++'s understanding of the calling convention in relation to the passing mechanism for function results. In C++ a function return acts like the syntax suggests: an out parameter. But for Delphi it is a var parameter.
To fix, try this:
function TestWideString: WideString; stdcall;
begin
Pointer(Result) := nil;
Result := 'TestWideString';
end;
In C#/C++ you will need to define the Result as out Parameter, in order to maintain binary code compatibility of stdcall calling conventions:
Returning Strings and Interface References From DLL Functions
In the stdcall calling convention, the function’s result is passed via the CPU’s EAX register. However, Visual C++ and Delphi generate different binary code for these routines.
Delphi code stays the same:
function TestWideString: WideString; stdcall;
begin
Result := 'TestWideString';
end;
C# code:
// declaration
[DllImport(#"Test.dll")]
static extern void TestWideString([MarshalAs(UnmanagedType.BStr)] out string Result);
...
string s;
TestWideString(out s);
MessageBox.Show(s);
Let me start off by saying that VB is not my strong suit.
I am developing a C++ dll to be used in a VB6 application's dll.
I have successfully instantiated the (C++) classes in VB. I am trying to access data members of the class using this syntax: "vbCppObj.dataMemberName".
I can confirm that this works for boolean and enum types and it invokes the getter methods defined in my class.
I have to access a string from the (C++) class as well.
The getter function for the string is given below:
class MyCPPClass
{
private:
WCHAR* CPPErrorString = L"This is a string";
public:
HRESULT __stdcall get_CPPErrorString(BSTR* pVal)
{
BSTR str = ::SysAllocString(CPPErrorString);
if(str)
*pVal = str;
return S_OK;
}
};
I am unable to debug the C++ dll right now.
I access this value in the VB6 code as follows:
ErrorString = vbCppObj.CPPErrorString
Logger.log "[Log]:" & ErrorString
"ErrorString" is a String type in VB. When this line executes, the "ErrorString" object shows "<Out of memory>" (when I hover over it). If I step further, to the logging code, it gives me a "Error 14: Out of string space".
Also, I have typed this code in the browser so, it may not be 100% correct.
As it turns out, I had to convert the string into a "_b_str" and then to a "BSTR". That worked for me.
I had tried it earlier but I don't know why it didn't work at that time.
Why you just don't use LPCTSTR?
I'm not an advanced C/C++ programmer, but this should work
class MyCPPClass
{
private:
LPCTSTR CPPErrorString = "This is a string";
public:
HRESULT __stdcall get_CPPErrorString(LPCTSTR * pVal)
{
// copy the value
*pVal = CPPErrorString;
// return
return S_OK;
}
}
I'm trying to call a C++ DLL function that is defined like this:
int read_record (filep *fptr, int key, char *contents, int *status)
This is a widely used DLL, so I'm pretty certain the problem I'm having is how I'm calling the function.
The DLL docs have this example of how to call it
TFILETYPE *fptr; /* file pointer
char contents[80];
int status = 0;
int single = key;
if (read_record(fptr, key, card, &status)) break;
printf("%s\n", card);
Here's what I think should work, and almost does:
type
TCharArray = Array[1..100] of AnsiChar; // Function returns an array less than 100 char
var
read_record : function( var fptr: TFILETYPE;
Key: Integer;
var Contents: TCharArray; // function fills this in
var Status: Integer): Integer cdecl stdcall;
Procedure Test;
var
Contents: TCharArray;
Status: Integer;
Key: Integer;
begin
#read_record:= GetProcAddress(DLLHandle, 'read_record');
for Key := 1 to 10 do
begin
Contents[1] := #0; // shouldn't be necessary
if (read_record( fptr^, Key, Contents, Status) <> 0) OR (Status <> 0) then
ShowMessage('Error')
else
ShowMessage(Contents); // This shows the expected proper string on all 10 calls
...Other calls at this point to other functions in the DLL result in
an Exception writing to x01a.
end;
Multiple calls from Delphi XE work fine. But after that, when I call different function in the DLL that has always worked in the past, I get an exception writing to x0000001a, which I suspect means I've trashed memory or the stack.
The *fptr pointer datatype I'm using in calls to other functions in the dll, so I don't think that's the problem.
This is the first time I've tried to call a function that returns a string, so I suspect I'm not understanding something with call by reference of string arrays.
Any suggestions on how I should call this function differently to avoid what appears to be trashing of memory?
You have a couple of problems here.
First, the function declaration in Delphi is wrong. First, it's declared as both cdecl and stdcallat the same time. It needs to be one or the other; it can't be both simultaneously.
Also, you have an extra level of dereferencing on the fptr variable.
The declaration indicates it's a pointer:
filep *fptr
You've said in your Delphi declaration that it's a pointer:
var fptr: TFileType
But you're passing a pointer to a pointer:
fptr^
Change your call to the DLL function to
if (read_record( fptr, Key, Contents, Status) <> 0) OR (Status <> 0)
(I think your test of the results should actually be <> 0) AND (Status <> 0), but without the docs I'm not sure.)
If you do in fact need to initialize Contents before passing it to the DLL, you should use FillChar(Contents, SizeOf(Contents), #0) (or ZeroMemory) to do so, BTW.
As an additional suggestion, you can simplify your code somewhat (I've chosen stdcall as the calling convention):
var
read_record : function( var fptr: TFILETYPE;
Key: Integer;
Contents: PAnsiChar; // function fills this in
var Status: Integer): Integer stdcall;
var
Contents: AnsiString;
...
begin
SetLength(Contents, 100);
if (read_record(fptr, Key, PAnsiChar(Contents), Status)...
....
end;
I have a dll written in delphi which exports a function which is as follows
function LaneController_Init(OnIOChangeEvent:TOnIOChangeEvent):Integer; stdcall;
OnIOChangeEvent is a callback function and propertype is
TOnIOChangeEvent = procedure(sender:TObject;DeviceID,iFlag:Integer) of object;
Now my question is in C++,how to define callback function TOnIOChangeEvent?
Thanks a lot.
Your DLL is using two distinct features of Delphi that only C++Builder supports, no other C++ compiler does:
Your callback is using the of object modifier, which means the callback can be assigned a non-static method of an object instance. That is implemented in C++Builder using a vendor-specific __closure compiler extension. Although standard C++ does have a syntax for using function pointers to object methods, the implementation is very different than how __closure is implemented.
Your callback does not declare any calling convention, so Delphi's default register calling convention is being used. In C++Builder, that corresponds to the vendor-specific __fastcall calling convention (which is not to be confused with Visual C++'s __fastcall calling convention, which is completely different, and implemented as __msfastcall in C++Builder).
If you only care about supporting C++Builder, then you can leave the DLL code as-is and the corresponding C++ code would look like this:
typedef void __fastcall (__closure *TOnIOChangeEvent)(TObject *Sender, int DeviceID, int iFlag);
int __stdcall LaneController_Init(TOnIOChangeEvent OnIOChangeEvent);
void __fastcall TSomeClass::SomeMethod(TObject *Sender, int DeviceID, int iFlag)
{
//...
}
TSomeClass *SomeObject = ...;
LaneController_Init(&(SomeObject->SomeMethod));
However, if you need to support other C++ compilers, then you need to change the DLL to support standard C/C++, for example:
type
TOnIOChangeEvent = procedure(DeviceID, iFlag: Integer; UserData: Pointer); stdcall;
function LaneController_Init(OnIOChangeEvent: TOnIOChangeEvent; UserData: Pointer): Integer; stdcall;
Then you can do the following in C++:
typedef void __stdcall (*TOnIOChangeEvent)(int DeviceID, int iFlag, void *UserData);
int __stdcall LaneController_Init(TOnIOChangeEvent OnIOChangeEvent, void *UserData);
void __fastcall TSomeClass::SomeMethod(int DeviceID, int iFlag)
{
//...
}
// note: not a member of any class. If you want to use a class
// method, it will have to be declared as 'static'...
void __stdcall LaneControllerCallback(int DeviceID, int iFlag, void *UserData)
{
((TSomeClass*)UserData)->SomeMethod(DeviceID, iFlag);
}
TSomeClass *SomeObject = ...;
LaneController_Init(&LaneControllerCallback, SomeObject);
You cannot have functions "of object" in DLL and in C++ for few reasons.
C++ classes infrastructure would probably be different from Delphi one, until you would prove they are identical bit to bit. "TObject" should be proved to be identical common ground. http://docwiki.embarcadero.com/RADStudio/XE5/en/Libraries_and_Packages_Index
DLLs have no type safety. They only have "name=pointer" lists. So even different versions of Delphi would have different, incompatible implementations of classes.
Even if you would make application and DLL in the same Delphi version, you would still have two different TObject classes: EXE.TObject and DLL.TObject. While their implementations would hopefully be clones of each other, as pointers they would be different and hence any checks like EXE.TForm is DLL.TComponent or typecast like DLL.TButton as EXE.TPersistent would fail, ruining the logic of your code, that would falsely expect OOP inheritance basics to work.
So what can you do about that ? What kind of "common ground" you can build ?
Hi-tech option is to use some rich cross-platform ABI (binary interface) that have notions of objects. For Windows you typically use COM-objects like Excel, Word, Internet Explorer. So you make a COM Server in C++, that has some GUID-tagged interface that has the callback functions in it. Then you pass the interface-pointer to the callback function. Just like you use other COM-servers and Active-X controls like TExcelApplication, TWebBrowser and whatever. There also are other methods like CORBA, JSON-RPC, SOAP and others. And you would only be allowed to use parameters of data types standardized by those cross-platform interop standards.
For low-tech option you have to look at Windows API as a typical example of "flattening" object-oriented interface to non-object language.
function LaneController_Init(OnIOChangeEvent:TOnIOChangeEvent; ASelf: pointer):Integer; stdcall;
TOnIOChangeEvent = procedure(const Self: pointer; const Sender: Pointer; const DeviceID, iFlag:Integer); stdcall;
In Delphi, you would write something like
procedure OnIOChangeCallBack(const ASelf: pointer; const Sender: Pointer; const DeviceID, iFlag:Integer); stdcall;
begin
(TObject(ASelf) as TMyClass).OnIOChange(Sender, DeviceID, iFlag);
end;
Probably that would look like this in C++
void stdcall OnIOChangeCallBack(const void * Self; const void * Sender: Pointer; const int DeviceID; const int iFlag);
{
((CMyClass*)Self)->OnIOChange(Sender, DeviceID, iFlag);
}
Again, you would only be able to use those datatypes for parameters, that are "greatest common denominator" between languages, like integers, doubles and pointers.
I agree with much that #Arioch is saying, but I think it's rather silly to use naked pointers when we have interfaces, that work across the board on Windows.
I suggest you use a COM automation interface.
First create a standard program.
Inside that create a Automation Object using the steps outlined below.
See here for a tutorial, note that both Delphi and C++ builder code/examples are provided.
http://docwiki.embarcadero.com/RADStudio/XE3/en/Creating_Simple_COM_Servers_-_Overview
Once you've created your automation object, add an interface and at least one procedure to that interface.
You cannot reference Delphi objects in your code, it just will not port; however you can use your own interfaces, as long as you declare them in the type library editor.
Make sure you set the parent interface to IDispatch.
Now everywhere where you wanted to use an object, just use an appropriate interface.
Note that TObject does not implement any interfaces, but TComponent does. Many other Delphi objects implement interfaces as well.
You'll probably want to extend an existing object so as to implement your own interfaces.
Here's some sample code:
unit Unit22;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, AxCtrls, Classes,
Project23_TLB, StdVcl;
type
TLaneController = class(TAutoObject, IConnectionPointContainer, ILaneController)
private
{ Private declarations }
FConnectionPoints: TConnectionPoints;
FConnectionPoint: TConnectionPoint;
FEvents: ILaneControllerEvents;
FCallBack: ICallBackInterface; /////////
{ note: FEvents maintains a *single* event sink. For access to more
than one event sink, use FConnectionPoint.SinkList, and iterate
through the list of sinks. }
public
procedure Initialize; override;
protected
procedure LaneController_Init(const CallBack: ICallBackInterface); safecall;
{ Protected declarations }
property ConnectionPoints: TConnectionPoints read FConnectionPoints
implements IConnectionPointContainer;
procedure EventSinkChanged(const EventSink: IUnknown); override;
procedure WorkThatCallBack; /////////
{ TODO: Change all instances of type [ITest234Events] to [ILaneControllerEvents].}
{ Delphi was not able to update this file to reflect
the change of the name of your event interface
because of the presence of instance variables.
The type library was updated but you must update
this implementation file by hand. }
end;
implementation
uses ComServ;
procedure TLaneController.EventSinkChanged(const EventSink: IUnknown);
begin
FEvents := EventSink as ILaneControllerEvents;
end;
procedure TLaneController.Initialize;
begin
inherited Initialize;
FConnectionPoints := TConnectionPoints.Create(Self);
if AutoFactory.EventTypeInfo <> nil then
FConnectionPoint := FConnectionPoints.CreateConnectionPoint(
AutoFactory.EventIID, ckSingle, EventConnect)
else FConnectionPoint := nil;
end;
procedure TLaneController.LaneController_Init(const CallBack: ICallBackInterface);
begin
FCallBack:= CallBack;
end;
procedure TLaneController.WorkThatCallBack;
const
SampleDeviceID = 1;
SampleFlag = 1;
begin
try
if Assigned(FCallBack) then FCallBack.OnIOChangeEvent(Self, SampleDeviceID, SampleFlag);
except {do nothing}
end; {try}
end;
initialization
TAutoObjectFactory.Create(ComServer, TLaneController, Class_LaneController,
ciMultiInstance, tmApartment);
end.
Note that most of this code is auto-generated.
Only the members marked with //////// are not.
I want to call a method in a COM component from C# using COM interop. This is the methods signature:
long GetPrecursorInfoFromScanNum(long nScanNumber,
LPVARIANT pvarPrecursorInfos,
LPLONG pnArraySize)
and this is sample code (which I checked is really working) to call it in C++:
struct PrecursorInfo
{
double dIsolationMass;
double dMonoIsoMass;
long nChargeState;
long nScanNumber;
};
void CTestOCXDlg::OnOpenParentScansOcx()
{
VARIANT vPrecursorInfos;
VariantInit(&vPrecursorInfos);
long nPrecursorInfos = 0;
m_Rawfile.GetPrecursorInfoFromScanNum(m_nScanNumber,
&vPrecursorInfos,
&nPrecursorInfos);
// Access the safearray buffer
BYTE* pData;
SafeArrayAccessData(vPrecursorInfos.parray, (void**)&pData);
for (int i=0; i < nPrecursorInfos; ++i)
{
// Copy the scan information from the safearray buffer
PrecursorInfo info;
memcpy(&info,
pData + i * sizeof(MS_PrecursorInfo),
sizeof(PrecursorInfo));
}
SafeArrayUnaccessData(vPrecursorInfos.parray);
}
And here's the corresponding C# signature after importing the typelib of the COM component:
void GetPrecursorInfoFromScanNum(int nScanNumber, ref object pvarPrecursorInfos, ref int pnArraySize);
If I'm not mistaken, I need to pass in null for pvarPrecursorInfos to make COM interop marshal it as the expected VT_EMPTY variant. When I'm doing it, I get a SafeArrayTypeMismatchException - not really surprising, looking at how the result is expected to be handled in the sample. So I was trying to use a custom marshaler. Since a cannot alter the component itself, I tried to introduce it this way:
[Guid("06F53853-E43C-4F30-9E5F-D1B3668F0C3C")]
[TypeLibType(4160)]
[ComImport]
public interface IInterfaceNew : IInterfaceOrig
{
[DispId(130)]
int GetPrecursorInfoFromScanNum(int nScanNumber, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshaler))] ref object pvarPrecursorInfos, ref int pnArraySize);
}
The TypeLibType and DispID attribute are the same as in the original version. This works as far as that the MyMarshaller.GetInstance() method is called, but I do not get a callback in MyMarshaller.NativeToManaged. Instead, an access violation is reported. So is this a reliable approach? If yes - how can I make it work? If no: are there any alternatives?
(Just a footnote: in theory I could try to use managed C++ to call the component natively. However, there are lots of other methods in it that work fine with COM interop, so I would very much like to stick with C# if there is any way.)
Since someone asked for it, here's my solution in Managed C++.
array<PrecursorInfo^>^ MSFileReaderExt::GetPrecursorInfo(int scanNumber)
{
VARIANT vPrecursorInfos;
VariantInit(&vPrecursorInfos);
long nPrecursorInfos = -1;
//call the COM component
long rc = pRawFile->GetPrecursorInfoFromScanNum(scanNumber, &vPrecursorInfos, &nPrecursorInfos);
//read the result
//vPrecursorInfos.parray points to a byte sequence
//that can be seen as array of MS_PrecursorInfo instances
//(MS_PrecursorInfo is a struct defined within the COM component)
MS_PrecursorInfo* pPrecursors;
SafeArrayAccessData(vPrecursorInfos.parray, (void**)&pPrecursors);
//now transform into a .NET object
array<PrecursorInfo^>^ infos = gcnew array<PrecursorInfo^>(nPrecursorInfos);
MS_PrecursorInfo currentPrecursor;
for (int i=0; i < nPrecursorInfos; ++i)
{
currentPrecursor = pPrecursors[i];
infos[i] = safe_cast<PrecursorInfo^>(Marshal::PtrToStructure(IntPtr(¤tPrecursor), PrecursorInfo::typeid));
}
SafeArrayUnaccessData(vPrecursorInfos.parray);
return infos;
}
I look at the github code mzLib, which I believe is related to this topic. The code looks good, where it calls
pin_ptr<const wchar_t> wch = PtrToStringChars(path);
I think it may cause some problem, better use
pin_ptr<const wchar_t> pathChar = static_cast<wchar_t*>(System::Runtime::InteropServices::Marshal::StringToHGlobalUni(path).ToPointer());
The code then seems to be worked just fine when compiles. However, it might run in problem when imported as dll. I worked on that by adding a constructor,such as
public ref class ThermoDLLClass
{
public:
ThermoDLLClass();
PrecursorInfo GetPrecursorInfo(int scanNum, String^ path);
};
Then, it seems to get precursorInfo and parameters appropriately.