DLL not exporting string in visual studio 2013 VB code - c++

I'm writing a small application in VB.NET in Visual Studio 2013, and so far what I've written is the following code:
Public Class MainMenu
Private Declare Function Version_Get Lib "mypath/mydll.dll" () As String
' Before anyone asks, yes, the DLL is present in the mypath folder
Private Sub MainMenu_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim Temp As String
Dim Dummy As Integer
On Error GoTo Handler
Temp = Version_Get
Dummy = 1 ' This line is never reached in debug mode
Handler:
If Not IsNothing(Err.GetException()) Then
MsgBox("Error " & Str(Err.Number) & " generated by the application " & Err.Source & ControlChars.CrLf & Err.Description, vbCritical, "Error")
End
End If
End Sub
End Class
Where the DLL is supposed to return a BSTR (this is of course extern "C" etc., but for the sake of comprehension I'm writing the function pure and simple):
File .h
#ifdef EVALFUNC_EXPORTS
#define EVALFUNC_API __declspec(dllexport)
#else
#define EVALFUNC_API __declspec(dllimport)
#endif
extern "C"
{
EVALFUNC_API BSTR __stdcall Version_Get();
}
File .cpp
BSTR __stdcall Version_Get()
{
CRegKey Key;
CString sValue;
BSTR Str;
LONG nA = Key.Open(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\my Software"), KEY_READ);
// Before anyone asks, yes, the application is present in the system
ULONG nValueLength = 0;
LONG nB = Key.QueryStringValue(_T("Version"), NULL, &nValueLength);
if (nValueLength > 0) LONG nC = Key.QueryStringValue(_T("Version"), sValue.GetBufferSetLength(nValueLength - 1), &nValueLength);
Str = sValue.AllocSysString();
return Str;
}
The problem is that even if I wrote an error handler, the code encounters no error, and Version_Get makes my code crash without any error (the MainMenu form gets loaded anyway).
I tried the DLL on another VB environment (Excel) with the same declaration.
Private Declare Function Version_Get Lib "mypath/mydll.dll" () As String
In that case the string variable is filled with the correct text.
What am I doing wrong?

I had to create a class to import the Dll by marshalling the BSTR String:
Imports System.Runtime.InteropServices
Public Class ImportDll
<DllImport("myDll.dll", SetLastError:=True, CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.StdCall)> _
Public Shared Function Version_Get() As <MarshalAs(UnmanagedType.BStr)> String
End Function
End Class
And could call the code as follows:
Version = ImportDll.Version_Get

Related

Access violation when invoking a C++ DLL from Delphi

I write a Unicode DLL in Visual C++ 6.0. Then try to invoke the DLL function from Delphi XE3.
When I debug in Delphi, when step over the line to invoke the DLL function, I will always get an Access violation exception.
However, when I debug in Visual C++, I can see all parameters passed from Delphi are correct and I can step over all codelines without any exceptions.
If running outside the debugger, then I will not see any "access violation exceptions.
I try many methods but still cannot figure out how to eliminate the exception when debuggin in Delphi.
Below is the code in Visual C++ 6.0 part:
TestDLL.cpp:
extern "C" VOID WINAPI Test(CONST MESSAGEPROC lpMessageProc, LPVOID lParam)
{
if (lpMessageProc != NULL)
(*lpMessageProc)(1500, (const LPVOID)(LPCTSTR)CString((LPCSTR)IDS_MYTEST), lParam);
/*
if (lpMessageProc != NULL)
(*lpMessageProc)(1500, (const LPVOID)(LPCTSTR)CString(_T("Test")), lParam);*/
}
TestDLL.h:
// TestDLL.h : main header file for the TESTDLL DLL
//
#if !defined(AFX_TESTDLL_H__38054A53_5CEE_4ABF_9BA8_BCE427FCB8E1__INCLUDED_)
#define AFX_TESTDLL_H__38054A53_5CEE_4ABF_9BA8_BCE427FCB8E1__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif
#include "resource.h" // main symbols
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef BOOL (CALLBACK* MESSAGEPROC)(CONST DWORD dwMessageId, CONST LPVOID lp, LPVOID lParam);
VOID WINAPI Test(CONST MESSAGEPROC lpMessageProc, LPVOID lParam);
#ifdef __cplusplus
}
#endif
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_TESTDLL_H__38054A53_5CEE_4ABF_9BA8_BCE427FCB8E1__INCLUDED_)
Below is the codes in Delphi XE3 part:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
public
{ Public declarations }
end;
PForm1 = ^TForm1;
TMessageProc = function (const dwMessageId: DWORD; const lp: Pointer; lParam: Pointer): BOOL; stdcall;
{$EXTERNALSYM TMessageProc}
var
Form1: TForm1;
procedure Test(const lpMessageProc: TMessageProc; lParam: Pointer); stdcall;
implementation
{$R *.dfm}
procedure Test; external 'TestDLL.dll' index 2;
function MessageProc(const dwMessageId: DWORD; const lp: Pointer; lParam: Pointer): BOOL; stdcall;
begin
Result := True;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Test(MessageProc, #Self); // <---- This code line will cause "access violation
end;
I belive the problem occurs in DLL test function, when it try to load string from resource using CString((LPCSTR)IDS_MYTEST). If I change the code to CString(_T("Test")), then problem disappears.
Thanks
As you surmised, this statement will not work:
CString((LPCSTR)IDS_MYTEST)
Although this constructor of CString does allow you to pass it a resource ID, it will try to find the resource in the calling process's (ie, the Delphi EXE's) resources, not in the DLL's resources. You need to use the HINSTANCE of the DLL, as provided by the DLL's DllMain(), when loading strings from the DLL's resources. You can use the CString::LoadString() method for that, eg:
HINSTANCE hInst;
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
hInst = hinstDLL;
return TRUE;
}
extern "C" VOID WINAPI Test(CONST MESSAGEPROC lpMessageProc, LPVOID lParam)
{
if (lpMessageProc != NULL)
{
CString str;
str.LoadString(hInst, IDS_MYTEST);
(*lpMessageProc)(1500, (LPCTSTR)str, lParam);
}
}
I finally figure out this is a bug of MFC codes(VC6.0 version).
I don't know if I can post MFC source codes so I will just paste the function headers and related parts only.
In Microsoft Visual Studio\VC98\MFC\SRC\STRCORE.CPP, we can see the following 3 functions:
//////////////////////////////////////////////////////////////////////////////
// More sophisticated construction
CString::CString(LPCTSTR lpsz) // Function 1
{
Init();
if (lpsz != NULL && HIWORD(lpsz) == NULL)
{
UINT nID = LOWORD((DWORD)lpsz);
if (!LoadString(nID))
TRACE1("Warning: implicit LoadString(%u) failed\n", nID);
}
else
{
// Construct string normally
}
}
/////////////////////////////////////////////////////////////////////////////
// Special conversion constructors
#ifdef _UNICODE
CString::CString(LPCSTR lpsz) // Function 2
{
// Construct string normally
}
#else //_UNICODE
CString::CString(LPCWSTR lpsz) // Function 3
{
// Construct string normally
}
#endif //!_UNICODE
As we can see in the above code snippet, only function 1 contains codes that will take special process on lpsz and check if it is a string resource ID, if yes, then load the string from the resource. Both function 2 & 3 have no such special processes.
When we create a project in VS6, the default settings for a project is _MBCS, in such a case, function 1 will become
CString::CString(LPCSTR lpsz)
so CString((LPCSTR)nResID) will actually invoke function 1 and load string resource properly.
Function 2 will be disabled since _UNICODE is not defined. And function 3 works with wide char strings.
Therefore, for _MBCS project, everything works perfectly and consistently with the MSDN document.
However, when I change _MBCS to _UNICODE, function 1 will become
CString::CString(LPCWSTR lpsz)
Fucntion 2 will be enabled and function 3 will be disabled.
So CString((LPCSTR)nResID) will actually invoke function 2, which does NOT have special process to load string resource, which makes the problem.
There are two solutions for this problem:
Always use CString((LPCTSTR)nResID) instead of CString((LPCSTR)nResID) to load a string from resource. However, this usage is inconsistent with MSDN document so we have to call it as an undocumented usage.
Always use LoadString to load a string resource.
Though solution 1 is a little simpler, it is an undocumented usage so I finally opt solution 2 to solve my problem.
Many thanks to all your helps in solving this issue.

Calling C dll from VB6, where the dll is written using Visual Studio 2013

This question is going to be poorly-posed, but I'm spinning my wheels big-time, and I'm not sure how to express it better.
I need to write a DLL, using C, which is to be called from VB6. I'm using Visual Studio Express 2013. I know...VB6 is ancient, and I think the managers of the code are now convinced they should junk it. But in the meantime, this needs to be done.
To start, I am trying to write a DLL with a single function that does nothing but print a message, and that works fine when calling the DLL using VB.NET.
Here's what I have for TinyDll.h.
#ifdef TINYDLL_EXPORTS
#define TINYDLL_API __declspec(dllexport)
#else
#define TINYDLL_API __declspec(dllimport)
#endif
extern TINYDLL_API void __stdcall testdll();
And here's TinyDll.cpp
#include "stdafx.h"
#include "TinyDll.h"
#include <stdexcept>
using namespace std;
void __stdcall testdll()
{
printf("Got into the dll.\n");
}
Incidentally, I've tried this with and without __stdcall. I also tried using a .def file to de-mangle the name in the dll, but it's not clear to me now that should work. The examples I've found indicate that this should work
LIBRARY TinyDll
Exports
testdll
but it does not. The name in the dll is still in mangled form: ?testdll##YGXXZ.
My test code is fairly trivial, and it works perfectly with VB.NET, but it won't work with VB6. The problem has something to do with the VB6 process not being able to find the dll and/or find the functions inside of it. For the record, here's the VB6 code being used to test
Declare Sub testdll Lib "TinyDll.dll" Alias "?testdll##YGXXZ" ()
Sub Main()
testdll()
End Sub
First, am I correct that it is not necessary to call regsvr32 or regasm? The dll is not a COM object -- VB6 is calling my C code, not the other way around. When we try to do either of these two things, either "the entry-point DllRegisterServer was not found" or "The specified module could not be found."
To top it off, the VB6 development environment is too old to run on my Windows 7 machine and the person who is trying to test my DLL is in another state and is strictly a VB programmer.
I've read everything I can find by googling, so I'm hoping that someone knows of a website that clearly lays out the facts or shows a working example.
As is so often the case with this kind of question (i.e., one involving a crusty old environment being used to do stuff it was never meant for), it now works, but it's not clear exactly why.
We think that without a full path to the dll, VB6 can't find it. If we declare the function with
Declare Function TestTinyDLL Lib "e:\full\path\down\to\TinyDll.dll" Alias "_testdll#0"
then the call works, but it doesn't work if the full path isn't given, no matter where the dll resides. We also found that we can avoid providing the full path by "initializing" the dll as follows (thanks to: File not found when loading dll from vb6 for this clue):
Option Explicit
' Windows API method declarations
Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function CallWindowProc Lib "user32" Alias _
"CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, _
ByVal msg As Any, ByVal wParam As Any, ByVal lParam As Any) _
As Long
Private Declare Function FormatMessage Lib "kernel32" Alias _
"FormatMessageA" (ByVal dwFlags As Long, lpSource As Long, _
ByVal dwMessageId As Long, ByVal dwLanguageId As Long, _
ByVal lpBuffer As String, ByVal nSize As Long, Arguments As Any) _
As Long
Declare Function BogusCall Lib "otherdll.dll" Alias "_BogusCall#0" () As Integer
Declare Function TestTinyDLL Lib "TinyDLL.dll" Alias "_testdll#0" () As Integer
Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Sub Main()
InitializeDLL App.Path & "\" & "otherdll.dll", "_BogusCall#0"
InitializeDLL App.Path & "\" & "TinyDLL.dll", "_testdll#0"
Dim Version As String
Version = "TinyDLLTestProgram" & vbCrLf & vbCrLf
Version = Version & "BogusCall = " & CStr(BogusCall ()) & vbCrLf
Version = Version & "TestTinyDLL= " & CStr(TestTinyDLL()) & vbCrLf
Form1.txtOutput.Text = CStr(Version)
End Sub
Sub InitializeDLL(myDLL As String, myFunc As String)
' Locate and load the DLL. This will run the DllMain method, if present
Dim dllHandle As Long
dllHandle = LoadLibrary(myDLL)
If dllHandle = 0 Then
MsgBox "Error loading DLL" & vbCrLf & ErrorText(Err.LastDllError)
Exit Sub
End If
' Find the procedure you want to call
Dim procAddress As Long
procAddress = GetProcAddress(dllHandle, myFunc)
If procAddress = 0 Then
MsgBox "Error getting procedure address" & vbCrLf & ErrorText(Err.LastDllError)
Exit Sub
End If
' Finally, call the procedure
CallWindowProc procAddress, 0&, "Dummy message", ByVal 0&, ByVal 0&
End Sub
' Gets the error message for a Windows error code
Private Function ErrorText(errorCode As Long) As String
Dim errorMessage As String
Dim result As Long
errorMessage = Space$(256)
result = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0&, errorCode, 0&, errorMessage, Len(errorMessage), 0&)
If result > 0 Then
ErrorText = Left$(errorMessage, result)
Else
ErrorText = "Unknown error"
End If
End Function
Why this works is a mystery, but it does. I hope this helps anyone else faced with old VB6 code!

Cannot use c++ dll in vb.net project

I want to call a c++ function from my vb.net project and i'm trying to create a dll to do so.I've never tried it before so according to the guides i read i created a dll.dll(using C++ in Visual Studio) with a dll.def file and i tried linking it to my VB project. Athough i can build it without any error it crushes and i get
'System.Runtime.InteropServices.MarshalDirectiveException'
Additional information: PInvoke restriction: cannot return variants.
My code is this:
dll.h
#define WDL_API __declspec(dllexport)
extern "C" WDL_API int __stdcall wdl(void);
dll.cpp
#include "stdafx.h"
#include "dll.h"
#include <stdio.h>
#include <windows.h>
char var[] = {"a"};
extern "C" WDL_API int __stdcall wdl(void)
{
int i, len1 = sizeof(var);
char sName[100], sAns[10];
FILE *ptr;
errno_t errorCode = fopen_s(&ptr, ".\\file", "wb");
for (i = 0; i<len1 - 1; i++)
fprintf(ptr, "%c", var[i]);
fclose(ptr);
return 0;
}
dll.def
LIBRARY dll
EXPORTS
wdl #1
vbproject
Module Module1
Public Declare Auto Function wdl _
Lib "dll.dll" Alias "wdl" ()
Sub Main()
Console.WriteLine("inside vb.net")
wdl()
End Sub
End Module
The code seems to make sense but i can't find out if i am missing something or there are mistakes of some kind.Any help would be much appreciated!
You did not specify the return type and so VB assumes that it is a variant. You don't want that. It is a C int, or VB Integer. Code it like this:
Public Declare Auto Function wdl Lib "dll.dll" Alias "wdl" () As Integer
That said, pinvoke is to be preferred over Declare these days so I would write it like this:
<DllImport("dll.dll")> _
Public Shared Function wdl() As Integer
End Function

Getting exception while using C++ dll in VB.Net

I have created unmanaged dll and used in VB.Net.
Both code snippiest is as follows.
VB.Net
Imports System.Text
Imports System.Runtime.InteropServices
Module Module1
Sub Main()
Dim c As cls = New cls()
c.Start()
End Sub
End Module
Public Class cls
Declare Sub Only Lib "dllproj2.dll" Alias "Only" (b As StringBuilder)
Public Sub Start()
Dim s As StringBuilder = New StringBuilder(15)
Only(s) '**Actual call to dll code **
Dim s1 As String = s.ToString.ToLower
Dim len As Integer = s.ToString.Length.ToString()
Console.Write(s.ToString())
End Sub
End Class
C++ dll
#include<stdio.h>
#include<stdlib.h>
#include<cstring>
extern "C"{
void Only(char *a)
{
char arr[10];
printf("Reached");
sprintf(arr,"This %d",33);
printf("\n%s\n",arr);
memcpy(a,arr,10);
}
}
Now as soon as I access line Only(s) I get exception shown in image.
I am not able to understand cause of exception. Output of code is fine, but while running it using Visual Studio 2012 Express it is giving above error.
It is sample code, which we also used in production, I afraid it may cause problem in future.
Kindly suggest is there any way to get rid of exception.
Thanks.
You have to declare bas: UnmanagedType.LPStr
Declare Sub Only Lib "dllproj2.dll" Alias "Only" (<InAttribute(), MarshalAs(UnmanagedType.LPStr)> b As StringBuilder)

Robert Giesecke's Unmanaged Exports

I'm trying to get an DllExport from vb.net to unmanaged c++ working.
I'm using Robert Giesecke's Unmanaged Exports with Visual Studio 2012 and tried to follow this very helpful hints. I copy the dlls from the .Net project by an post build action in the directory where my *.cpp and *.h files reside.
I checked my dll with dumpbin /EXPORTS Nugget.Discovery.dll and it tells me that there are exports:
File Type: DLL
Section contains the following exports for \Nugget.Discovery.dll
00000000 characteristics
52554A05 time date stamp Wed Oct 09 14:20:21 2013
0.00 version
0 ordinal base
2 number of functions
2 number of names
ordinal hint RVA name
0 0 0000532E StartAnnouncing
1 1 0000533E StopAnnouncing
Summary
2000 .reloc
4000 .rsrc
2000 .sdata
4000 .text
But if I try to import it in the cpp file with
#import "Nugget.Discovery.dll"
void StartAnnouncing(int serial);
I get one IntelliSense error and one error after I try to compile:
IntelliSense: cannot open source file "Debug/Nugget.Discovery.tlh"
error C1083: Cannot open type library file: 'nugget.discovery.dll': Fehler beim Laden der Typbibliothek/DLL.
Any idea what I'm doing wrong?
Best regards!
Stefan
As part of DllExport, a .lib file is generated. You can use that to use the normal C++ linker instead of LoadLibrary/GetProcAddress.
Starting from the managed code you posted, on the native side:
extern CALLBACK void StartAnnouncingType(int serial);
extern CALLBACK int TestType(void);
int _tmain(int argc, _TCHAR* argv[])
{
int test = TestPtr();
StartAnnouncingPtr(1);
}
In the settings of you unmanaged project, add Nugget.Discovery.lib to the project properties: Configuration Properties->Linker->Input. And copy the Nugget.Discovery.dll to the output directory.
Ok, thanks to Hans Passant I came to this solution:
This is my code on the managed side:
Imports System.Runtime.InteropServices
Imports RGiesecke.DllExport
Public NotInheritable Class Beacon
Private Sub New()
End Sub
Private Shared _nuggetAnnouncement As NuggetAnnouncement
' ReSharper disable UnusedMember.Local
''' <remarks>Cannot be called from managed code!</remarks>
<DllExport("StartAnnouncing", CallingConvention.StdCall)>
Private Shared Sub StartAnnouncingNative(serial As Integer)
StartAnnouncing(serial)
End Sub
''' <remarks>Cannot be called from managed code!</remarks>
<DllExport("Test", CallingConvention.StdCall)>
Private Shared Function TestNative() As Integer
Return Test()
End Function
' ReSharper restore UnusedMember.Local
Public Shared Sub StartAnnouncing(serial As Integer)
'do something
End Sub
Public Shared Function Test() As Integer
Return 42
End Function
End Class
Interesting is, that I cannot call functions that are marked with <DllExport> from managed code (even if they are Public).
And this is the code on the native side:
typedef void (CALLBACK* StartAnnouncingType)(int);
typedef int (CALLBACK* TestType)(void);
int _tmain(int argc, _TCHAR* argv[])
{
HINSTANCE dllHandle = NULL;
StartAnnouncingType StartAnnouncingPtr = NULL;
TestType TestPtr = NULL;
wchar_t dllNameWide[64];
int size = mbstowcs(dllNameWide, "Nugget.Discovery.dll", sizeof(dllNameWide));
dllHandle = LoadLibrary(dllNameWide);
if (NULL != dllHandle)
{
//Get pointer to our function using GetProcAddress:
StartAnnouncingPtr = (StartAnnouncingType)GetProcAddress(dllHandle,"StartAnnouncing");
TestPtr = (TestType)GetProcAddress(dllHandle,"Test");
int test;
if (NULL != TestPtr) test = TestPtr();
int serial = 1;
if (NULL != StartAnnouncingPtr) StartAnnouncingPtr(1);
//Free the library:
FreeLibrary(dllHandle);
}
}
Are there any other better solutions?
Ciao!
Stefan