2008. 6. 28. 14:19

Connector/ODBC Examples

23.1.5. Connector/ODBC Examples

Once you have configured a DSN to provide access to a database, how you access and use that connection is dependent on the application or programming language. As ODBC is a standardized interface, any application or language that supports ODBC can use the DSN and connect to the configured database.

23.1.5.1. Basic Connector/ODBC Application Steps

Interacting with a MySQL server from an applications using the Connector/ODBC typically involves the following operations:

  • Configure the Connector/ODBC DSN

  • Connect to MySQL server

  • Initialization operations

  • Execute SQL statements

  • Retrieve results

  • Perform Transactions

  • Disconnect from the server

Most applications use some variation of these steps. The basic application steps are shown in the following diagram:

사용자 삽입 이미지

23.1.5.2. Step-by-step Guide to Connecting to a MySQL Database through Connector/ODBC

A typical installation situation where you would install Connector/ODBC is when you want to access a database on a Linux or Unix host from a Windows machine.

As an example of the process required to set up access between two machines, the steps below take you through the basic steps. These instructions assume that you want to connect to system ALPHA from system BETA with a username and password of myuser and mypassword.

On system ALPHA (the MySQL server) follow these steps:

  1. Start the MySQL server.

  2. Use GRANT to set up an account with a username of myuser that can connect from system BETA using a password of myuser to the database test:

    GRANT ALL ON test.* to 'myuser'@'BETA' IDENTIFIED BY 'mypassword';

    For more information about MySQL privileges, refer to Section 5.5, “MySQL User Account Management”.

On system BETA (the Connector/ODBC client), follow these steps:

  1. Configure a Connector/ODBC DSN using parameters that match the server, database and authentication information that you have just configured on system ALPHA.

    Parameter Value Comment
    DSN remote_test A name to identify the connection.
    SERVER ALPHA The address of the remote server.
    DATABASE test The name of the default database.
    USER myuser The username configured for access to this database.
    PASSWORD mypassword The password for myuser.
  2. Using an ODBC-capable application, such as Microsoft Office, connect to the MySQL server using the DSN you have just created. If the connection fails, use tracing to examine the connection process. See Section 23.1.4.8, “Getting an ODBC Trace File”, for more information.

23.1.5.3. Connector/ODBC and Third-Party ODBC Tools

Once you have configured your Connector/ODBC DSN, you can access your MySQL database through any application that supports the ODBC interface, including programming languages and third-party applications. This section contains guides and help on using Connector/ODBC with various ODBC-compatible tools and applications, including Microsoft Word, Microsoft Excel and Adobe/Macromedia ColdFusion.

Connector/ODBC has been tested with the following applications:

Publisher Application Notes
Adobe ColdFusion Formerly Macromedia ColdFusion
Borland C++ Builder  
  Builder 4  
  Delphi  
Business Objects Crystal Reports  
Claris Filemaker Pro  
Corel Paradox  
Computer Associates Visual Objects Also known as CAVO
  AllFusion ERwin Data Modeler  
Gupta Team Developer Previously known as Centura Team Developer; Gupta SQL/Windows
Gensym G2-ODBC Bridge  
Inline iHTML  
Lotus Notes Versions 4.5 and 4.6
Microsoft Access  
  Excel  
  Visio Enterprise  
  Visual C++  
  Visual Basic  
  ODBC.NET Using C#, Visual Basic, C++
  FoxPro  
  Visual Interdev  
OpenOffice.org OpenOffice.org  
Perl DBD::ODBC  
Pervasive Software DataJunction  
Sambar Technologies Sambar Server  
SPSS SPSS  
SoftVelocity Clarion  
SQLExpress SQLExpress for Xbase++  
Sun StarOffice  
SunSystems Vision  
Sybase PowerBuilder  
  PowerDesigner  
theKompany.com Data Architect  

If you know of any other applications that work with Connector/ODBC, please send mail to about them.

출처 : MySQA 5.0 Reference Manual

2008. 6. 21. 01:47

IDL 파일, TypeLibrary, 그리고 Automation

Automation(자동화)으로 생성하지 않았던 Project에 자동화 기능을 추가함에 있어
몇가지 알아야할 사항이 있다.

-------------------------------------------------------------------------------
[ uuid(E4029F3C-B8DE-45A0-A190-2C5A8B240487), version(1.0) ]
library TypeLibTest
{
 importlib("stdole32.tlb");
 importlib("stdole2.tlb");

 //  CTypeLibTestDoc의 기본 디스패치 인터페이스
 
 [ uuid(DBF2D3CB-9991-4D7F-954C-2DDF53B597DF) ]
 dispinterface ITypeLibTest
 {
  properties:
   
  methods:
  [id(1), helpstring("메서드 Test")] LONG Test(LONG lTemp, LONG* lpTemp);
 };

 //  CTypeLibTestDoc의 클래스 정보
 
 [ uuid(41BB86A1-7049-4C43-92DC-07C3E5629C6C) ]
 coclass CTypeLibTestDoc
 {
  [default] dispinterface ITypeLibTest;
 };
};
-------------------------------------------------------------------------------

위 기본적인 IDL 파일에는 세가지 uuid가 있다.

TypeLibrary ID : 타입라이브러리 아이디로 CWinApp를 상속하는 클래스에서 InitInstance 부분에서
등록된다.

DispInterface ID : Coclass로 정의된 CTypeLibTestDoc의 인터페이스를 정의하는 ID로 CTypeLibTestDoc
내에서 Interface로 정의된다. 이 dispinterface에 정의된 메쏘드 및 속성은 Coclass에서 정의되어야 한다.
(IDL 파일에 정의하지 않고, Coclass에만 정의해도 되는 듯 하나 나중에 코드 관리가 어려워진다.)

그리고 마지막 coclassID : Inteface ID와 연결된 Coclass의 uuid로 TypeLibrary와 마찬가지로
CWinApp를 상속하는 클래스의 InitIntance 부분에서 등록된다.

이러한 TypeLibrary 를 기존 Project에 추가하기 위해서는 몇가지 추가작업이 더 필요하다.

그 중에서,

resource를 직접 편집하여 TypeLibrary를 등록하는 부분이 필요하다.

그리고

프로젝트 속성에서 Post-Build Event에서 $(TargetPath) /register를 등록하면
컴파일시 등록까지 할 수 있도록 설정할 수 있다.

이에 대한 좀더 자세한 내용은 CodeGuru에서 퍼온 다른 글을 참고하기 바람.

( Adding automation to MFC applications )


2008. 6. 20. 00:47

비스타에서 Telnet 클라이언트 사용하기

비스타는 기본적으로 Telnet 프로그램 사용이 Disable 되어 있는 듯하다.
아래 그림같이 해당 항목을 체크하면 Telnet을 비스타에서 사용할 수 있다.

사용자 삽입 이미지

출처 : 네이버 지식인 [hammomo1961 님] 답변 중 발췌

2008. 6. 18. 23:35

Adding automation to MFC applications

Table of contents

Introduction

Automation support in MFC is a great example of why some people don't like MFC: it works great as long as you follow the pre-made examples and use MFC exactly like it was designed, but as soon as you get even a little bit off the beaten path, you're on your own. There are some tutorials in MSDN and articles online that will teach you how to use the 'New project' wizard to generate an automation-enabled application, and some of those even explain some of the working of the code the wizard generates - but unfortunately, that's not always how things go in the Big Bad Real World. Sometimes, you inherit an application that is so old that the initial skeleton was probably generated with the wizard of Visual Studio 4; that has been expanded and hacked on by dozens of people, each with their own understanding (or lack thereof) of the MFC doc/view architecture; that has mutated from an afternoon-hack-proof-of-concept to a piece of software that is critical for the survival of the company; that has had so much surgery that its innards have started to resemble (metaphorically speaking) the leftovers of the struggle that a 3-year old had with a big bowl of spaghetti bolognese. Believe me - not pretty.

So, what can you do when you are asked to add automation support to such an application? Starting over with a fresh application and moving the functionality of the old code back in is not an option. And even if it was, you would find out that the wizard-generated code is built for the idea that the central element of your application's object model is the Document. In some MFC applications, that is simply not the case any more. The only recourse is to figure out exactly what it is that makes automation tick in an MFC application - and add those pieces into the old application.

The obvious way to start is to take an application that is wizard-generated with automation support, and compare it to an application that doesn't have automation support. Add those differences to the old application and you're all set, right? Well, that's partially true - as long as the object model that you want to implement is centered around a Document, as I said before. If you have an object model that doesn't work this way, and you wonder how to get automation to work, read on.

Automation

"But wait", you say, "what is this 'automation' thing you've been talking about?". Ah yes, esteemed reader, excuse me for not elaborating on this earlier. Automation is, simply put, a way for your application to interact with Visual Basic, VBScript, JavaScript and, indeed, any other language that can work with COM objects. Which reveals how automation is implemented: as a COM interface. Which also reveals that in order for your application to support automation, it will have to support COM. It would be outside the scope of this article to give a complete primer on COM (and indeed for a complete primer on automation as well); I refer to the section 'References' for further reading for those who do not have at least some notion of what COM is and how it works. I will suppose from now on that you know what COM and automation are; that you know what an 'object model' is, and that you have one (or at least have an idea of what it will look like) for the application you wish to automate; and that you know about MFC's Doc/View architecture. You don't have to know a whole lot about that last one though, since the biggest part of this article is about how to *not* use it.

Although I said that you can use automation from any language, there is a little nuance to to be made to that statement. For scripting languages to access a COM object, they would need to have a description of the interface of the object. That description can be read from a type library (.tlb file) but not all scripting languages have access to that. Therefore, there is a way to query an object for the methods it provides: implementing the IDispatch interface. But for languages who *can* read a tlb, there should be a way to do that, too. The solution is simple: a dual interface. Again, for details on the theory on dual interfaces, see the section 'References'; I'm bringing it up here because I will assume that you want your objects to have a dual interface.

The Problem

By now, being the interested reader you are, you've looked up 'automation' in MSDN, and you've seen a wealth of articles that explain the concept of automation in MFC and how to use the class wizard to add automation-enabled classes. So, you're probably wondering "Why am I reading this - I can get this same information from MSDN?". You see, the problem is that these articles are based on the following (implicit) assumptions:

  • You generated your application skeleton with the 'enable automation' option on.
  • Your object model revolves around a Document object.
  • You want a dispinterface, not a dual interface.

No documentation gives a step-by-step overview of what you have to do to automate an existing application, detailing what everything does, and the considerations to take into account. That is what this article tries to remedy.

The Solution

This article, of course! Below, I will present the steps to take to make your application scriptable from every COM-enabled language. It turns out that the changes you have to make can be divided in these groups:

  • Defining your object model in IDL.
  • Implementing your object model as CCmdTarget-derived classes.
  • The general initialization. Initializing COM, registering your objects with the system.

I've also included a small section on how to start your automated application from C++ and VBScript for writing small test clients.

I will present all steps in a tutorial-style way, and explain in the process what those steps do and what they are for. The included sample project contains a vanilla MFC multiple-document application, except for the minimal changes necessary to get automation to work. Those changes are clearly marked in the code.

To keep the code in this article to a minimum, I will only add one COM object. The name of the application that I'll automate is 'MyCoolApp', and the COM object that will be accessible from the outside will be a generic object named 'Application' with one method: Show(), which will, unsurprisingly, show the window. I find this to be a good first method to implement since automated applications will not be shown by default when they are instantiated from an automation client. When you are developing your application, you can call the Show() method, and when your application shows up, you know that the automation works.

What to do in your application

IDL file

The first step is to add a file that has the description of the COM objects. In the Solution Explorer, right-click on your application and select 'Add' -> 'Add New Item'. Choose 'MIDL file' and type in a file name, like 'mycoolapp.idl'. This will add the file to your project and open it. As mentioned before, we'll make an object named 'application' with one method: Show(). The IDL is mostly boilerplate code. The only thing you must remember if you choose to copy this sample is to change the GUIDs. A GUID is a globally unique identifier for your interfaces; if you copy the GUIDs below, they may conflict with another application that also uses them! This may or may not pose a problem in the future, but to be sure, change them! It's easy to generate a GUID: in Visual Studio, click 'Tools', 'Create GUID', and there you go. Click the 'Copy' button to copy the newly generated GUID to the clipboard.

That being said, here is the code:

#include "olectl.h"
[ uuid(526E36B7-F346-4790-B741-75D9E5B96F4B), version(1.0) ]
// This is usually the name of your application.
library mycoolapp
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");

    [ uuid(6263C698-9393-4377-A6CC-4CB63A6A567A),
      oleautomation,
      dual
    ]
    interface IApplication : IDispatch
    {
        [id(1), helpstring("method Show")] HRESULT Show (void);
    };

    [ uuid(9ACC7108-9C10-4A49-A506-0720E0AACE32) ]
    coclass Application
    {
        [default] interface IApplication;
    };
};

Some points of attention:

  • you need to have 3 different GUIDs
  • you need to have a coclass which will be named after the object you're modeling
  • you need an interface definition which is (by convention) named 'I' + the name of the object you're modeling.

Automated class header

Now, we'll add a class that represents the object that will be automated (that is, that can be accessed by automation clients). This is, your client's entry point into your application (or one of the entry points if you implement multiple interfaces). Adding it is fairly straightforward, I'll walk you through it:

Add a class and name it after your interface, 'Application' in this case. I'll stick with the MFC naming and call it CApplication for now (although I hate the C prefix personally - see References). You can do this with the wizard or add a class by hand. Derive it from CCmdTarget. Add in #include statement (I'll explain later where this comes from):

#include "mycoolapp_h.h"

Add the following functions:

virtual void OnFinalRelease()
{
  CCmdTarget::OnFinalRelease();
}

HRESULT Show()
{
  AfxGetApp()->m_pMainWnd->ShowWindow(TRUE);
  return TRUE;
}

This is the place where you would place any cleanup code for your object. We have a simple example, there is no cleanup needed; we just call the parent.

Add the following macros in the header:

  DECLARE_DYNCREATE(CApplication)
  DECLARE_MESSAGE_MAP()
  DECLARE_OLECREATE(CApplication)
  DECLARE_DISPATCH_MAP()
  DECLARE_INTERFACE_MAP()

These macros set up some members and functions that are needed to register the class with the system and to route calls to the COM object to your (C++) object.

Add an enum:

enum 
{
  dispidShow = 1L
};

In this enum, you need to have an entry for every function you add to your interface. In this example, there is only one, Show(), so there is only one entry in the enum. You can make up your own names here - later, we'll see where those names are referenced.

Next, declare an interface map and put in entries for every function you want your automation object to have. This sounds complicated but it's just a simple macro and some cut and paste:

BEGIN_INTERFACE_PART(LocalClass, IApplication)
    STDMETHOD(GetTypeInfoCount)(UINT FAR* pctinfo);
    STDMETHOD(GetTypeInfo)(
        UINT itinfo,
        LCID lcid,
        ITypeInfo FAR* FAR* pptinfo);
    STDMETHOD(GetIDsOfNames)(
        REFIID riid,
        OLECHAR FAR* FAR* rgszNames,
        UINT cNames,
        LCID lcid,
        DISPID FAR* rgdispid);
    STDMETHOD(Invoke)(
        DISPID dispidMember,
        REFIID riid,
        LCID lcid,
        WORD wFlags,
        DISPPARAMS FAR* pdispparams,
        VARIANT FAR* pvarResult,
        EXCEPINFO FAR* pexcepinfo,
        UINT FAR* puArgErr);
    STDMETHOD(Show)(THIS);
END_INTERFACE_PART(LocalClass)

"But Roel", you'll ask, "what the hell is all of that?", and I'll tell you: GetTypeInfoCount(), GetTypeInfo(), GetIDsOfNames() and Invoke() form the implementation of IDispatch, and Show() is the implementation of the method in our interface. If you want to know exactly what the first four do, I'll refer you to MSDN, but believe me, you're better off copying and pasting - that's what I did. For those wondering if all that standard stuff cannot be wrapped in a macro: see at the end of this article. The MFC ACDual example provides such macros.

Automated class implementation

Now, it's time for the implementation of the class that we just wrote a header for. Start with the MFC macro to implement dyncreate:

IMPLEMENT_DYNCREATE(CApplication, CCmdTarget)

Next, implement the constructor and the destructor, and add the following statements to them besides your own code:

  • EnableAutomation() and ::AfxOleLockApp() in the constructor
  • ::AfxOleUnlockApp() in the destructor

Then implement the message map:

BEGIN_MESSAGE_MAP(CApplication, CCmdTarget)
END_MESSAGE_MAP()

Now comes the interesting part: the dispatch map. A dispatch map looks a lot like a message map, in that it has a BEGIN_DISPATCH_MAP part, entries for every function that your interface has, and ends with a END_DISPATCH_MAP macro. This example will show the dispatch map for our simple interface with only one method:

BEGIN_DISPATCH_MAP(CApplication, CCmdTarget)
  DISP_FUNCTION_ID(CApplication, "Show", dispidShow, Show, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()

The arguments to the BEGIN_DISPATCH_MAP macro are the same as those to the message map: the name of the class and the name of the class it is derived from. The arguments to DISP_FUNCTION_ID are more interesting. The first one is (again) the name of the class you're implementing. The second one is a short string description of your method. It will generally be the same as the name you use in your class. The third one is a unique number that we've set up in an enum in the class declaration. This number has to be unique, that's why an enum is convenient here (and of course, also because it allows you to work with descriptive names instead of numbers). The next argument is the name of the method of the class in which the interface method is implemented. As I mentioned, this is usually the same as the second argument (except for the quotes, that is).

The fifth argument then is the return type of the method. This is not as you would expect VT_HRESULT but rather VT_NONE for all methods. It would take us too far to explain here why that is exactly, but in a few words: all methods of a dispinterface return HRESULTs, as reflected by the return type of the Show() method. That HRESULT, however, is used for error reporting, not to actually return a value. As such, when you call Show() from, for example, Visual Basic, you don't get to see that HRESULT at all - if an error would occur, VB's standard error handling mechanism would kick in. Therefore, you should declare all functions in your dispatch map as returning VT_NONE. The last argument to the DISP_FUNCTION_ID macro then is a space-separated list of the arguments that the function takes. Since ours doesn't take any arguments at all, we put in VTS_NONE. If it would have taken a string and an integer, we would have used "VTS_BSTR VTS_I2", for example. See MSDN for a full list of constants that are allowed here.

So far, for the dispatch map. On to the next map: the interface map. This is where the actual 'connection' between the COM object (your automated application) and your C++ object is made. Again, let's start with an example:

BEGIN_INTERFACE_MAP(CApplication, CCmdTarget)
  INTERFACE_PART(CApplication, IID_IApplication, LocalClass)
END_INTERFACE_MAP()

The first argument of the INTERFACE_PART macro is once again the name of the class we're implementing; the second argument is the interface name (most likely 'IID_' + the name of your interface), and the third argument is the first argument to the BEGIN_INTERFACE_PART macro (which we put in the declaration). Easy as that.

One more 'standard' implementation has to be done, using the IMPLEMENT_OLECREATE macro. Sample:

IMPLEMENT_OLECREATE(CApplication, "mycoolapp.Application", 
  0x9acc7108, 0x9c10, 0x4a49, 0xa5, 0x6, 0x7, 0x20, 0xe0, 0xaa, 0xce, 0x32)

The first argument is the name of the coclass as you specified it in the IDL file. The second name is the textual description by which your object will be known to, for example, Visual Basic; if you're not sure what to choose, make it '<library name>' + '.' + '<interface name>'. Look back at the IDL example and you'll see what I mean. The third parameter is very important: it's the GUID of the coclass you declared in the IDL file.

We're almost done here: add the implementation of the IDispatch members to the class:

Collapse
STDMETHODIMP_(ULONG) CApplication::XLocalClass::AddRef()
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  return pThis->ExternalAddRef();
}
STDMETHODIMP_(ULONG) CApplication::XLocalClass::Release()
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  return pThis->ExternalRelease();
}
STDMETHODIMP CApplication::XLocalClass::QueryInterface(
  REFIID iid, LPVOID* ppvObj)
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  return pThis->ExternalQueryInterface(&iid, ppvObj);
}
STDMETHODIMP CApplication::XLocalClass::GetTypeInfoCount(
    UINT FAR* pctinfo)
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
  ASSERT(lpDispatch != NULL);
  return lpDispatch->GetTypeInfoCount(pctinfo);
}
STDMETHODIMP CApplication::XLocalClass::GetTypeInfo(
  UINT itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
  ASSERT(lpDispatch != NULL);
  return lpDispatch->GetTypeInfo(itinfo, lcid, pptinfo);
}
STDMETHODIMP CApplication::XLocalClass::GetIDsOfNames(
  REFIID riid, OLECHAR FAR* FAR* rgszNames, UINT cNames,
  LCID lcid, DISPID FAR* rgdispid) 
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
  ASSERT(lpDispatch != NULL);
  return lpDispatch->GetIDsOfNames(riid, rgszNames, cNames, 
    lcid, rgdispid);
}
STDMETHODIMP CApplication::XLocalClass::Invoke(
  DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,
  DISPPARAMS FAR* pdispparams, VARIANT FAR* pvarResult,
  EXCEPINFO FAR* pexcepinfo, UINT FAR* puArgErr)
{
  METHOD_PROLOGUE(CApplication, LocalClass)
  LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
  ASSERT(lpDispatch != NULL);
  return lpDispatch->Invoke(dispidMember, riid, lcid,
    wFlags, pdispparams, pvarResult,
    pexcepinfo, puArgErr);
}

Again, this is boilerplate code that can be simplified a lot using a few macros that are provided with the ACDual MSDN example. See the section near the end about that.

And finally, add the implementation of the Show() function. We just call the owner class' Show() method:

STDMETHODIMP CApplication::XLocalClass::ShowWindow()
{
    METHOD_PROLOGUE(CApplication, LocalClass)
    pThis->ShowWindow();
    return TRUE;
}

Changes to OnInitInstance

We also need to make some changes to OnInitInstance() to setup and register the COM objects with the system (in the registry). The first one is to call:

COleTemplateServer::RegisterAll();

The project wizard will put this call right after the ini files are loaded with LoadStdProfileSettings(); I suggest putting it there as well. It doesn't really matter, but it will look more familiar if/when you'd compare it to a wizard-generated application.

Then, scroll down a few lines until you see a call to ParseCommandLine(). Right after that, add the following code:

if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)
{
    return TRUE;
} else if (cmdInfo.m_nShellCommand == CCommandLineInfo::AppUnregister){
    AfxOleUnregisterTypeLib(LIBID_mycoolapp);
} else {
    COleObjectFactory::UpdateRegistryAll();
    AfxOleRegisterTypeLib(AfxGetInstanceHandle(), LIBID_mycoolapp);
}

When your application is started from automation, it will be run with the parameters /Embedding or /Automation. In that case, we don't want to show the main window, so we return immediately. This means, of course, if you do want to show the main window, that you should call pMainFrame->ShowWindow(TRUE); before returning. The second 'if' tests whether your application was run with the /Unregserver or /Unregister switches. If it is, we remove all references to our application from the registry (actually, we let MFC do this for us). Finally, if we detect none of these switches, we let MFC put references to our COM objects into the registry. Yes, this means that every time your application is run, it is re-registered; this is to ensure that the registry is always consistent with the latest location of the executable.

That's all there is to it as far as the changes to OnInitInstance go. If you would generate an automation-enabled application with the wizard, you'd see more code here, more specifically a few calls to functions of an m_server object of type ColeTemplateServer. They are for the case that you want your Document to be automation-enabled; it is associated with a doctemplate so that a new document can be created when the automation server is started. Since this code is generated automatically with the wizard, I won't describe it here.

Resource

One final thing to do is to embed the type library in the resources section of your application. Go to the resource view, right-click on the resource file name, and choose 'resource includes'. At the bottom of the lower box, insert:

1 TYPELIB "<appname>.tlb"

where <appname> is the name of your application, of course (in our case, it would have been 'MyCoolApp.tlb'). This way, the resource compiler will embed the type library into the executable so that you don't have to distribute it separately.

Post-build step

This step isn't strictly necessary but will make your life easier: register your application every time it is build. It is simple: in the Properties of your project, add the following line to your post-build event:

"$(TargetPath)" /RegServer

Compiling your project

To get your project to compile, you need to link in a file that was generated by the MIDL compiler: mycoolapp_i.c. To do this, right-click on 'MyCoolApp' in the Solution Explorer, choose 'Add' -> 'Add Existing Item...', and select mycoolapp_i.c.

How to use your automation object

Of course, you want to test your new automated application. Remember that there are two ways to get to your objects: through 'regular' COM (or 'early binding', only for those environments that support it, like C++) and through IDispatch ('late binding', for scripting languages like VBScript). I'll demonstrate both methods here.

From C++

Let's start with a very simple C++ application. Make a simple dialog-based application with the class wizard, be it an MFC or an ATL/WTL application. Just make sure that :CoInitialize() and CoUninitialize() are called somewhere (that is done automatically in ATL applications). Put a button on the dialog somewhere, wire it up, and put the following in the message handler for the BN_CLICKED handler:

HRESULT hr;
hr = ::CoCreateInstance(CLSID_Application, NULL, 
     CLSCTX_LOCAL_SERVER, IID_IApplication, (void**)&m_IApplication);

if(SUCCEEDED(hr)) {
  if (m_IApplication) {
    m_IApplication->Show ();
  }
}

In the header for the dialog, declare a member like this:

IApplication* m_IApplication;

Now, all you need to do is include the file where IApplication is declared. It is automatically generated from the IDL file by the midl.exe IDL compiler, so you'll have to either copy it to the directory of your test application (which you'll have to do every time you change the IDL file) or construct a #include statement with a relative path in it. The file is named (by default) <coclassname>_h.h, so in our example, it is Application_h.h. When you go looking for this file, you'll notice another file: Application_i.c. This file contains the implementation of the interface and is needed by the linker. So, again, you can copy it, or add it to your project directly.

Now, build your application, click the button you've made, and voila - there is your application! Notice that if you take out the m_IApplication->Show(); line, you can still see your application being started by looking for it in the process list in the Windows Task Manager.

From VBScript

It's easier to start your application from VBScript. In three lines:

Dim obj
Set obj = CreateObject("mycoolapp.Application ")
obj.Show ()

Notice that the argument that we pass to CreateObject is the name we passed in the IMPLEMENT_OLECREATE macro. Also, note that if you leave out the last line in one script, create a new script with all three lines, and run first the two-line script and then the three-line one, your application will be started only once! That means that if you call CreateObject() when an instance of your application is already running, a reference to that running instance will be returned.

Using the macros from the ACDual example

A lot of the code in this article is very standard: it will be exactly the same in every application you will ever automate. To avoid that, you can use the code in a header file that is provided with the ACDual example: mfcdual.h. The example itself can be found on MSDN; apart from some error handling code (which we didn't discuss in this article), it contains these macros:

  • BEGIN_DUAL_INTERFACE_PART: use this instead of BEGIN_INTERFACE_PART; it takes care of declaring the implementation of the IDispatch interface.
  • DELEGATE_DUAL_INTERFACE: add this one to your .cpp file to implement the functions that were declared with BEGIN_DUAL_INTERFACE_PART.

I strongly suggest you look at the way these macros are used in the ACDual example, and that you look at their contents; it will help you understand your application better when something goes wrong (notice that I didn't say 'if' but 'when').

References

Books on COM and automation basics:

Online resources:

History

4 Jul 2005 - updated download by Trevisan Andrea: The download code was reworked a little to include some important parts and ensure the solution file type is compatible with Microsoft Visual Studio.NET 2002 Academic.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

roel_v2



Location: Belgium Belgium

From : http://www.codeproject.com/KB/COM/mfc_autom.aspx
2008. 6. 18. 20:57

CStupidClassName

CStupidClassName

by Dejan Jelović

I'm seeing too many classes whose name starts with a capital 'C'. CMainWindow. CParameters. CSecurity. CThis. CThat. This madness must stop!

The first company that started doing this was Borland. They just added objects to their Turbo Pascal compiler, and wanted to ship a large class library.

Now, Borland's managers may be complete morons, but their engineers were quite good. They knew about barriers to entry. If they shipped this huge class library with generic names like Time and String, some of their clients would already have a record (record == C struct) named Time and String, and they would be unable to compile their stuff with the newest version of the compiler. And if you are a busy software engineer with a deadline close by, you won't spend your day troubleshooting the problems you have with your new compiler. You will simply dump it and revert to the old one.

The solution that Borland's engineers came up with is to prepend every class name with a capital 'T'. Thus we got TWindow, TTime, TString and such.

Borland, of course, expected everyone to continue using normal class names, without the capital 'T'. Or, in case of other library vendors, other capital letters. That would reduce the chance of collision to the two libraries using the same capital letter plus having the same class name.

However, programmers are busy people. They frequently take concepts provided by their vendors at face value. So when they saw that Borland was using a capital 'T' to name their classes, they thought that for some reason that is a good thing. Otherwise, the smart programmers at Borland wouldn't be doing it, right?

This of course defeated the whole system. Yes, Borland was able to ship their first version of the library without causing compilation errors in the existing code base, but as soon as the practice of using the capital 'T' spread, every new version of the library had the same problem.

So this adding of capital 'T's did no good in the long run.

Microsoft Joins the Fray

Next comes Microsoft. Since they only had a C compiler they were quickly loosing market share to Borland C++, and had to come up with a new C++ compiler and a class library. They have the same problem that Borland had few years ago: If they ship out a new C++ compiler and it won't compile the existing customers' C code, they are in trouble.

So what do they do? They add a capital 'C' to the name of each class they ship with their compiler.

Again, the users follow: They add a capital 'C' to the name of each class, and the system is defeated.

Thus we live in a world of classes with a capital 'C'.

What Should You Do?

The first common sense thing you should do is abandon the capital 'C' for your class names. Since everyone is using them, by not using them you are reducing the chance of a name conflict.

Second, learn about namespaces and start using them properly.

But isn't the Capital 'C' Useful to Distinguish Classes?

How is the difference between a class and a struct useful to a person using either? Can you name one situation in which this distinction would be useful, and not clutter?

There are only two distinctions useful in C++ code:

1. Whether a name is a data type or a function.

2. Whether a variable is a class-level variable or a function-level variable.

The first distinction is useful for looking at the code with parenthesis, i.e.:

something (2); // did the code just create a function or a temporary variable 

The second is useful in order to avoid using the wrong variable in a function.

So What Should I Use?

Use the simplest thing that works. For example, many of the people on the C++ standards committee use the following naming convention:

1. Data type names start with capital letters.

2. Variable and function names start with lower-case letters.

3. Class-level variables end in an underscore.

An example of this would be:

class MyClass {
public:
    MyClass (int numYears) {
        numYears_ = numYears;
    }

    void setYears (int numYears) {
        numYears_ = numYears;
    }

    int getYears () {
        return numYears_;
    }

private:
    int numYears_;
};

An alternative to rule #3 is to use the Microsoft convention of pre-pending class-level variable names with "m_". Thus the variable in the above example would not be numYears_, but m_numYears. Both are good and do the trick.

Feedback

Ae few days ago I got  message from Ernesto Guisado informing me that I was wrong to think that Borland was the first widely-known company to use the capital 'T'. Here's his e-mail:

From: Ernesto Guisado <erngui@acm.org>
To: "Dejan Jelovic" <djelovic@sezampro.yu>


Hi,

just read your article 
(http://www.jelovic.com/articles/stupid_naming.htm).

I agree with the ideas that you so nicely express in the 
article, but have to point out a factual mistake:

"The first company that started doing this was Borland"

See 

<http://developer.apple.com/tools/macapp/macapp_advantages.html>

for some history:

"Versions 1 and 2 of MacApp were written in Object 
Pascal. In the early '90s MacApp was ported to C++ and 
the result was Version 3.0."

MacApp is full of TApplication, TWindow, ...

This was in 1986.

Borlands OWL was created by the Whitewater group, who 
also created the Actor language. Actor is a kind of a 
Smalltalk with Pascal/C syntax, so there might be the 
link to the TFoo thing.

See also:

<http://www.applefritter.com/lisa/texts/Lisa_ToolKit_3_0_Sources.pdf>

This was for the Apple Lisa (programed in Clascal), and 
it already used TByte, TOctet,...

This "Apple Lisa Toolkit 3.0 Source Code Listing" 
(29/1012) has a very interesting line:

TByte = -128..127; {The T-names are so programs can 
USE UObject, NOT USE UClascal, and use "Byte"}

This might be a confirmation of your theory!

Regards,
Ernesto.


Read more...


From : http://www.jelovic.com/articles/stupid_naming.htm

2008. 6. 16. 00:20

파일 Drag & Drop 간단하게 구현하기

1. 드래그앤드랍을 받을 윈도우 핸들에서, DragAcceptFiles(...) 함수를 호출한다.

2. WM_DROPFILES 이벤트를 처리한다. 아래는 샘플.

void CDragNDropTestView::OnDropFiles(HDROP hDropInfo)
{
 UINT uiRet = DragQueryFile(hDropInfo,-1,NULL, NULL);
 if ( uiRet == 1)
 {
  CString sTemp;
  DragQueryFile(hDropInfo, 0, sTemp.GetBuffer(MAX_PATH), MAX_PATH);
  SetWindowText(sTemp);
 // 오픈한 파일 패스를 에디트에 표시합니다.
 }
 else if(uiRet > 1) // 파일이 한개 이상일 경우
 {
  MessageBox("Cannot open multiple files!");
 }
 
 CEditView::OnDropFiles(hDropInfo);
}

이 코드 적용시 SDI였기 때문에 uiRet가 1 이상일 경우에는 단순히 메시지 박스만 출력했다.

아래는 샘플.

(Visual C++ 6.0으로 빌드하였으며,
   SDI형태, 파일을 한개 드랍했을 경우 드랍한 파일의 패스를 보여주고,
   여러개의 파일을 드랍한 경우에는 다중 파일을 열수 없다는 메시지를 출력한다.
)


2008. 6. 15. 23:41

VariantUse 샘플: Variant 사용 설명

VariantUse 샘플에서는 기존 데이터를 variant로 변경하고 variant를 다른 데이터 형식으로 변경하는 방법을 보여 줍니다. 대부분의 COM 개체는 variant를 함수 매개 변수로 받아들입니다. 이 샘플을 통해 표준 C 데이터 형식을 variant로 변경하는 방법을 이해할 수 있습니다.

이 샘플에서는 통화, 날짜, SAFEARRAY, 다차원 배열, 문자열, char, short 및 long 형식을 다룹니다.

------------------------------------------------------------------------------------------

void CVarUseDlg::OnCurrency()
{
 VARIANT myVar;
 myVar.vt = VT_CY;
 

 // put 15.15 into the currency variant.
 CURRENCY myValue;
 myValue.Hi = 0;
 myValue.Lo = (LONG)(15*10000 + 1537);
// set the value to $15.15;
 myVar.cyVal = myValue;
 
//or
 myValue.int64 = (151234);
 myVar.cyVal = myValue;
 
 
 
 //Now get the value from a float and put it into the currency.
 float myMoney = (float)5.37;
 myVar.vt = VT_R4;
 myVar.fltVal = myMoney;
 //no loss of precission.
 ENSURE(SUCCEEDED(VariantChangeType(&myVar, &myVar, 0, VT_CY)));
 

 //Now get the value from a String
 USES_CONVERSION;
 myVar.bstrVal = SysAllocString(L"23.4345");
 myVar.vt = VT_BSTR;
 ENSURE(SUCCEEDED(VariantChangeType(&myVar, &myVar, 0, VT_CY)));

 //Finally output a currency to a string.
 myVar.cyVal.int64 += myVar.cyVal.int64;
// + myVar.cyVal;
 ENSURE(SUCCEEDED(VariantChangeType(&myVar, &myVar, 0, VT_BSTR)));
 
}

void CVarUseDlg::OnStrings()
{
 //Create a BSTR and assign it to a Variant
 BSTR x = SysAllocString(L"Hello");
 VARIANT myVariant;
 myVariant.vt = VT_BSTR;
 myVariant.bstrVal = x;
 SysFreeString(x);

 //Create a CString and change it to a variant;
 CString myCString(_T("My String"));
 CString mySecondString;
 
 BSTR y = myCString.AllocSysString();
 myVariant.bstrVal = y;
 mySecondString = y;
 SysFreeString(y);

 
 //Create two BSTRs and add them.
 BSTR a = SysAllocString(L"One two ");
 BSTR b = SysAllocString(L"three four.");
 _bstr_t my_bstr_t(a, TRUE);
 my_bstr_t += b;
 myVariant.bstrVal = my_bstr_t;
 // or
 myVariant.bstrVal = _bstr_t(a, FALSE) + b;

 //Change a bstr to a CString.
 CString ANewString(b);
 //or if CString already exists.
 myCString = b;


 //Use of CComBSTR
 CComBSTR myCComBSTR(L"Hello");
 myCComBSTR.Append(L", how are you?");
 VARIANT varFromCCom;
 varFromCCom.vt = VT_BSTR;
 varFromCCom.bstrVal = myCComBSTR;
 
}

void CVarUseDlg::OnBasic()
{
 //create a variant of the character 'c'
 VARIANT varC;
 varC.vt=VT_UI1;
 varC.cVal = 'c';

 //create a variant of the short 12
 VARIANT varS;
 varS.vt = VT_I2;
 varS.iVal = 12;

 //create a variant of the long 1234567;
 VARIANT varL;
 varL.vt = VT_I4;
 varL.lVal = 1234567;
}

void CVarUseDlg::OnSafeArray()
{

 //Create a standard array;
 int* pMyOriginalArray = new int[100];
 int count;
 for(count = 0; count < 100; count++) pMyOriginalArray[count] = count;

 
 //Now get ready to put it into a safe array
 HRESULT hr;
 //create an array bound
 SAFEARRAYBOUND rgsabound[1];
 rgsabound[0].lLbound = 0;  
//The first (and only) collumn of our array starts at 0.
 rgsabound[0].cElements = 100;
//and has 100 elements.
 
 //create the array
 SAFEARRAY FAR* pMySafeArray;
 pMySafeArray = SafeArrayCreate(VT_I4, 1, rgsabound);
//create the array of 4byte integers, one dimension
               //with the bounds stored in rgsabound.
 
 //now access the safe array's data.

 int* pData;
 hr = SafeArrayAccessData(  pMySafeArray, (void**)&pData);
//Get a pointer to the data.
 
 
 //copy the 400 bytes of data from our old array to the new one.
 memcpy_s(pData, 400, pMyOriginalArray, 400);
 
 //here we verify to you that the data is correct.
 for(count = 0; count < 100; count++) ASSERT(pData[count] == pMyOriginalArray[count]);
 SafeArrayUnaccessData(pMySafeArray);
 
 
 //To put the SafeArray in a Variant
 VARIANT myVariant;
 myVariant.parray = pMySafeArray;
 myVariant.vt = VT_ARRAY|VT_I4;
// state it's an array and what type it holds.
 
 
 //pass the Variant to a function call. 
 FunctionCallWithVariant(myVariant);
 //see that the original array was modified.
 hr = SafeArrayAccessData(myVariant.parray, (void**)&pData);
//Get a pointer to the data.
 ASSERT(pData[0] == 1234);
 SafeArrayUnaccessData(pMySafeArray);
 
 //Copy the variant.
 VARIANT NewVariant;
 NewVariant.vt = VT_EMPTY;
// must set variant type for copy to work.
 hr = VariantCopy(&NewVariant, &myVariant);

 //check array in copy of variant
 int* pNewData;
 hr = SafeArrayAccessData(NewVariant.parray, (void**) &pNewData);
 for(count = 0; count < 100; count++) ASSERT(pData[count] == pNewData[count]);
 ASSERT(pData != pNewData); //copying the varriant created a new copy of the
         
//array it contained too.  There is now 2 copies of the data.
 SafeArrayUnaccessData(NewVariant.parray);

 //Or you could use CreateVector to create the 1 dimensional array.
 SAFEARRAY* pMyVector = SafeArrayCreateVector(VT_I4, -50, 100);  //Vecoor of VT_I4, starting at 0
               
//with 100 elements.
 int* pVectorData;
 hr = SafeArrayAccessData(pMyVector, (void**)&pVectorData);
 memcpy_s(pVectorData, 400, pMyOriginalArray, 400);
//copy the 400 bytes of data from our old array into the new one.
 int element;
 long location[1] = {-50};
 hr = SafeArrayGetElement(pMyVector, location, (void*)&element);
 ASSERT(element == 0);
//element -50 is the first element from the original array
 location[0] = 0;
 hr = SafeArrayGetElement(pMyVector, location, (void*)&element);
 ASSERT(element == 50);
// element 0 is the 50th element from the original array
 delete[] pMyOriginalArray;
 hr = SafeArrayDestroy(pMySafeArray);
 hr = SafeArrayDestroy(pMyVector);
 hr = SafeArrayDestroy(NewVariant.parray);
 
}

void CVarUseDlg::OnOleArray()
{
 //Create and fill an array.
 int* pMyOriginalArray = new int[100];
 int* pData;
 int count;
 for(count = 0; count < 100; count++) pMyOriginalArray[count]=count;
 
 //Put it into a safe array.
 COleSafeArray myOleSafeArray;
 myOleSafeArray.CreateOneDim(VT_I4, 100, pMyOriginalArray);
// create one dimension array of 4-byte values
                     // 100 entries w/data from pMyOriginalArray.
 // Access that safe array.
 //Get a pointer to the Data.
 myOleSafeArray.AccessData((void**)&pData);
 //verify all of the data.
 for(count = 0; count < 100; count++) ASSERT(pData[count] == pMyOriginalArray[count]);
 myOleSafeArray.UnaccessData();
 
 //clean up.
 delete[] pMyOriginalArray;
}

void CVarUseDlg::OnMultiDimensionArray()
{
 short pMyOriginalData[2][3][4][5];
 HRESULT hr;
 //initialize data
 for(short a = 0; a < 2; a++)
  for(short b = 0; b < 3; b++)
   for(short c = 0; c < 4; c++)
    for(short d = 0; d < 5; d++)
     pMyOriginalData[a][b][c][d] = a*b*c*d;

 

 SAFEARRAY* pMyArray;
 SAFEARRAYBOUND rgsabound[4];
 for(int count = 2; count < 6; count++)
 {
  rgsabound[count - 2].lLbound = 0;
  rgsabound[count - 2].cElements = count;
 }
 pMyArray = SafeArrayCreate(VT_I2, 4, rgsabound);
 short* pData;
 hr = SafeArrayAccessData(pMyArray, (void**)&pData);
 memcpy_s(pData, 2*3*4*5*2, pMyOriginalData, 2*3*4*5*2);
 short element;
 long location[4] = {1,2,3,4};
 hr = SafeArrayGetElement(pMyArray, location, (void*)&element);
 ASSERT(element == 1*2*3*4);
 
 
 
 hr = SafeArrayDestroy(pMyArray);
 ASSERT(hr != S_OK);
 hr = SafeArrayUnaccessData(pMyArray);
 hr = SafeArrayDestroy(pMyArray);
 ASSERT(hr == S_OK);
 
}

void CVarUseDlg::OnDate()
{
 VARIANT timeSelection;
 COleDateTime timeNow;
 DATE curDate;
 HRESULT hr;
 //Get current Time.
 timeNow = COleDateTime::GetCurrentTime();
 
 //Put time into variant.
 timeSelection.vt = VT_DATE;
 timeSelection.date = timeNow.m_dt;
 
 
//Convert Variant into string using Variant Change Type.
 hr = VariantChangeType(&timeSelection, &timeSelection, 0, VT_BSTR);
 CString sCurTime(timeSelection.bstrVal);
 
 
 
//Get Time as System Time.
 SYSTEMTIME mySysTime;
 timeNow.GetAsSystemTime(mySysTime);
 
 //Use COleDateTime functionality to get change SYSTEMTIME into DATE.
 COleDateTime pastTime(mySysTime);
 curDate = pastTime.m_dt;


 //Use COldeDateTime Format command to get date as CString.
 LPCTSTR format = _T("%X %z"); 
//Current time and time zone.
 
//Note see "strftime" help for valid formating strings.
 CString sTime = pastTime.Format(format);

}

void CVarUseDlg::FunctionCallWithVariant(VARIANT myVariant)
{
 HRESULT hr;
 int* pData;
 int count;
 hr = SafeArrayAccessData(myVariant.parray, (void**)&pData); //access the array stored in the varriant.
 for(count = 0; count < 100; count++) ASSERT(pData[count] == count);
//check data;
 pData[0] = 1234;
//modify data
 SafeArrayUnaccessData(myVariant.parray);
}

------------------------------------------------------------------------------------------
출처 : MSDN 2005

첨부 파일 : 소스 원본 (Visual Studio 2005 Project)

2008. 6. 15. 13:49

Strategy 패턴 소스 (셀 정렬 알고리즘 포함)

import java.util.Comparator;


public class Sorters
{
 /** 셀 정렬 구현. */
  public static void shellSort(Object[] base, Comparator compareStrategy)
  {
    int i, j;
    int gap;
    Object p1, p2;

    for(gap=1; gap <= base.length; gap = 3*gap + 1)
      ;

    for( gap /= 3; gap > 0 ; gap /= 3)
      for( i = gap; i < base.length; i++ )
        for( j = i-gap; j >= 0 ; j -= gap )
        {
          if( compareStrategy.compare( base[j], base[j+gap] ) <= 0)
            break;

          Object t = base[j];
          base[j] = base[j+gap];
          base[j+gap] = t;
        }
  }

  // ...

  public static void main( String[] args )
  {
    String array[] = { "b", "d", "e", "a", "e" };
    Sorters.shellSort(
      array,
      new Comparator()
      {
        public int compare( Object o1, Object o2 )
        {
          // 역방향으로 정렬
          return -( ((String)o1).compareTo((String)o2) );
        }
      }
   );

   for ( int i=0; i < array.length; ++i )
     System.out.println( array[i] );
  }
}


2008. 6. 15. 13:32

Doxygen 주석 활용 [VC++ 스타일]

//////////////////////////////////////////////////////////////////////////
/// @brief  Test Class for an example of doxygen usage
/// @remark  More Information
/// @author  Jihoon.Yim
/// @version V1.0
/// @date  3/21/2008
//////////////////////////////////////////////////////////////////////////
/// @note  I changed my mind.
/// @todo  I will have to think more complex design on this class.
class Test
{
 int Add(int i, int j);
 int Sum(int i, int j);
};

//////////////////////////////////////////////////////////////////////////
/// @brief  Add two parameters and then return the sum
/// @param  i Integer value
/// @param  j Integer value
/// @exception Throws an exception when error occurs.
/// @return  Return (i+j)
/// @retval  -1 Failed to add
/// @remark  More Information
///    - Item 1
///     -# Sub Item 1
///     -# Sub Item 2
///     .
///    - Item 2
///    .
/// @code
/// int i=3;
/// int j=3;
/// try
/// {
///  int result = Add(i, j);
/// }
/// catch
/// {
///  ...
/// }
/// @endcode
/// @author  Jihoon.Yim (A company)
/// @author  Gildong.Hong (B consulting company)
/// @date  3/21/2008
//////////////////////////////////////////////////////////////////////////
/// @bug  [Fixed 3/22/2008 Jihoon.Yim] That was not correct in some cases.
/// @bug  [Fixed 3/21/2008 Jihoon.Yim] I forgot some calculation. (Fixed 3/23/2008)
/// @warning Watch out when using this class.

int Test::Add(int i, int j)
{
 return (i+j);
}


//////////////////////////////////////////////////////////////////////////
/// @brief  Add two parameters and then return the sum
/// @param  i Integer value
/// @param  j Integer value
/// @return  Return (i+j)
/// @retval  -1 Failed to add
/// @author  Jihoon.Yim
/// @date  3/21/2008
//////////////////////////////////////////////////////////////////////////
int Test::Sum(int i, int j)
{
 return (i+j);
}

2008. 6. 15. 13:22

Code Muri

코드무리란,

무리는 사람이나 짐승이 모여서 뭉친 한 동아리란 순 우리말로서, 코드무리는 코드 모음이 모여있는 묶음이란 뜻이다.
그래서 이 코드무리에서는 여러가지 주제에 대한 간단한 샘플 코드들을 모으는 블로그이다.

영어론 Code Snippets의 의미와 같다고 보면 된다.