Problems with Delphi DLL IStream parameter calling from C++ - c++

I have DLL written on Delphi. I have only DLL without header file, so I load it dynamically. (to C++ project)
HMODULE hLib = LoadLibrary(L"LibName.dll");
if (!hLib) {
//Throw error
}
DLL provides functions:
function DataToFile(AddressName: PChar; Request: PChar;
RequestSize: integer; ResultFile: PChar;
ErrorBuf: PChar; ErrorBufSize: integer):BOOL;stdcall;
function DataToStream(AddressName: PChar; Request: PChar;
RequestSize: integer; ResultStream: IStream;
ErrorBuf: PChar; ErrorBufSize: integer):BOOL;stdcall;
My Visual Studio Code (C++):
typedef bool(__stdcall* f_DataToFile)(
char*, //AddressName: PChar
char*, //Request: PChar
int, //RequestSize: integer
char*, //FileName: PChar
char*, //ErrorBuf: PChar
int); //ErrorBufSize: integer);
typedef bool(__stdcall* f_DataToStream)(
char*, //AddressName: PChar
char*, //Request: PChar
int, //RequestSize: integer
std::istream &, //ResultStream: IStream
char*, //ErrorBuf: PChar
int); //ErrorBufSize: integer);
...
Get funct. address:
//load DataToFile
f_DataToFile DataToFile = (f_DataToFile) GetProcAddress(hLib, "DataToFile");
if (!DataToFile) { //throw error
}
//load DataToStream
f_DataToStream DataToStream = (f_DataToStream) GetProcAddress(hLib, "DataToStream");
if (!DataToStream) { //throw error
}
...
Set data:
char* AddressName = _strdup("127.0.0.1:1234"); //AddressName: PChar
char* Request = _strdup("<?xml request... >"); //Request: PChar
int RequestSize = strlen(Request); //RequestSize: integer
char* ResultFile = _strdup("exportpath\\output.xml"); //FileName: PChar
char* ErrorBuf = new char[255]; //ErrorBuf: PChar
int ErrorBufSize = 255; //ErrorBufSize: integer);
std::filebuf(buffer);
std::istream ResultStream(&buffer);
...
First function working correctly
bool reesult1= (DataToFile)(AddressName, Request, RequestSize, ResultFile, ErrorBuf, ErrorBufSize);
...
I have problems with second function execution -
bool reesult2= (DataToStream)(AddressName, Request, RequestSize, ResultStream, ErrorBuf, ErrorBufSize);
It is compiling, but gives Access Violoation on run.
Can someone help me to get - how to correctly work with IStream data type from (Delphi)?
When i declare ResultStream as nullptr pointer and call DataToStream function with to incorrect connection address, function return "Connection error" - so it is imported correctly and main question is returning IStream from function.

Your translation of IStream is not correct. The DLL is using the COM IStream interface. You can't replace that with C++'s std::istream class. You need to use a COM object that implements the IStream interface. To access a file as an IStream, you can use the Win32 SHCreateStreamOnFileEx() function, for instance.

Thanks to Remy Lebeau!
HRESULT GetFileStream(
_In_ LPCWSTR fullFileName,
_Outptr_ IStream** stream)
{
HRESULT hr = S_OK;
// Create stream for writing the file
if (SUCCEEDED(hr))
{
hr = SHCreateStreamOnFileEx(
fullFileName,
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0, // default file attributes
TRUE, // create file if it does not exist
NULL, // no template
stream);
}
return hr;
}
in typedef:
IStream *, //ResultStream: IStream
..
LPCWSTR filename = L"path\\to\\file.txt";
IStream* ResultStream = NULL;
HRESULT hr = GetFileStream(filename, &ResultStream);
..
bool result = (CallRK7XMLRPCToStream)(AddressName, Request, RequestSize, ResultStream, ErrorBuf, ErrorBufSize);
here is a good example of using SHCreateStreamOnFileEx:
https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/AppxPackingCreateBundle/cpp/CreateBundle.cpp

Related

Import Delphi DLL function into C++ Builder , transfer PAnsiChar

I want to use Delphi code , export via DLL from C++ Builder
Delphi Code fragment goes like this
// function declare
function NameData(ItemIndex: Integer;
Buffer: PAnsiChar; var BufSize: DWORD): DWORD; stdcall;
external 'database.dll'
// function calling code
s1, S2: AnsiString;
begin
for i := 1 to ... do
begin
BufSize := 0;
NameData(i, nil, BufSize);
SetLength(s1, BufSize);
NameData(i, PAnsiChar(s1), BufSize);
mmo_dll.lines.Add(' name -> ' + string(s1));
relevant DLL code
library DLLCode;
function NameData(ItemIndex: Integer;
Buffer: PAnsiChar; var BufSize: DWORD): DWORD; stdcall;
var
returnString: Ansistring;
begin
returnString := ' call some other functions .....';
if BufSize < Length(returnString) then
result := ERROR_BUFFER_TOO_SMALL
else
begin
StrPCopy(Buffer, returnString);
result := ERROR_NO_ERROR;
end;
BufSize := Length(returnString);
end;
this and a lot of more stuff works fine, Delphi and Delphi DLL.
Now here is my not working C++ code :
// function prototype
typedef void (__stdcall*IntCharIntIn_VoidOut)(int, PAnsiChar, int);
// DLL prototype
extern "C" __declspec(dllimport)
IntCharIntIn_VoidOut __stdcall NameData(int, PAnsiChar, int);
// instance declaration
IntCharIntIn_VoidOut NameData;
// load library data, no error raise, other simpler function call already working
........
NameData = (IntCharIntIn_VoidOut)::GetProcAddress(load,
"NameData");
/// calling code
int Bufsize;
PAnsiChar DataName;
for (i = 0; i < count - 1; i++) {
*Bufsize = 0;
NameData(i, NULL, Bufsize);
StrLen(SignalName);
NameData(i, DataName, Bufsize );
Memo1->Lines->Add(IntToStr(i)); // for test only
}
In the second call I get an access violation, but can't see why/where I'm wrong
You don't allocate any memory, and your function declaration is wrong.
The function really should be declared like so:
typedef void (__stdcall *IntCharIntIn_VoidOut)(int, char*, unsigned int*);
And your calling code should be:
unsigned int Bufsize;
char* DataName;
for (i = 0; i < count - 1; i++) {
Bufsize = 0;
NameData(i, NULL, &Bufsize);
DataName = new char[Bufsize + 1];
NameData(i, DataName, &Bufsize);
// do something with DataName
delete[] DataName;
}
I've omitted error checking on the memory allocation and deallocation. If it were me I would be using grown up C++ string objects and not raw memory. The loop looks like it misses the final iteration, should be <= count - 1 or < count surely. Your type name, IntCharIntIn_VoidOut fails to recognise that two of the arguments are pointers. I'm using char* rather than PAnsiChar, but I guess that the latter is just an alias to the former.
I'll leave all of the above for you to deal with.

SAFEARRAY(BSTR) as [out] parameter in DCOM fails to return strings

I want to write a DCOM server and a client so that they could exchange some data. Both sides are compiled using MS Visual Studio 2008, client connects using pure WinAPI, proxy/stub is a separate dll (in case anything of this matters). The problem is, I can't return an array of strings (it's filled properly yet client receives an array of empty strings).
Server: IDL declaration of COM interface has this method:
[id(7)] HRESULT foo([in] int arg1, [out] SAFEARRAY(int)* arg2, [out] SAFEARRAY(BSTR)* arg3);
Implementation, with header as generated by Studio:
HRESULT STDMETHODCALLTYPE CoClass::foo(int arg1, SAFEARRAY** arg2, SAFEARRAY** arg3){
SAFEARRAYBOUND bounds;
bounds.cElements = arg1;
bounds.lBound = 0;
*arg2 = SafeArrayCreate(VT_INT, 1, &bounds);
*arg3 = SafeArrayCreate(VT_BSTR, 1, &bounds);
for(LONG i=0; i<arg1; ++i){
int int_value = 42;
BSTR string_value = SysAllocString(L"Hello");
//string_value is correct here
SafeArrayPutElement(*arg2, &i, &int_value);
SafeArrayPutElement(*arg3, &i, &string_value);
//string_value isn't destroyed here (explicitly, at least)
}
return ERROR_SUCCESS;
}
Client: Included Studio-generated header:
virtual /* [id] */ HRESULT STDMETHODCALLTYPE foo(
/* [in] */ int arg1,
/* [out] */ SAFEARRAY * *arg2,
/* [out] */ SAFEARRAY * *arg3) = 0;
Caller code (pInterface is properly initialized, other calls are successful):
SAFEARRAY *pInts = NULL, *pStrings = NULL;
HRESULT error = pInterface->foo(23, &pInts, &pStrings);
// in debugger:
// error is ERROR_SUCCESS, pInts filled properly,
// pStrings is an array of 23 NULLs
Other details:
There is no other method with ID 7 in IDL file;
Using [out] BSTR *str works, the string is returned properly;
pInterface is recieved from CoCreateInstanceEx call;
There is no older version of the server on the system;
The code is to be run on Windows XP without some updates, so using Visual Studio 2008 is a constraint that's hard to bypass.
Does anyone have an idea what I'm doing wrong?
The answer was provided by Hans Passant in comments section.
The answer is: syntax of adding elements to SAFEARRAY is different for int and BSTR:
// SAFEARRAY **intArray, **stringArray; LONG i;
int int_value = 42;
BSTR string_value = SysAllocString(L"Hello");
SafeArrayPutElement(*intArray, &i, &int_value);
//SafeArrayPutElement(*stringArray, &i, &string_value); //WRONG!
SafeArrayPutElement(*stringArray, &i, string_value); //Right
Note that syntax for reading is the same:
// SAFEARRAY *intArray, *stringArray; LONG i;
int int_value;
BSTR string_value;
SafeArrayGetElement(intArray, &i, &int_value);
SafeArrayGetElement(stringArray, &i, &string_value);

C++ chars stored in the LPCSTR .. broken?

LPCSTR dllPath = ExePath().append("\\").append(DEF_INJECT_DLL).c_str();
DWORD dwBufSize = (DWORD)(strlen(dllPath) + 1) * sizeof(LPCSTR);
/* test */
char tbuf[1024]= {0,};
sprintf_s(tbuf, "dllPath : %s\r\ndwBufSize : %d", dllPath, dwBufSize);
MessageBoxA(NULL, tbuf, "TEST", MB_OK);
part of the code to inject my dll.
ExePath() is a function to get AbsolutePath of std::string data type using GetModuleFileNameA API and so on.
DEF_INJECT_DLL is defined by #define "MyDll.dll"
But when I run this code, it shows me broken strings....
And, when I change the MessageBoxA to this:
MessageBoxA(NULL,
ExePath().append("\\").append(DEF_INJECT_DLL).c_str(),
"TEST",
MB_OK);
it shows properly?
Also, I tried in this way:
MessageBoxA(NULL,dllPath, "TEST", MB_OK);
but it shows to me like first screenshot.
What is the problem?
The problem is in this line:
LPCSTR dllPath = ExePath().append("\\").append(DEF_INJECT_DLL).c_str();
here you call ExePath(), which returns a std::string instance, modify it, and finally call c_str() to get the raw data buffer.
However, the return value is a temporary object. After that line, the returned std::string is deleted, and will clean its memory. Therefore, the address where dllPath points to is no longer valid!
You could store the return value in a local instance, e.g.
std::string str = ExePath().append("\\").append(DEF_INJECT_DLL);
LPCSTR dllPath = str.c_str();

How to convert BSTR to std::string in Visual Studio C++ 2010?

I am working on a COM dll. I wish to convert a BSTR to a std::string to pass to a method that takes a const reference parameter.
It seems that using _com_util::ConvertBSTRToString() to get the char* equivalent of the BSTR is an appropriate way to do so. However, the API documentation is sparse, and the implementation is potentially buggy:
http://msdn.microsoft.com/en-us/library/ewezf1f6(v=vs.100).aspx
http://www.codeproject.com/Articles/1969/BUG-in-_com_util-ConvertStringToBSTR-and-_com_util
Example:
#include <comutil.h>
#include <string>
void Example(const std::string& Str) {}
int main()
{
BSTR BStr = SysAllocString("Test");
char* CharStr = _com_util::ConvertBSTRToString(BStr);
if(CharStr != NULL)
{
std::string StdStr(CharStr);
Example(StdStr);
delete[] CharStr;
}
SysFreeString(BStr);
}
What are the pros and cons of alternatives to using ConvertBSTRToString(), preferrably based on standard methods and classes?
You can do this yourself. I prefer to convert into the target std::string if possible. If not, use a temp-value override.
// convert a BSTR to a std::string.
std::string& BstrToStdString(const BSTR bstr, std::string& dst, int cp = CP_UTF8)
{
if (!bstr)
{
// define NULL functionality. I just clear the target.
dst.clear();
return dst;
}
// request content length in single-chars through a terminating
// nullchar in the BSTR. note: BSTR's support imbedded nullchars,
// so this will only convert through the first nullchar.
int res = WideCharToMultiByte(cp, 0, bstr, -1, NULL, 0, NULL, NULL);
if (res > 0)
{
dst.resize(res);
WideCharToMultiByte(cp, 0, bstr, -1, &dst[0], res, NULL, NULL);
}
else
{ // no content. clear target
dst.clear();
}
return dst;
}
// conversion with temp.
std::string BstrToStdString(BSTR bstr, int cp = CP_UTF8)
{
std::string str;
BstrToStdString(bstr, str, cp);
return str;
}
Invoke as:
BSTR bstr = SysAllocString(L"Test Data String")
std::string str;
// convert directly into str-allocated buffer.
BstrToStdString(bstr, str);
// or by-temp-val conversion
std::string str2 = BstrToStdString(bstr);
// release BSTR when finished
SysFreeString(bstr);
Something like that, anyway.
Easy way
BSTR => CStringW => CW2A => std::string.

Dumping Useful Data to Console on Caught Exception

I have a CExceptionHandler class that is invoked whenever my application detects a run-time exception. For example:
if ( val == NULL )
{
TRACE(_T("Unexpected NULL in sequence."));
AfxThrowException( ERROR_INVALID_DATA );
}
AfxThrowException is very simple:
void AfxThrowException( DWORD error )
{
CExceptionHandler * pException = NULL;
if ( error == 0 )
{
error = ::GetLastError();
}
pException = new CExceptionHandler( error );
TRACE(_T("Warning: throwing CSerialException for error %d\n"), error);
THROW(pException);
}
This is the member Dump function of CExceptionHandler:
void CExceptionHandler::Dump( CDumpContext & dc ) const
{
CObject::Dump(dc);
dc << "m_dwError = " << m_dwError;
}
Higher up in my code I have the try/catch statements:
try
{
/* My app does stuff where the exception is thrown. */
}
catch( CExceptionHandler * ex )
{
afxDump << _T("Dumping exceptional data: ") << _T("\r\n");
ex->Dump( afxDump );
afxDump << _T("\r\n");
}
I would like the collected debug information to be dumped to the console. However, when the PC gets into the catch statement (verified with breakpoint), nothing happens on the console. I am using Visual Studio 2008 in Debug mode. Thoughts are appreciated. Thanks.
CDumpContext sends output to the debugger, not to the console (see OutputDebugString for more information), and if you run under the Visual Studio debugger, the output will appear in the Output window.
If you want to also send output to the console, you can configure CDumpContext to write to a CFile by passing a pointer to a CFile object in the CDumpContext constructor.
If your application is built with the 'Use Multi-Byte Character Set' configuration option, you can use CStdioFile to write to either stdout or stderr:
CStdioFile file(stdout);
CDumpContext dumpContext(&file);
ex->Dump(dumpContext);
Or, if you want to use afxDump, you can set its m_pFile member variable directly (it's declared public). For example you could put this code inside your main function:
CStdioFile file(stdout);
afxDump.m_pFile = &file;
But, this won't work if your app is built as Unicode because strings need to be converted to multi-byte to be written to stdout. To do the conversion, write a class that inherits CFile:
class CDumpFile : public CFile
{
public:
virtual void Write(const void* lpBuf, UINT nCount);
};
void CDumpFile::Write(const void* lpBuf, UINT nCount)
{
// Construct string from array of wide chars
const wchar_t* p = static_cast<const wchar_t*>(lpBuf);
UINT len = nCount / sizeof(wchar_t);
CStringW str(p, len);
CStringA astr(str); // Convert wide char to multibyte
fputs((LPCSTR)astr, stdout); // Write multibyte string to stdout
}
and use it like this:
CDumpFile file;
afxDump.m_pFile = &file;
A couple of other points about your code:
You should ensure that the exception object is deleted in the catch block. If your CExceptionHandler class inherits MFC's CException then you should call ex->Delete(). If it doesn't, you need to delete ex.
I'd advise against using the Afx prefix for own functions - you should consider this reserved for use by the MFC library, in my opinion.
I hope this helps!