Link to home
Start Free TrialLog in
Avatar of PacificaResearch
PacificaResearch

asked on

Class instantiation blowup

I can create this class without error in a console .EXE, and it works fine. The class is in a .DLL called by the console app.

When I compile the app as a .DLL, with no other changes at all, the class can't be instantiated, and blows up with Invalid Access deep in Windows.

The app is native C++ without /clr. The .DLL containing the class is C++ with /clr.

In both cases, everything compiles without error.

Here's the code:

    YahooAPIWrapper yahoo;    // <--- this line blows up.



The class is declared in a separate .DLL as:

class YahooAPIWrapperPrivate;

class YahooAPIWrapper
{
    private: YahooAPIWrapperPrivate* _private;

    public: YahooAPIWrapper();
   
    public: double GetBid(const char* symbol);

    public: double GetAsk(const char* symbol);
   
    public: const char* GetCapitalization(const char* symbol);
   
    public: const char** GetValues(const char* symbol, const char* fields);

    public: ~YahooAPIWrapper();
};

How could there be any difference between a console app and .DLL in the way they initialize a class? There must be something wrong in my setup, but I can't see what it might be, the code is so simple.
Avatar of Zoppo
Zoppo
Flag of Germany image

Hi PacificaResearch,

could you post the implementation of the constructor YahooAPIWrapper::YahooAPIWrapper()?

Where do you instantiate the object? If it's i.e. in a DllMain there may be some reasons for a failure.

BTW: I'm not sure if you know it, but you don't need to add a 'public:' for every function, it's ok to just write it once to mark all following members/functions as public, i.e.:
class YahooAPIWrapper
{
    // default is 'private'
    YahooAPIWrapperPrivate* _private;

    public:
        YahooAPIWrapper();
   
        double GetBid(const char* symbol);

        double GetAsk(const char* symbol);
   
        const char* GetCapitalization(const char* symbol);
   
        const char** GetValues(const char* symbol, const char* fields);

        ~YahooAPIWrapper();
};

Open in new window

ZOPPO
Avatar of PacificaResearch
PacificaResearch

ASKER

Thank you for the advice. I don't have a lot of experience with classes. This is sample code I downloaded.

Here's the main program. It works when compiled as console, fails when compiled as .DLL.

// yahoo_look.cpp
//
#include "..\YahooAPIWrapper.h"
extern "C"
{
int yahoo_lookup();
}

int yahoo_lookup()
//int main()
{
const char *bid,*ask,*cap;

    YahooAPIWrapper yahoo;    // <-- this line blows up.
    
    const char* stock = "MSFT";
    const char** bidAskCapi = yahoo.GetValues(stock, "b3b2j1");
    bid = bidAskCapi[0];
    ask = bidAskCapi[1];
    cap = bidAskCapi[2];
	return (0);
}

Open in new window



Here's the code containing the constructor and all methods. It's C++ with /clr, compiled as a .DLL:

#using "YahooAPI.dll"

#include <msclr\auto_gcroot.h>

using namespace System::Runtime::InteropServices; // Marshal

class YahooAPIWrapperPrivate
{
    public: msclr::auto_gcroot<YahooAPI^> yahooAPI;
};

class __declspec(dllexport) YahooAPIWrapper
{
    private: YahooAPIWrapperPrivate* _private;

    public: YahooAPIWrapper()
    {
        _private = new YahooAPIWrapperPrivate();
        _private->yahooAPI = gcnew YahooAPI();
    }
    
    public: double GetBid(const char* symbol)
    {
        return _private->yahooAPI->GetBid(gcnew System::String(symbol));
    }

    public: double GetAsk(const char* symbol)
    {
        return _private->yahooAPI->GetAsk(gcnew System::String(symbol));
    }
    
    public: const char* GetCapitalization(const char* symbol)
    {
        System::String^ managedCapi = _private->yahooAPI->GetCapitalization(gcnew System::String(symbol));
    
        return (const char*)Marshal::StringToHGlobalAnsi(managedCapi).ToPointer();
    }
    
    public: const char** GetValues(const char* symbol, const char* fields)
    {
        cli::array<System::String^>^ managedValues = _private->yahooAPI->GetValues(gcnew System::String(symbol), gcnew System::String(fields));
        
        const char** unmanagedValues = new const char*[managedValues->Length];
        
        for (int i = 0; i < managedValues->Length; ++i)
        {
            unmanagedValues[i] = (const char*)Marshal::StringToHGlobalAnsi(managedValues[i]).ToPointer();
        }
        
        return unmanagedValues;
    }
    
    public: ~YahooAPIWrapper()
    {
        delete _private;
    }
};

Open in new window




Here's the C# code called by the code above, compiled for .NET 4.

using System.Net; // WebClient
using System.Globalization; // CultureInfo

public class YahooAPI
{
    private static readonly WebClient webClient = new WebClient();

    private const string UrlTemplate = "http://finance.yahoo.com/d/quotes.csv?s={0}&f={1}";

    private static double ParseDouble(string value)
    {
         return double.Parse(value.Trim(), CultureInfo.InvariantCulture);
    }
    
    private static string[] GetDataFromYahoo(string symbol, string fields)
    {
        string request = string.Format(UrlTemplate, symbol, fields);

        string rawData = webClient.DownloadString(request).Trim();
        
        return rawData.Split(',');
    }

    public double GetBid(string symbol)
    {
        return ParseDouble(GetDataFromYahoo(symbol, "b3")[0]);
    }

    public double GetAsk(string symbol)
    {
        return ParseDouble(GetDataFromYahoo(symbol, "b2")[0]);
    }
    
    public string GetCapitalization(string symbol)
    {
        return GetDataFromYahoo(symbol, "j1")[0];
    }
    
    public string[] GetValues(string symbol, string fields)
    {
        return GetDataFromYahoo(symbol, fields);
    }
}

Open in new window

Hm, sorry, I'm not very familiar with mixing managed and none-managed C++/C# code this way. I don't see any obvious problem, but I'm not sure how this works at all because if I understand it correctly the DLL is used as if it is implicit linked, but the class in the header always is declared as exported with class __declspec(dllexport) YahooAPIWrapper. IMO in the EXE where you want to use this class from the DLL the directive has to be class __declspec(dllimport) YahooAPIWrapper in order the functions can be linked at runtime.

Usually this is done via preprocessor macros. For this only in the DLL a symbol is defined (i.e. MYDLL_EXPORT) and the class is declared like this:
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif

class MYDLL_API MyClass
{
 ...
};

Open in new window

IMO without this the first code can't compile (or, better said, linking should fail), so as told I don't understand how this can work at all.

ZOPPO
ASKER CERTIFIED SOLUTION
Avatar of PacificaResearch
PacificaResearch

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
No expert offered helpful advice.