Avatar of Randwro
Randwro
 asked on

Getting A String from unmanaged C++ to VB.NET

I am trying to obtain the HTML source from the current instance of Internet Explorer to a VB.NET app.

 I have working unmanaged C++ code (with Win32 calls) in VS.NET 2003 that does obtain the current instance of IE as a CComQIPtr<IWebBrowser2> and can extract its source HTML.  So, I'm trying to get that string to my VB.NET app. I'm OK on VB.NET but weak on C++.

I'm aware of that there are reasons not to just pass a string from C++ to VB.NET.  Unless someone has a better approach (I doubt I want to write a wrapper class as the C++ is intricate, and  I tried passsing the WebBrowser object, but it won't marshal, no layout, perhaps the HTML doc would?), I'm passing in a StringBuilder argument to the C++ function as a BSTR to allow it to set the "preallocated" string.

I am successfully calling the C++ function, as I get a valid boolean return value, but the content of the string is unchanged. I have the 'Use Managed Extensions' as Yes, and copy the C++ DLL to system32.  I suspect I need to label the argument as in & out, and/or designate the argument as a pointer of some type.  I can supply full source code if needed.  I'm aware that with this approach for a large webpage I will need to allocate a large StringBuilder space, but that seems like the least of my worries.

Like I said, I'm open to other easier approaches (Temp file? - really?), but not embedding the browser in VB.NET - I've done that, but want all the native IE features: favorites, history, etc.
Thanks,
- Rand W.

VB.NET Code:
---------------
   <DllImport("Automation.dll")> Public Shared Function GetAString(ByRef PassString As StringBuilder) As Boolean '  NOTE: I have tried ByVal as well
    End Function

Sub GetDataFromIE()

        Dim sStr = New StringBuilder("PREINPUT", 1024)
        bResult = GetAString(sStr) ' Get a good True result
        TextBox1.Text = sStr.ToString
End Sub

C++ Code: ( i have a .DEF to list GetAString as an entry point)
=======
AutomationDlg.h
-------------------
 class CAutomationDlg : public CDialog
{
public:      BOOL __stdcall CAutomationDlg::GetAString(BSTR PassString);
... }


AutomationDlg.cpp
-------------------
// RW ADDED: for Main DLL entry point,  Needed ??
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved                               )
{
    return TRUE;
}

// ... unmanaged code ....

#using <mscorlib.dll>
BOOL      __stdcall CAutomationDlg::GetAString(BSTR PassString)
{
PassString = SysAllocString (L"This is the test string.");  // If S, I get an error C2664: cannot convert from System::String__gc to const OLECHAR *
return TRUE;
}
Visual C++.NET

Avatar of undefined
Last Comment
Randwro

8/22/2022 - Mon
AlexFM

Try this:

<DllImport("Automation.dll")> Public Shared Function GetAString((<MarshalAs(UnmanagedType.LPWStr)> ByVal PassString As StringBuilder) As Boolean

bool   __stdcall CAutomationDlg::GetAString(WCHAR* PassString)
{
     wcscpy(PassString, L"output");
     return true;
}

If it works, replace wcscpy with actual implementation.
I made the following changes:
1) Return type of unmanaged function is bool and not BOOL. BOOL is 32 bits, and Boolean is one byte - only this mistake can crash the program. In my code I avoid using booleans for interoperability and use int which is always 32 bits both in .NET and C++.
2) StringBuilder must be passed ByVal. Passing it ByRef, you actually pass pointer to StringBuilder itself. ByVal passes pointer to internal string buffer.
3) String is explicitly defined as Uicode string: LPWStr.
4) WCHAR* PassString parameter can be used by unmanaged function as pointer to buffer which can be filled.
AlexFM

Oops, I didn't see this before:

class CAutomationDlg : public CDialog
{
public:     BOOL __stdcall CAutomationDlg::GetAString(BSTR PassString);
...

You CANNOT call unmanaged C++ class functions using PInvoke. PInvoke can be used only for API calls: function must be exported from Dll.
Randwro

ASKER
Alex,
Thanks for the quick response. I implemented your code, and had similiar results: the first invocation did not replace the string from the perspective of VB.NET. Curiously, the second invocation during the same debug session caused an error: ...of type 'System.NullReferenceException' occurred in WindowsApplication1.exe.

The GetAString( ) function is being exported (supported by dumpbin results), and the return value becoming True in VB.NET seem to indicate it is being called.  So,does this satisfy your last comment regarding using PInvoke? We are not referencing the function as a method call, yes? Or is there another definition of GetAString that would be preferable?  I'll go ahead and set it up for an int return.

ODL specification for in and  out wouldn't be needed would it? I do not have an odl file.
----------
Actual code:

bool __stdcall CAutomationDlg::GetAString(WCHAR* PassString);  // .h

bool   __stdcall CAutomationDlg::GetAString(WCHAR* PassString)  // .cpp
{
     wcscpy(PassString, L"output");
     return true;
}
------------
    <DllImport("Automation.dll")> Public Shared Function GetAString(<MarshalAs(UnmanagedType.LPWStr)> ByVal PassString As StringBuilder) As Boolean
    End Function

        Dim sStr = New StringBuilder("TESTING", 1024)
       bResult = GetAString(sStr)
       TextBox1.Text = sStr.ToString




This is the best money I have ever spent. I cannot not tell you how many times these folks have saved my bacon. I learn so much from the contributors.
rwheeler23
AlexFM

Try to do the same with global exported function. I still don't understand how can you call class member function.
Randwro

ASKER
The revision to return an int was successful in returning an arbitrary number, IMHO confirming a good function call. At the same time, I get this warning:

    LINK : warning LNK4243: DLL containing objects compiled with /clr is not linked with /NOENTRY; image may not run correctly

BTW: the DLL does not register as a COM object.
ASKER CERTIFIED SOLUTION
AlexFM

Log in or sign up to see answer
Become an EE member today7-DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform
Sign up - Free for 7 days
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.
Not exactly the question you had in mind?
Sign up for an EE membership and get your own personalized solution. With an EE membership, you can ask unlimited troubleshooting, research, or opinion questions.
ask a question
Randwro

ASKER
Alex,
Success!  Moving the function external to the class definition apparently did the trick, the passed string is changed. As a class function called externally,  I was getting the arbitrary int 111 value returned, but no operation against the string parameter - rather curious.  But following da rules works.  You hit the nail on the head. What was that Clarke quote about sufficiently advanced science appearing as magic? Thanks for getting the bytes lined up and connecting my two dots.
- Rand W.
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.