dsneditor/EsOdbcDsnBinding/EsOdbcDsnBinding.cc (154 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
#include <Windows.h>
#include <string>
#include <assert.h>
#include <vcclr.h>
#include "EsOdbcDsnBinding.h"
/* ODBC's unprefixed define is 512 */
#define _SQL_MAX_MESSAGE_LENGTH 1024
using namespace System;
using namespace System::Reflection;
using namespace System::Text;
using namespace System::Runtime::InteropServices;
using namespace System::Threading;
using namespace EsOdbcDsnEditor;
namespace EsOdbcDsnBinding {
/*
* Proxy/Bridge class for the C# actual implementation.
*/
public ref class EsOdbcDsnBinding
{
bool onConnect;
String ^dsnInStr;
int dsnOutLen;
driver_callback_ft cbConnectionTest;
void *argConnectionTest;
driver_callback_ft cbSaveDsn;
void *argSaveDsn;
public:static HWND hwnd;
private:int cbImplementation(driver_callback_ft cbFunction, void *cbArgument,
String ^connString, String ^%errorMessage, unsigned int flags)
{
assert(cbFunction);
if (connString->Length <= 0) {
// this should not happen, but if it does, shortcut it here.
return 0;
}
// the callback understands wchar_t* only, so need to convert the input String
pin_ptr<const wchar_t> wch = PtrToStringChars(connString);
//buffer to hold the (possible) error message
wchar_t errorMessageW[_SQL_MAX_MESSAGE_LENGTH] = { 0 };
int ret = cbFunction(cbArgument, (const wchar_t *)wch,
errorMessageW, sizeof(errorMessageW) / sizeof(*errorMessageW), flags);
// if there was an error/warning, let the caller know of it
if (0 < wcslen(errorMessageW)) {
// The driver should only return errors that are actionable by the user.
// Error message usage is dictated by error code: might be discarded for some "expected" errors.
errorMessage = gcnew String(errorMessageW);
}
return ret;
}
/*
* proxy method for the connection test call back
*/
private:int proxyConnectionTest(String ^connString, String ^%errorMessage, unsigned int flags)
{
return cbImplementation(cbConnectionTest, argConnectionTest, connString, errorMessage, flags);
}
/*
* proxy method for the connection test call back
*/
private:int proxySaveDsn(String ^connString, String ^%errorMessage, unsigned int flags)
{
return cbImplementation(cbSaveDsn, argSaveDsn, connString, errorMessage, flags);
}
/*
* The constructor merely saves the input parameters and registers an assembly resolve handler
*/
public: EsOdbcDsnBinding(HWND hwnd, bool onConnect, wchar_t *dsnInW,
driver_callback_ft cbConnectionTest, void *argConnectionTest,
driver_callback_ft cbSaveDsn, void *argSaveDsn)
{
/* there should(?) be one window handler in use for one application through the driver */
assert(this->hwnd == NULL);
this->hwnd = hwnd;
this->onConnect = onConnect;
if (dsnInW != NULL) {
dsnInStr = gcnew String(dsnInW);
}
else {
dsnInStr = "";
}
this->cbConnectionTest = cbConnectionTest;
this->argConnectionTest = argConnectionTest;
this->cbSaveDsn = cbSaveDsn;
this->argSaveDsn = argSaveDsn;
// register an event handler for failed assembly loading
AppDomain::CurrentDomain->AssemblyResolve += gcnew ResolveEventHandler(resolveEventHandler);
}
public: ~EsOdbcDsnBinding()
{
hwnd = NULL;
}
/*
* Handler called if loading an assembly fails.
*/
static Assembly^ resolveEventHandler(Object^ sender, ResolveEventArgs^ args)
{
Assembly^ assembly;
try {
// TODO: check if this is safe "enough" ('args' always there, with proper format)
String^ loadingAssembleyName = args->Name->Split(',')[0];
if (loadingAssembleyName->EndsWith("resources")) {
// no resources available to load
assembly = nullptr;
} else {
// Get the bridging assembley, its location and based on that build the loading assembly path.
// Note: this assumes that the two libraries are always going to be collocated.
assembly = Assembly::GetExecutingAssembly();
int lastBackSlash = assembly->Location->LastIndexOf('\\');
String ^loadingAssemblyPath = assembly->Location->Substring(0, lastBackSlash + 1) +
loadingAssembleyName + ".dll";
if (!System::IO::File::Exists(loadingAssemblyPath)) {
throw gcnew Exception("Failed to load assembly '" + loadingAssemblyPath + "'.");
}
// then load the assembley
assembly = Assembly::LoadFrom(loadingAssemblyPath);
}
return assembly;
} catch (Exception ^e) {
if (hwnd) {
pin_ptr<const wchar_t> wch = PtrToStringChars(e->Message);
MessageBox(hwnd, wch, L"Loading Exception", MB_OK | MB_ICONERROR);
}
assembly = nullptr;
}
return assembly;
}
public:int EsOdbcDsnEditor()
{
Thread ^ t;
/* (Re)set the threading model; "Multiple calls to CoInitializeEx by the same
thread are allowed as long as they pass the same concurrency flag".
For neutral/MTAs create a new thread, for STAs just run the worker. */
switch (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_SPEED_OVER_MEMORY)) {
case S_OK:
case S_FALSE:
DsnEditorWorker();
break;
case RPC_E_CHANGED_MODE:
t = gcnew Thread(gcnew ThreadStart(this, &EsOdbcDsnBinding::DsnEditorWorker));
t->ApartmentState = ApartmentState::STA;
t->Start();
t->Join();
break;
default:
throw gcnew Exception("setting threading model failed.");
}
return dsnOutLen;
}
private:void DsnEditorWorker()
{
EsOdbcDsnEditor::DriverCallbackDelegate ^delegConnectionTest;
EsOdbcDsnEditor::DriverCallbackDelegate ^delegSaveDsn;
delegConnectionTest = gcnew DriverCallbackDelegate(this, &EsOdbcDsnBinding::proxyConnectionTest);
delegSaveDsn = gcnew DriverCallbackDelegate(this, &EsOdbcDsnBinding::proxySaveDsn);
dsnOutLen = DsnEditorFactory::DsnEditor(onConnect, dsnInStr, delegConnectionTest, delegSaveDsn);
}
};
}
#ifdef __cplusplus
extern "C"
#endif /* __cpluplus */
#ifndef DRIVER_BUILD
__declspec(dllexport)
#else /* DRIVER_BUILD */
__declspec(dllimport)
#endif /* DRIVER_BUILD */
int EsOdbcDsnEdit(HWND hwnd, BOOL onConnect, wchar_t *dsnInW,
driver_callback_ft cbConnectionTest, void *argConnectionTest,
driver_callback_ft cbSaveDsn, void *argSaveDsn)
{
try {
EsOdbcDsnBinding::EsOdbcDsnBinding binding(hwnd, onConnect, dsnInW,
cbConnectionTest, argConnectionTest,
cbSaveDsn, argSaveDsn);
return binding.EsOdbcDsnEditor();
} catch (Exception^ e) {
if (hwnd) {
pin_ptr<const wchar_t> wch = PtrToStringChars(e->Message);
if (! MessageBox(hwnd, wch, L"Loading Exception", MB_OK | MB_ICONERROR)) {
// failed to display failure error
return -3;
}
// failure error presented to user
return -1;
}
// no window hander available
return -2;
}
}