Search code examples
c++comc++buildervclbstr

Using a COM-DLL with BSTR* parameters --> Float Div By Zero error


Currently, I'm developing a mid-sized in-house application, which should include some features that are already included in a previous application. This has been developed in Visual Basic 6 many years ago by an external supplier. Unfortunately I don't have access to the source code, but fortunately, the necessary classes are contained in a COM DLL, which was also created in VB6. The currently used development environment is C++ Builder 10.1. I can easily install the DLL via regsvr32.exe. The type library import function in CBuilder has generated a nice VCL wrapper based on TOleServer. The DLL is quite huge, and after the import there was already a problem with a missing function overload, which I had to add by hand. Otherwise, the wrapper seems to work.

Now to the real problem:

The DLL contains a class whose fields can be loaded from an XML file by a method called Load. This function has 2 parameters: File As String and Schema As String in VB6 and VBA (checked with Excel 2010). The parameter Schema is optional. If I now create a class object in VBA, I can pass a filename string as the File param and the class fields are loaded without problems. If I look at the generated wrapper class in CBuilder, the Load function has two parameters of the "BSTR *" type. The Schema parameter is marked as optional in the automatically generated comment, but has not assigned a default value and is therefore not optional (?). So I have to use the parameter Schema. Strangely, a pointer to BSTR is expected instead of BSTR. So I tried the following:

BSTR File = SysAllocString(L"C:\\temp\\file.xml");
BSTR Schema = SysAllocString(L"");

TMyOleClass *MyClass = new TMyOleClass(this);

MyClass->Load(&File, &Schema);

Since schema files are not normally used, I have none. Therefore it's a zero string.

The class is created without errors, but the Load function throws a Float Divide By Zero error in MSVBVM60.dll. Other class functions that require a normal BSTR (not a pointer to it) work without problems.

So...

  • Why is the second parameter not optional in the generated VCL wrapper?
  • Why are the parameters of type BSTR* and not of type BSTR, like the others?
  • Why is there the said error?

Thanks for all answers.


EDIT:

I've opened the class with oleview.exe. The function Load has the following definition there:

HRESULT Load(
            [in, out] BSTR* Filepath, 
            [in, out, optional] BSTR* Schema, 
            [out, retval] VARIANT* );

albeit in the Excel VBA Object Catalog only

Function Load(Filepath As String, [Schema As String])

is shown (no references).

The idea to set the schema Parameter to NULL didn't work. The same div error came up.

Thanks to @Remy Lebeau, I created a variant and tried it again:

BSTR Path = SysAllocString(L"C:\\temp\\file.xml");
VARIANT varOpt;

varOpt.vt = VT_ERROR;
varOpt.scode = DISP_E_PARAMNOTFOUND;

TC_MyClass *MyClass = new TC_MyClass(this);

MyClass->Load(&Path, (BSTR*)&varOpt);

--> same error.

I've discovered, that if skipping the Exceptions with Continue for 3 times, an error comes up saying: This array is fixed or temporarily locked.

Below are some Pictures;

The error (German BCB)

The code line in utilcls.h after that the error is being raised


Here are some parts of the CBuilder generated VCL-Wrapper:

class PACKAGE TC_MyClass : public Vcl::Oleserver::TOleServer
{
   _C_MyClassPtr m_DefaultIntf;
   _di_IUnknown __fastcall GetDunk();
public:
  __fastcall TC_MyClass(System::Classes::TComponent* owner) : Vcl::Oleserver::TOleServer(owner)
 {}

...  

VARIANT __fastcall Load(BSTR* Filepath/*[in,out]*/, BSTR* Schema/*[in,out,opt]*/)
{
    VARIANT Param3;
    OLECHECK(GetDefaultInterface()->Load(Filepath, Schema, (VARIANT*)&Param3));
    return Param3;
}  

...

@Remy Lebeau: The function I had to change manually was

HRESULT __fastcall  set_Sections(MyHugeAndComplex_dll_tlb::_E_SectionsPtr* Param1/*[in,out]*/)
{
    return set_Sections((MyHugeAndComplex_dll_tlb::_E_Sections*)Param1/*[in,out]*/);
}

where I had to add a second implementation

HRESULT __fastcall  set_Sections(MyHugeAndComplex_dll_tlb::_E_SectionsPtr** Param1/*[in,out]*/)
{
    return set_Sections(Param1/*[in,out]*/);
}

and also I had to add it for a second function:

HRESULT __fastcall  set_Document(Msxml2_tlb::IXMLDOMDocument2Ptr* Param1/*[in,out]*/)
{
    return set_Document((Msxml2_tlb::IXMLDOMDocument2*)Param1/*[in,out]*/);
}

EDIT 2:

I've created a VI in LabVIEW which creates an instance of the class and which can call the 'Load' function without problems.

Now, when wrapping this created VI itself within a new Win32 DLL with an exported wrapper function call 'Load' and opening this DLL by code in CBuilder by a 'SafeLoadLibrary' call, the same floating point error occurs, even when I hard code the path to the XML in the LabVIEW-VI. I can call the 'Load' function from nearly everywhere, except for C++ Builder. Thinking about, if maybe it's a bug in CBuilder and not my fault...

String DLL_FileName = ExtractFilePath(Application->ExeName) + "MyNewCreatedDLLWrapper.dll";
HINSTANCE hInstance = (HINSTANCE)SafeLoadLibrary(DLL_FileName.w_str());

if(!hInstance)
    throw(Exception("Error loading DLL"));

Load = (Load_Ptr)GetProcAddress(hInstance,"Load");

int32_t Length = 1000;

uint8_t Array[1000];
int32_t RetVal = Load("", Array, &Length); // <-- path can be omitted as it is hard coded in DLL

FreeLibrary(hInstance);

Solution

  • So....I finally got it up and running, thanks to z32a7ul's proposal to set the 2nd Parameter to NULL and Remy Lebeau's advice of doing a check on the manually edited method. It seems, that this edited method itself is being called by the Load function. It was my fault in thinking of a missing overload where it was just needed to change a parameter def to a pointer type. Finally, I had to set the second param to NULL as mentioned above, but indirectly:

    BSTR Path = SysAllocString(L"C:\\temp\\file.xml");
    BSTR Schema = NULL;
    
    TC_MyClass *MyClass = new TC_MyClass(this);
    
    MyClass->Load((BSTR*)&Path, (BSTR*)&Schema);
    

    Thanks for your answers.