'Project/T-Set'에 해당되는 글 14건

  1. 2013.05.07 Plugin에서 Project 내장객체 사용하기
  2. 2013.05.07 DisconnectedContext 디버그 메시지 처리하기
  3. 2012.02.22 전역 코드 디버깅 하기
  4. 2011.04.05 Scenario Script 생성시 시나리오 추가하기
  5. 2011.01.15 MFC에서 IEnumVariant 구현 예제
  6. 2011.01.15 JScript supports SafeArrays of Variants only!!!
  7. 2011.01.15 JScript arrays and COM objects
  8. 2011.01.03 New ASP.NET Charting Control
  9. 2011.01.03 HTTP Request ActiveX Object
  10. 2011.01.03 C#을 이용한 Array 또는 Enumerator 간단 구현
  11. 2011.01.03 Article 5. The Safe OLE Way of Handling Arrays
  12. 2011.01.03 OLE Automation 에서 배열을 반환하는 방법
  13. 2010.12.08 Windows XP 서비스 팩 2 웹 보관 파일 (.mht) 파일 이미지 관련 오류
  14. 2010.12.05 Word-wrap 방지하기
2013. 5. 7. 06:12

Plugin에서 Project 내장객체 사용하기

ActiceX 컨트롤 (반드시 컨트롤이어야 합니다.)에서 T-Set 의 Project 내장 객체를 Keep 해 두었다가 사용하는 방법은 다음과 같습니다. 


인터페이스에 Project 를 저장할 Attribute 를 하나 선언합니다.


    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
    public interface ITest
    {
        object Project
        {
            get;
            set;
        }       
    }


그리고 구현부에 위 Project 를 선언해 줍니다. 


    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("Example.Test")]
    public partial class UserControl1 : UserControl, ITest
    {
        public object Project
        {
            get;
            set;
        }
    }


마지막으로 제일 중요한 건, 위 Project COM 객체를 릴리즈 해주어야 합니다. 


    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("Example.Test")]
    public partial class UserControl1 : UserControl, ITest
    {
        const int WM_DESTROY = 0x0002;        

       protected override void WndProc(ref Message m)
        {            
            else if (m.Msg == WM_DESTROY)
            {
                if (this.Project != null)
                {
                    try
                    {
                        Marshal.ReleaseComObject(this.Project);
                        this.Project = null;
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.ToString());
                    }
                }
            }

            base.WndProc(ref m);
        }
    }


참고로, 만약 Project 를 이용하여 다른 내장 객체를 반환받은 경우에도 반드시 릴리즈 해주는 것을 잊지 마시기 바랍니다.


2013. 5. 7. 05:47

DisconnectedContext 디버그 메시지 처리하기

T-Set 의 Plugin 을 C# AcitveX 로 작성하면 아주 가끔 다음의 오류메시지를 접하게 됩니다. 


"Managed Debugging Assistant 'DisconnectedContext' has detected a problem in 'C:\Program Files\T-Set\T-Set.exe'."





"DisconnectedContext" 와 관련해서 MSDN 에 찾아보면, COM 이 해제된 상태에서 접근되었기 때문에 COM 의 구성요소를 응용 프로그램이 완전히 종료될 때까지 컨텍스트가 종료되지 않도록 해야 된다고 하는데 이게 풀기가 쉽지가 않습니다. 그리고 디버깅중에 이 메시지 박스를 보일 뿐이지 릴리즈 후에 실제로 문제로 이어지는 경우는 보지 못했습니다. MSDN 의 런타임 효과에도 아래처럼 명시되어 있습니다.


런타임 효과

  이 MDA는 CLR에 아무런 영향을 주지 않습니다. 연결이 끊어진 컨텍스트에 대한 데이터만 보고합니다.


  ☞ '이 MDA' 는 DisconnectedContext 를 말합니다.


참고: http://msdn.microsoft.com/ko-kr/library/2c1czate(v=vs.80).aspx


자 이제 이 메시지 박스만 디버깅중에 보이지 않게 하면 될 것 같습니다. 그 방법을 소개하면 아래의 2 단계의 설정이 필요합니다. 


1. MDA 활성화


  • 레지스트리 키를 이용한 방법
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework]
"MDA"="1"

위 레지스트리 값을 설정해 줍니다. 레지스트리 편집기를 이용하거나 위 내용을 복사한 후, .reg 파일에 넣고 더블클릭을 해서 추가할 수도 있습니다.

  • 환경변수를 이용한 방법

COMPLUS_MDA=1


위 환경변수를 시스템에 추가해 줍니다. 추가 후에는 Visual Studio 를 재시작 해주어야 합니다.


2. T-Set MDA 구성 설정


T-Set.exe 가 있는 위치에 T-Set.exe.mda.config 파일을 생성하고, 아래 내용을 추가합니다.


<mdaConfig>
  <assistants>
    <disconnectedContext enable="false" />
  </assistants>
</mdaConfig>

[아래 그림 참고]




이렇게 한 후, 디버깅을 하면 귀찮은 "DisconnectedContext" 디버그 메시지가 보이지 않게 됩니다.


참고: http://msdn.microsoft.com/ko-kr/library/d21c150d.aspx

2012. 2. 22. 21:15

전역 코드 디버깅 하기

T-Set 은 JScript 를 실행할 때 test case 이름과 동일한 이름의 함수를 entry point 로 사용합니다. 설정된 중단점의 경우 이 엔트리 함수로 진입한 이후에야 적용이 됩니다. 하지만 엔트리 함수와는 별개로 전역 코드(함수 밖에서 선언된 코드)를 작성할 수 있으며 이 전역 코드는 엔트리 함수가 실행되기 전 수행됩니다. 단점이라면 중단점 설정이 안되는 것이죠.

하지만 가끔 전역 코드를 디버깅하고 싶을 때가 있습니다. 이럴 때 예외를 미리 발생시키는 트릭을 사용하면 됩니다.

예를 들어, test 라는 함수에

Log.Print("What's your name?");

Log.Print("My name is sskk");

function test() {
Log.Print("Entry point");


이 코드에서 전역 코드 Log.Print("What's ...") 를 디버깅 하고 싶으면 미리 예외를 던진 후에 단계별 실행을 수행하면 됩니다.

throw 1;

Log.Print("What's your name?");

Log.Print("My name is sskk");

function test() {
Log.Print("Entry point");


예외로 인해 중단된 시점에서 step into, out, 그리고 over 명령을 이용해서 하나씩 디버깅하면 됩니다. 
2011. 4. 5. 07:15

Scenario Script 생성시 시나리오 추가하기

시나리오 스크립트에서는 다음과 같은 인터페이스를 노출하여 임의 시나리오 생성을 지원합니다.


var sceObj = CreateScenarioNew();  // 새로운 시나리오 노드 객체를 생성
sceObj.ScenarioText = "첫번째 시나리오";
sceObj.ScenarioImage = "이미지 파일 경로";
sceObj.Expected = "기대 결과 문자열";
sceObj.ExepctedImage = "이미지 파일 경로"; 
2011. 1. 15. 12:18

MFC에서 IEnumVariant 구현 예제

INPROC 클래스

CVariantMap 클래스는 VARIANT 맵에 VARIANT를 구현합니다. 이렇게 되면 한 VARIANT를 다른 VARIANT에 매핑할 수 있습니다. CMap을 직접 사용하는 Visual C++ 프로그래머들에게는 유용하지 않겠지만 이로 인해 Visual Basic 사용자들은 MFC의 컬렉션 클래스 기능을 이용할 수 있습니다. mfc.inproc.varmap 이름을 사용하여CVariantMap 클래스에 액세스합니다. 이것은 Windows 레지스트리에서 개체를 등록하는 방법입니다. CVariantMap _NewEnum 메서드뿐만 아니라 표준 컬렉션 메서드와 속성을 구현합니다.

CStringCollect 클래스는 문자열 배열을 구현합니다. CStringCollect CVariantMap과 동일한 여러 가지 자동화 기능을 구현하지만 CStringCollect 구현이CVariantMap 클래스보다 간단합니다. mfc.inproc.strcoll 이름을 사용하여 CstringCollect 개체에 액세스합니다. CStringCollect는 대부분의 표준 컬렉션 메서드와 속성을 구현하며 자체 컬렉션을 구현하는 방법을 보여주는 좋은 예제이기도 합니다. _NewEnum 메서드를 구현하여 컬렉션의 콘텐츠를 열거할 때 Visual Basic 사용자가For Each... In 구문을 사용할 수 있도록 합니다. CStringCollect CEnumVariant를 사용하여 이 기능을 구현하며 MFC 인터페이스 맵을 사용하여 IEnumVARIANT를 구현합니다. 사용자의 응용 프로그램에서 CEnumVariant가 유용함을 확인할 수 있습니다.

마지막으로 이 샘플에서는 in-process 서버와 LocalServer 서버의 성능을 비교하는 데 사용되는 몇 가지 더미 속성을 구현합니다. 더미 속성은 IPDRIVE의 Test1 및 Test2 단추로 테스트한 속성입니다.


참고:
2011. 1. 15. 12:11

JScript supports SafeArrays of Variants only!!!

SafeArray 를 구현할 때, VT_VARIANT 만 지원한다는 내용입니다. VT_BSTR 로 해서 구현하다가 계속해서 문제가 생겨서 원인이 무얼까 했는데 아래 글을 찾아서 다행입니다.

SafeArray 를 반환하면 JScript 에서는 다음의 변환 과정을 거쳐야만 사용할 수 있습니다.

    var obj = Project.GetAllTestScriptList();
    obj = new VBArray(obj).toArray();
    for(var key in obj)
    {
     Log.Print(obj[key]);
    }

중요한건 SAFEARRAY 보다는 IEnumVARIANT 를 이용하는 것이 더 좋습니다. 

IEnumVARIANT 를 구현하는 방법은 


를 참고하세요.

출처: http://blogs.msdn.com/b/jaiprakash/archive/2007/01/09/jscript-supports-safearrays-of-variants-only.aspx

Recently I investigated a bug in Jscript which was reported on Vista. The scenario was working perfectly fine on XP and win2k3 but regressed in Vista. Here is the repro code...

var strSCID = "{305CA226-D286-468e-B848-2B2E8E697B74} 2";
var shell = new ActiveXObject("Shell.Application");
var cpls = shell.Namespace(3).Items();   // 3 is the ID for Control Panel
var num = cpls.Count;
while (num)
{
   var fldrItem = cpls.Item(num-1);   
   WScript.Echo("The cpl " + fldrItem.Name + " belongs to category id " + fldrItem.ExtendedProperty(strSCID));   
   num--;
}

The issue was that above JScript code was printing nothing for fldrItem.ExtendedProperty(strSCID) on Vista. Same script was printing correctly on XP/Win2k3. On Debugging I found out that fldrItem.ExtendedProperty was returning a SafeArray on vista while it used to return a variant of type VT_BSTR or VT_I4 on XP/Win2k3. That means it was ExtendedProperty() which actually changed the behavior and regressed the scenario. Job done!

 But still I was wondering why JScript was not able to print the array because to my knowledge, JScript has got a datatype VBArray which is actually nothing but JScript representation of SafeArrays. I decided to debug more and go to the root of the issue. On debugging further I found out that Jscript can interpret SafeArrays only of Variants, i.e. vt of array variant must be set to VT_ARRAY | VT_VARIANT. On Vista ExtendedProperty() was returning SAFEARRAYS of type VT_ARRAY | VT_I4 and unfortunately Jscript doesn't recognizes these type of safearrays.

Well, you must be thinking that what do I mean JScript doesn't recognize one type of safearrays but not of other type. . What I mean is, if Jscript is passed a SafeArray of type other than VT_ARRAY | VT_VARIANT, then JScript just keeps the reference. None of the JScript operations which can be performed on a normal JScriptVBArray Object, can be performed on these objects. JScript can be used to pass these non-variant safearrays from one automation object to another, but JScript itself can’t manipulate them. Hope it makes the thing clear.

 So if you are designing an Automation object (ActiveX object) and want that object to be consumed by JScript, then…

1. Design it in such a way that it neither takes nor returns a safearray.

2. Otherwise design it in such a way that it returns safearrays of type VT_ARRAY | VT_VARIANT (But doesn't take a safearray).

One important point to keep in mind is that SafeArray of type VT_ARRAY | VT_VARIANT can be converted to JScript representation of arrays but JScript arrays can’t be converted back to SafeArrays. This is just not supported at all. So while designing you ActiveXObject, keep in mind that if any of the methods needs a SafeArray as formal parameter than that method can’t be called from JScript.

 To know more about Jscript VBArrays please read Eric Lippert’s excellent blog.

2011. 1. 15. 11:50

JScript arrays and COM objects

출처: 

MONDAY, AUGUST 13, 2007

JScript arrays and COM objects, part 1

In one of the projects I am doing I needed to create the COM object that is going to be used from JavaScript. It is a well-known scenario - the JScript implementations lets programmer instantiate any COM object that is an ActiveX automation-enabled, i.e. it implements an IDispatch interface. Then, you can call any method or manipulate any property of the COM object. It looks nice and perfect as long as you are using simple data types - numbers, string. Still, there is no problem even to pass the object. Your property may be even visible as an object to the script - you may pass back the pointer to the IDispatch implementation of some object as the property and it can be treated in script almost like a native JScript object.

However, it no longer looks so bright when you need to pass to/from your COM object an array. The problem is that what you call an array in JavaScript is the completely different thing than what you call an array in your programming language you use to create your COM object. To complicate things more, an array in JScript, an array in ActiveX world and an array in "serious" programming languages like C++ or Delphi are different objects. In C, C++ and pascal it is, simply, the segment of memory which contains consecutive elements of the same certain type. In ActiveX world and Visual Basic which is set in this world, array refers to what is called variant arrays, safe arrays or VBArrays, which are bound inside the VARIANT type and are still, more or less, block of memory. On the other hand, in JScript, the array is the object that has its own properties (in fact one - length) and methods. It is not hard to guess that it is in no way compatible with safe arrays.

Your COM object will use safe arrays as it is a standard way in ActiveX. Let us, firstly, concentrate on returning arrays from the function. We will consider the object Some.Object that exposes the following method:

[id(0x000002C0)]
HRESULT GetSomeArray([out, retval] VARIANT *retArr );
which may have the following implementation in Delphi:
function TSomeObject.GetSomeArray : OleVariant;
var
vals: array [0..2] of Variant;
begin
vals[0] := 'Hello';
vals[1] := 'World';
vals[2] := 'Dude';
Result := VarArrayOf(vals);
end;
In VBScript there would be no problem with dealing with the result of this function. In JScript, however, we get something which is not familiar - it is not an array as JScript understands it.
Is there any way to convert safe arrays to JScript arrays? Fortunately, the answer is affirmative. JScript architects has provided the class VBArray which is the JScript front-end to safe arrays. We can manage it in the following way:
var someObj = new ActiveXObject("Some.Object");
var arr = (new VBArray(someObj.GetSomeArray())).toArray();

alert(arr[0]);
We have used here toArray() method to convert the VBArray to the JScript array. We could also use getItem() method to directly access element of the array. Note, that script engines, both JScript and VBScript have the limitation - they can only process safe array containing elements of type Variant. Therefore, even if you want to pass an array of strings, make the array's element to be of type Variant.

OK, it was not so bad. But, what about passing an array to the function. Here, we encounter the real problem. We will solve it in the next post.

출처:

WEDNESDAY, AUGUST 15, 2007

JScript arrays and COM objects, part 2

In the previous post I have been discussing the scenario where we have COM object and the script in JScript and we need to interchange the array of data between them. I have demonstrated how to convert, in your JScript code, the vb-style array received from call to ActiveX object to the array native to JScript. To that, fortunately, the architects of JScript provided us with a tool - VBScript class. However, in reverse situation we don't have any object or routine in JScript scripting engine that would let us or help us converting JScript array to VBArray.

Eric Lippert in his blog states that, although he started writing code to manage conversion to VBArray, it was eventually abandoned and there is no way to do this.
And, actually, this is a truth. JScript engine does not provide us with any way to do that conversion. But why not try looking for it outside the engine but in what is by default available?
Fortunately, here our quest is successful and what we find is that there a COM object in WSH called Scripting.DictionaryIt is, well, as its name says, a dictionary, but what would be most important for you is that it has a method called Items which returns all the items in the dictionary as a VBArray object. So now, I am sure, you know what to do. Here is a code snippet of the function that converts JScript arrays into VBArray:

function GetVBArray(jsArray)
{
var dict =
new
ActiveXObject("Scripting.Dictionary");

for (i in jsArray)
{
dict.add(i, objJSArray[i]);
}

return dict.Items();
}

However, it may have some drawbacks like efficiency. But if it is not critical to your script it will suffice. On the other hand, if you are also an author of the COM object it would be better to just pass JScript array and deal with it in the code, wouldn't it?

If you read my previous post you probably remember that I wrote that in JScript arrays are nothing more than objects. You may also remember that JScript passes objects to automation objects as a pointer to IDispatch interface. Therefore, we may query the IDispatch object about the elements of array, can't we?

Below there is a piece of code in pascal (delphi) of the method that can both accept and process VBArrays and JScript arrays (to be precise, one-dimensional one):

procedure TSomeObject.ProcessArray(SomeArr: OleVariant);
var
i: integer;
ind: array [0..0] of integer;
begin
if VarIsArray(SomeArr) then // (1)
begin
for i := VarArrayLowBound(SomeArr, 1)
to
VarArrayHighBound(SomeArr, 1) do
begin
ind[0] := i;
ProcessElement(VarArrayGet(SomeArr, ind));
end;
end
else if VarIsType(SomeArr, VT_DISPATCH) then // (2)
begin
for i := 0 to SomeArr.length - 1 do
begin
ProcessElement(SomeArr.pop());
end;
end
else
raise EOleException.Create
('Parameter is not an array!',
E_INVALIDARG, '', '', 0);
end;

My example is in Delphi, but if you prefer, for instance, programming in C++ and ATL you can follow the same reasoning. However it will consume more code, since C++ semantics do not allow that you do late binding and you need to do that manually by pairs of calls to IDispatch's GetIDsOfNames and Invoke. In Delphi, we could use its magic of having IDispatch reference in Variant variable and leave it to Delphi to play with name resolving and calls to IDispatch methods.

Now let us analyze the above code. Firstly, in the if in (1) we are checking if the variant contains the traditional VBArray. If so, we process as we normally do with safe arrays. If not, we again examine our variant parameter if its content is IDispatch object. If so, it's probably the JScript array. Therefore, we can try getting the length property or get all elements by calls topop method. You may also get the value at the given index, but it cannot be done automatically in Delphi. All you need to do is to extract an IDispatch interface, invoke GetIDsOfNames for the given index (which as you know, need not be an integer) and then Invoke retrieved DISPID to get the actual value.

That is all about arrays in JScript and COM object, I wanted to say for now. I hope somebody will find it usable. Maybe, I will also post in the future sample in C++ and ATL, stay tuned ;).


출처:

TUESDAY, AUGUST 21, 2007

JScript arrays and COM objects, part 3

I have promised in my previous post that I will present also how to deal with JScript arrays in C++ code with ATL. Below, there is a code of a function, I have come up with, in C++ that gets the JScript array of integers and calculates the sum:

#include <string>
#include <sstream>
#define _C(oleop) hr = oleop; \
if (!SUCCEEDED(hr)) \
return hr;

STDMETHODIMP CHelloWorld::JSArrSum(VARIANT js_arr, LONG* Sum)
{
HRESULT hr;

/* checking if argument is an object */
if (!(js_arr.vt & VT_DISPATCH))
return E_INVALIDARG;

/* retrieving IDispatch */
IDispatch *disp = js_arr.pdispVal;
if (js_arr.vt & VT_BYREF)
disp = *(js_arr.ppdispVal);

/* getting array's length */
DISPPARAMS params;
FillMemory(¶ms, sizeof(DISPPARAMS), 0);
VARIANT res;

DISPID dl;
LPOLESTR ln = L"length";

_C(disp->GetIDsOfNames(IID_NULL, &ln, 1, LOCALE_USER_DEFAULT, &dl));

_C(disp->Invoke(dl, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET,
¶ms, &res, NULL, NULL));

VARIANT len1;
VariantInit(&len1);

_C(VariantChangeType(&len1, &res, 0, VT_I4));
LONG len = len1.lVal;

/* summing elements */

int total = 0;
for (int i = 0; i < len; i++)
{
std::stringstream ss;
ss << ind =" CComBSTR(ss.str().c_str());">GetIDsOfNames(IID_NULL, &ind, 1, LOCALE_USER_DEFAULT, &id));
_C(disp->Invoke(id, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &res, NULL, NULL));

VARIANT val1;
VariantInit(&val1);

_C(VariantChangeType(&val1, &res, 0, VT_I4));
total += val1.lVal;
}

*Sum = total;

return S_OK;
}

What it does is simply the following:

  1. It is checked if an argument is some object (hopefully, a JScript array)
  2. IDispatch reference of an array is retrieved from the variant.
  3. The property length is obtained from the array's object as it is for any automation object. We receive some variant value, so we need to convert it to integer.
  4. For each element of an array its DISPID is queried and its value received, converted to integer, and summed up.
  5. It returns the sum of array's values.
It can be called from JScript as following:

var arr = new Array();
arr[0] = 10;
arr[1] = 15;
arr[2] = 2;

var obj = new ActiveXObject("ProgID.OfClass");
alert(obj.JSArrSum(arr));
2011. 1. 3. 23:20

New ASP.NET Charting Control

MS에서 제공하는 Chart 컨트롤입니다. 

Microsoft recently released a cool new ASP.NET server control - <asp:chart /> - that can be used for free with ASP.NET 3.5 to enable rich browser-based charting scenarios:





하기에는 실시간으로 차트를 업데이트하는 예제의 링크입니다.

protected void Timer1_Tick(object sender, EventArgs e)
{
   Series s = Chart1.Series["Series1"];
   //Determine the next X value
   double nextX = 1;

   if (s.Points.Count > 0)
   {
      nextX = s.Points[s.Points.Count-1].XValue+1;
   }

   //Add a new value to the Series
   Random rnd = new Random();
   s.Points.AddXY(nextX,(rnd.NextDouble() * 10)-5);

   //Remove Points on the left side after 100
   while (s.Points.Count > 100)
   {
      s.Points.RemoveAt(0);
   }

   //Set the Minimum and Maximum values for the X Axis on the Chart
   double min = s.Points[0].XValue;
   Chart1.ChartAreas[0].AxisX.Minimum = min;
   Chart1.ChartAreas[0].AxisX.Maximum = min + 100;
}



2011. 1. 3. 23:11

HTTP Request ActiveX Object

HTTP Request 를 요청하기 위해서, 

"Msxml2.XMLHTTP" 또는 "Microsoft.XMLHTTP" 와 같이 MS XML 객체를 사용하다가 
원인을 알 수 없는 문제가 발생하였습니다. 

웹페이지에 대한 요청이 아니라 특정 application 이 직접 구현한 http 서비스에 요청을 하는 것인데 위 객체로는 정상적으로 동작하지 않았습니다. 제 생각에 이 특정 app 이 제대로 구현했을거라 생각되지도 않습니다. 더군다나 process 간 통신을 위해서 http 프로토콜을 사용하는 건 처음 보았답니다. 단순히 프로세스간 통신에는 파이프, 메시지, TCP, 공유 메모리, 그리고 COM 등의 다른 수단이 많음에도 말이죠. 

어쨋든 문제는 고쳐야 했고, 혹시나 해서 WinHttpRequest 객체로 바꾸어 봤는데 제대로 동작하더군요. 그래서 혹시나 저와 같이 프로세스간 통신에 http request 를 요청해야 하고, 그걸 스크립트 상에서 호출할 수 있어야 한다면 XMLHTTP 외에도 winHttpRequest 가 있다는 것도 유념하시길 바랍니다.

사용법은 XMLHttp 와 거의 유사합니다.

function getText(strURL)
{
    var strResult;
    
    try
    {
        // Create the WinHTTPRequest ActiveX Object.
        var WinHttpReq = new ActiveXObject(
                                  "WinHttp.WinHttpRequest.5.1");
        
        //  Create an HTTP request.
        var temp = WinHttpReq.Open("GET", strURL, false);

        //  Send the HTTP request.
        WinHttpReq.Send();
        
        //  Retrieve the response text.
        strResult = WinHttpReq.ResponseText;
    }
    catch (objError)
    {
        strResult = objError + "\n"
        strResult += "WinHTTP returned error: " + 
            (objError.number & 0xFFFF).toString() + "\n\n";
        strResult += objError.description;
    }
    
    //  Return the response text.
    return strResult;
}

WScript.Echo(getText("http://www.microsoft.com/default.htm"));

아 그리고 호출하기 전 SetTimeouts 함수를 호출하는 것도 유념해야 합니다.

The following scripting example shows how to set all WinHTTP time-outs to 30 seconds, open an HTTP connection, and send an HTTP request.

// Instantiate a WinHttpRequest object.
var WinHttpReq = new ActiveXObject("WinHttp.WinHttpRequest.5.1");

// Set time-outs. If time-outs are set, they must 
// be set before open.
WinHttpReq.SetTimeouts(30000, 30000, 30000, 30000);

// Initialize an HTTP request.  
WinHttpReq.Open("GET", "http://www.microsoft.com", false);

// Send the HTTP request.
WinHttpReq.Send();


참고: 

2011. 1. 3. 22:50

C#을 이용한 Array 또는 Enumerator 간단 구현

MFC 나 ATL 로는 꽤 까다로운 작업을 C#으로는 간단하게 구현할 수 있습니다.

Array array = Array.CreateInstance(typeof(CustomType), arrayCount);

for(int i=0; i<arrayCount; i++)
{
array.setValue(new CustomType(), i);
}

이렇게 Array 를 반환하면 스크립트 디버거 변수창에서 [0], [1], ... 이런식으로 배열 인자로 
내용을 참고 할 수 있습니다.

Enumerator 로 반환하고자 하는 경우는, 간단히 ArrayList 에 내용을 채워 반환하면 됩니다.

자세한 예제는 생략하겠습니다.
2011. 1. 3. 22:44

Article 5. The Safe OLE Way of Handling Arrays

Source: http://www.ecs.syr.edu/faculty/fawcett/handouts/cse775/presentations/BruceMcKinneyPapers/safeArrays.htm

Bruce McKinney

April 18, 1996

Although Microsoft® Visual C++® supports arrays intrinsically, it does so with no index protection, no size limit, no initialization--just a pointer to random-value memory. Even C++ programmers are reluctant to use raw arrays. Many of them write protected wrapper classes with names like Array or Vector. You can make such classes look and act like an array--but one that is protected by armor.

If you're going to make arrays available across process, machine, and operating system boundaries, your clients will expect more protection than a raw C++ array can provide. The OLE way of doing arrays (which is exactly the same as the Visual Basic® way) is through a protected data structure called a SAFEARRAY.

What Is a SAFEARRAY?

Because the OLE header files are implemented for both C and C++ (although using C is cumbersome, so this article will examine only the C++ case), they provide the protected standard array as a SAFEARRAY structure with a group of system functions that work on it.

The SAFEARRAY Structure

When converted to C++ and trimmed of excess typedefs and conditionals, the SAFEARRAY structure looks something like this:

struct SAFEARRAY {
    WORD cDims;
    WORD fFeatures;
    DWORD cbElements;
    DWORD cLocks;
    void * pvData;
    SAFEARRAYBOUND rgsabound[1];
};

  • The cDims field contains the number of dimensions of the array.
  • The fFeatures field is a bitfield indicating attributes of a particular array. (More on that later.)
  • The cbElements field defines the size of each element in the array.
  • The cLocks field is a reference count that indicates how many times the array has been locked. When there is no lock, you're not supposed to access the array data, which is located in pvData.
The last field is an array of boundary structures. By default, there's only one of these, but if you define multiple dimensions, the appropriate system function will reallocate the array to give you as many array elements as you need. The dimension array is the last member of the array so that it can expand. A SAFEARRAYBOUND structure looks like this:

struct SAFEARRAYBOUND {
    DWORD cElements;
    LONG lLbound;
};

The cElements field has the number of elements in the dimension, and the lLBound field has the lower boundary. In theory, you could define a range either by giving the first and last element, or by giving the first element and the number of elements. OLE chose the second format, but we'll fix that shortly.

The SAFEARRAY System Functions

Many of the system functions tend to work together in groups, so we'll talk about related functions in logical order. My descriptions of these functions are sometimes different from (and sometimes more complete than) the descriptions in OLE documentation. Because I personally tested them to determine some behavior that was sparsely documented, I am confident that my descriptions are valid and will work for you.

As long as you're working with single-dimension arrays, the SAFEARRAY functions are simple and straightforward. Things get more complicated with multi-dimensional arrays, partly because Visual Basic and Visual C++ have a different ideas of how to arrange the data in different dimensions. Also C++ wants to access zero-based arrays, but the SAFEARRAY type can have index boundaries based on any signed number.

The TestSA function in Test.Cpp gives the SafeArray system functions a workout. The examples for the functions described below are taken from this function, and the event handler for the SAFEARRAY button in the Cpp4VB sample program calls this function. I'm not going to go through the code in detail, but I will say that the more you study the raw functions, the more you'll appreciate the SafeArray class shown later.

One note on terminology: OLE documentation calls a pointer to an allocated SAFEARRAY structure an array descriptor. Note that an array descriptor isn't necessarily the same as a pointer to a SAFEARRAY structure. The structure has space for only one dimension (SAFEARRAYBOUND structure). A descriptor is expanded to provide additional memory for each dimension.

SAFEARRAY * SafeArrayCreate(VARTYPE vt, UINT cDims, SAFEARRAYBOUND * aDims);
HRESULT SafeArrayDestroy(SAFEARRAY * psa);

You create an array by calling SafeArrayCreate, passing it the type of the array in the vt parameter, the number of dimensions in the cDims parameter, and the size of each dimension in the adims parameter (an array of SAFEARRYBOUND structures). SafeArrayCreate creates a new array, allocates and initializes the data for the array, and returns a pointer to the SAFEARRAY structure. When you're done with the array, call SafeArrayDestroy. The advantage of these functions is that they're simple. The disadvantage is that they can only handle the OLE variant subtypes (excluding VT_ARRAY, VT_BYREF, VT_EMPTY, or VT_NULL). That's not really a disadvantage when you're dealing with Visual Basic or most other OLE clients.

When you destroy an array of BSTRs, VARIANTs, or objects with SafeArrayDestroy, BSTRs and VARIANTs are freed and objects are released.

Example:

// Create a new 1-D array of Integers.
SAFEARRAY * psaiNew; 
SAFEARRAYBOUND aDim[1]; 
aDim[0].lLbound = 1; 
aDim[0].cElements = 8; 
// Equivalent to: Dim aiNew(1 To 8) As Integer.
psaiNew = SafeArrayCreate(VT_I2, 1, aDim);
if (psaiNew == NULL) throw ERROR_NOT_ENOUGH_MEMORY;
.
. // Use array.
.
if (hres = SafeArrayDestroy(psaiNew)) throw hres;

HRESULT SafeArrayAllocDescriptor(UINT cDims, SAFEARRAY ** ppsaOut);
HRESULT SafeArrayAllocData(SAFEARRAY * psa);
HRESULT SafeArrayDestroyData(SAFEARRAY * psa);
HRESULT SafeArrayDestroyDescriptor(SAFEARRAY * psa);

These functions provide more complicated and flexible alternative to SafeArrayCreate and SafeArrayDestroy. You can put non-OLE types into the array, but you must manage the data yourself. In this series of articles, we have no reason to put nonstandard data types into arrays. Visual Basic wouldn't know what to do with them anyway.

HRESULT SafeArrayGetElement(SAFEARRAY * psa, long * aiIndex, void * pvElem);
HRESULT SafeArrayPutElement(SAFEARRAY * psa, long * aiIndex, void * pvElem);

These functions insert or extract a single array element. You pass one of these functions an array pointer and an array of indexes for the element you want to access. It returns a pointer to a single element through the pvElem parameter. You also need to know the number of dimensions and supply an index array of the right size. The rightmost (least significant) dimension should be aiIndex[0] and the leftmost dimension should be aiIndex[psa->cDims-1]. These functions automatically call SafeArrayLock andSafeArrayUnlock before and after accessing the element. If the data element is a BSTR, VARIANT, or object, it is copied correctly with the appropriate reference counting or allocation. During an assignment, if the existing element is a BSTR, VARIANT, or object, it is cleared correctly, with the appropriate release or free before the new element is inserted. You can have multiple locks on an array, so it's OK to use these functions while the array is locked by other operations.

Example:

// Modify 2-D array with SafeArrayGetElement and SafeArrayGetElement.
long ai[2];
Integer iVal;
xMin = aDims[0].lLbound;
xMax = xMin + (int)aDims[0].cElements - 1;
yMin = aDims[1].lLbound; 
yMax = yMin + (int)aDims[1].cElements - 1;
for (x = xMin; x <= xMax; x++) {
    ai[0] = x;
    for (y = yMin; y <= yMax; y++) {
        ai[1] = y;
        if (hres = SafeArrayGetElement(psaiInOut, ai, &iVal)) throw hres;
        // Equivalent to: aiInOut(x, y) = aiInOut(x, y) + 1.
        iVal++;
        if (hres = SafeArrayPutElement(psaiInOut, ai, &iVal)) throw hres;
    }
}

HRESULT SafeArrayLock(SAFEARRAY * psa);
HRESULT SafeArrayUnlock(SAFEARRAY * psa);

These functions increment or decrement the lock count of an array. The data becomes accessible through the pvData field of the array descriptor. The pointer in the array descriptor is valid until SafeArrayUnlock is called. Note that the pvData field, like all C++ arrays, is zero-indexed. If you need to keep track of the Basic index, initialize it from the lLbound field of the SAFEARRAYBOUND structure.

When processing data in a loop, it is more efficient to lock the array, process the data, and then unlock it, rather than making multiple calls to SafeArrayGetElement andSafeArrayPutElement. You can nest equal pairs of calls to SafeArrayLock and SafeArrayUnlock, so it's possible to lock and use an array while another operation also has a lock on the array. An array can't be deleted while it is locked.

Example:

// Initialize Integer array to squares of index.
if (hres = SafeArrayLock(psaiNew)) throw hres;
int iCur = aDim[0].lLbound;
// Keep separate C++ index (i) and Basic index (iCur).
for (i = 0; i < (int)aDim[0].cElements; i++, iCur++) {
    // Equivalent to: ai(iCur) = iCur * iCur.
    ((Integer*)psaiNew->pvData)[i] = iCur * iCur;
}
if (hres = SafeArrayUnlock(psaiNew)) throw hres;

The example above illustrates accessing a simple 1-D array. As a bonus, here's an example of accessing a 2-D array without any help from SafeArrayPtrOfIndex. I had to consult a pack of C++ language lawyers (special credit to Paul Johns) for help untangling a type cast that looks sort of like a spilled can of night crawlers.

// Set up dimension array and pointer to receive value.
if (hres = SafeArrayLock(psaiInOut)) throw hres; 
Integer (*aiInOut)[4] = (Integer(*)[4])psaiInOut->pvData;
for (x = 0; x < (int)aDims[0].cElements; x++) {
    for (y = 0; y < (int)aDims[1].cElements; y++) {
        // Equivalent to: aiInOut(x, y) = aiInOut(x, y) + 1.
        // Switch x and y order for Visual Basic storage order.
        aiInOut[y][x]++;
    }
}
if (hres = SafeArrayUnlock(psaiInOut)) throw hres;

HRESULT SafeArrayPtrOfIndex(SAFEARRAY * psa, long * aiIndex, void ** ppv);

This function returns a pointer to an array element. You pass it an array of index values that identify an element of the array; it returns a pointer to the element. The array should be locked before SafeArrayPtrOfIndex is called. Use this function with multi-dimension arrays when using SafeArrayLock. For single-dimension arrays, it's usually easier to just index into the array directly without this function.

Example:

// Lock 2-D array and modify.
xMin = aDims[0].lLbound; 
xMax = xMin + (int)aDims[0].cElements - 1;
yMin = aDims[1].lLbound; 
yMax = yMin + (int)aDims[1].cElements - 1;
// Set up dimension array and pointer to receive value.
Integer * piInOut;
if (hres = SafeArrayLock(psaiInOut)) throw hres; 
for (x = xMin; x <= xMax; x++) {
    ai[0] = x;
    for (y = yMin; y <= yMax; y++) {
        ai[1] = y;
        hres = SafeArrayPtrOfIndex(psaiInOut, ai, (void **)&piInOut);
        if (hres) throw hres;
        // Equivalent to: aiInOut(x, y) = aiInOut(x, y) + 1.
        (*piInOut)++;
    }
}
if (hres = SafeArrayUnlock(psaiInOut)) throw hres;

HRESULT SafeArrayAccessData (SAFEARRAY * psa, void ** ppvData);
HRESULT SafeArrayUnaccessData(SAFEARRAY * psa);

You pass SafeArrayAccessData a SAFEARRAY pointer and a variable to receive the address of the array data; it locks the array and returns a pointer to the data. When you're done, you call SafeArrayUnaccessData. This is the verbose equivalent of locking the data and using the pvData member out of the SAFEARRAY structure. It provides no way to calculate the index of multi-dimension arrays. I can't think of any reason to use these functions, so if you do, you're on your own.

HRESULT SafeArrayCopy(SAFEARRAY * psaIn, SAFEARRAY ** ppsaOut);

This function creates a copy of an existing safe array: You pass it the descriptor of the array to copy and the address of a SAFEARRAY pointer that will receive the copy; it copies the source data to the destination. If the source array contains BSTR or VARIANT types, SafeArrayCopy calls the appropriate system functions to create the copies. If the source array contains object references, SafeArrayCopy increments their reference counts. You end up with two identical copies of the array.

Example:

// Copy from psaiNew to psaiRet.
SAFEARRAY * psaiRet; 
if (hres = SafeArrayCopy(psaiNew, &psaiRet)) throw hres;

UINT SafeArrayGetDim(SAFEARRAY * psa);
UINT SafeArrayGetElemsize(SAFEARRAY * psa);

These functions return the number of dimensions in the array or the size in bytes of an element. They are equivalent to getting the corresponding elements out of the descriptor.

Example:

long cDim = SafeArrayGetDim(psaiInOut); 
long cbElem = SafeArrayGetElemsize(psaiInOut); 

HRESULT SafeArrayGetLBound(SAFEARRAY * psa, UINT cDim, long * piLo);
HRESULT SafeArrayGetUBound(SAFEARRAY * psa, UINT cDim, long * piUp);

These functions return the lower or upper boundary for any dimension of a safe array.

Example:

SAFEARRAYBOUND * aDims = new SAFEARRAYBOUND[cDim];
long iT;
for (i = 0; i < cDim; i++) {
    hres = SafeArrayGetLBound(psaiInOut, i + 1, &aDims[i].lLbound);
    if (hres) throw hres; 
    if (hres = SafeArrayGetUBound(psaiInOut, i + 1, &iT)) throw hres;
    // Calculate elements from upper and lower boundaries.
    aDims[i].cElements = iT - aDims[i].lLbound + 1;
}

HRESULT SafeArrayRedim(SAFEARRAY * psa, SAFEARRAYBOUND * pdimNew);

This function changes the least significant (rightmost) bound of a safe array. You pass an array pointer to SafeArrayRedim and a pointer to a SAFEARRAYBOUND variable containing the desired dimensions. If you reduce the size of the array, SafeArrayRedim deallocates the array elements outside the new array boundary. If you increase the size,SafeArrayRedim allocates and initializes the new array elements. The data is preserved for elements that exist in both the old and the new array. To redimension an array passed from Visual Basic, the array must be a non-static array:

' Use SafeArrayRedim on this one.
Dim aiModify () As Integer
ReDim Preserve aiModify(1 To 8, 1 To 8) As Integer
' Don't use SafeArrayRedim.
Dim aiFixed(1 To 8, 1 To 8) As Integer

You can identify a fixed-length array passed from Visual Basic by the fFeatures field of the SAFEARRAY structure. Basic sized arrays declared with DimPrivate, or Public will have the FADF_STATIC and FADF_FIXEDSIZE flags. Arrays sized with Basic's ReDim statement (and thus usable with SafeArrayRedim) will not have these flags.

Example:

// Double the size of the last dimension.
i = cDim - 1;
aDims[i].cElements *= 2;
if (hres = SafeArrayRedim(psaiInOut, &aDims[i])) throw hres;

The SafeArray Class

You're probably not used to thinking of an array as a type--it's more of a container for other types. But in C++ just about anything can be a type. Until recently, if you wanted to define one type that held objects of another type, you had to decide at design time what type you wanted to contain. You could design an ArrayLong type, but then you'd have to copy all your code and do a lot of searching and replacing to add an ArrayString type.

C++ templates now let you define an array type that can contain any kind of OLE type. You can declare an array of strings like this:

SafeArray<BSTR, VT_BSTR> as = Dim(1, 8); // Dim as(1 To 8) As String

You specify by what you put in the angle brackets exactly what type you want to be contained in the array. It gets a little tiresome, not to mention redundant, to specify both the type and the OLE type constant for every array, but typedefs make it easy to define the standard OLE types:

typedef SafeArray<BSTR, VT_BSTR>            ArrayString;

Now you can define the string more naturally.

ArrayString as = Dim(1, 8);             // Dim as(1 To 8) As String

The other OLE types have Basic-style names: ArrayByte, ArrayInteger, ArrayLong, ArraySingle, ArrayDouble, ArrayVariant, ArrayCurrency, ArrayDate, ArrayBoolean, and ArrayObject. The trick is that we don't have to define a separate class for each type. There's just one class with 11 predefined variations, and the ability to define more, although the predefined ones are all you need for a Visual Basic client.

You can see some of these array definitions in action in the TestSafeArray function in Test.Cpp. This function is tested by the event handler of the SafeArray button in the Cpp4VB sample program.

But before we look at things you might want to do with SafeArray types, what the heck is that Dim object being assigned to the array?

The Dim Type

The SAFEARRAYBOUND type is the official way of specifying a dimension for a SAFEARRAY, but it's not my idea of how a dimension ought to be specified. It certainly doesn't look like a Visual Basic dimension, so I wrote a simpler, more friendly class called DimDim is inherited from SAFEARRAYBOUND, so it has the same data members, but it also has friendly constructors and methods that make it easier to use. All methods of the Dim class are inline, so there's no penalty for using it.

Here's the entire class:

class Dim : public SAFEARRAYBOUND
{
public:
    Dim(const long iLo, const long iHi)
    { cElements = abs(iHi - iLo) + 1; lLbound = iLo; }
    Dim(const long c)
    { cElements = c; lLbound = 0; }
    const Dim & operator=(const Dim & dim) 
    { cElements = dim.cElements; lLbound = dim.lLbound; return *this; }
    const Dim & operator=(const long c) 
    { cElements = c; lLbound = 0; return *this; }
    ~Dim() {}
    long Elements() { return cElements; }
    long LBound() { return lLbound; }
    long UBound() { return lLbound + cElements - 1; }
};

Notice first that the Dim object is inherited publicly from SAFEARRAYBOUND. This means that the Dim is a SAFEARRAYBOUND and you can use its data members--lLbound andcElements. These have to be public so that you can pass a Dim to system functions such as SafeArrayCreate. You can use a Dim the hard way:

Dim dim;
dim.lLBound = 4;
dim.cElements = 9;

but why bother, when you can do the following:

Dim dim(4, 12);

That's more like Visual Basic. But you can also just specify the number of elements and assume zero as the starting point:

Dim dim2(8);            // Same as Dim dim2(0, 7)

Normally, you don't need a separate variable for Dim. Just create a temporary one in the assignment statement:

ArrayString as = Dim(1, 8);             // Dim as(1 To 8) As String

So why didn't I just skip the Dim class and give arrays a constructor taking two arguments? Then you could define the array like this:

ArrayString as(1, 8);                   // Dim as(1 To 8) As String

True, this would have been easy, and you should feel free to add it if you want. But what happens with multidimensional arrays? Which makes more sense? This:

ArrayString2 as(Dim(1, 8), Dim(3, 9)); // Dim as(1 To 8, 3 To 9) As String

Or this:

ArrayString2 as(1, 8, 3, 9);           // Dim as(1 To 8, 3 To 9) As String

It's arguable, but I find the separate Dim class easier to read. It's also more efficient internally.

Notice that I show a separate class, ArrayString2, for two-dimensional arrays. I tried to write a single SafeArray class that could handle multiple dimensions, but function overloading issues made it hard to implement cleanly. In this article, we'll only be using one-dimensional arrays, but you should have no problem cloning the SafeArray class as SafeArray2,SafeArray3, and so on. You won't be able to redimension a one-dimensional class to a two-dimensional class at run time.

A SafeArray Warm-up

The TestSafeArray function seems simple enough. It takes two parameters. One is an input array that the function will modify; the other is an empty output array that the function will create. The function would like to return another output array through the return value, but Visual Basic doesn't support direct return of arrays. They can only be returned in Variants (check out Visual Basic's GetAllSettings function), so that's what we'll do.

The function prototype looks simple enough:

Variant DLLAPI TestSafeArray(ArrayInteger & aiInOut, ArrayString & asOut);

Of course what you're really doing behind the typedefs is using templates:

Variant DLLAPI TestSafeArray(SafeArray<short, VT_I2> & aiInOut, 
                             SafeArray<BSTR, VT_BSTR> & asOut);

SafeArray parameters must always be passed as pointers, but we use references to make the array objects look normal. You'll see how clean this looks in the implementation shortly.

Unfortunately, MKTYPLIB doesn't know anything about references, much less about templates. Here's how you're supposed to define the SAFEARRAY structures in a type library:

Variant WINAPI TestSafeArray([in, out] SAFEARRAY(short) * ai, 
                             [out] SAFEARRAY(BSTR) * as);

You can see what's going on, but it looks almost as ugly as the template prototype. We can do better:

Variant WINAPI TestSafeArray([in, out] ArrayInteger ai, 
                             [out] ArrayString as);

This simple definition is made possible by #define statements in the OLETYPE.ODL standard include file. For example:

#define ArrayLong     SAFEARRAY(long) *

OLETYPE.ODL should be included in any type libraries that require SafeArrays. It includes WINTYPE.ODL, so you don't need to include both. You should put this file in a standard location known to your compiler (such as \MSDEV\INCLUDE for Microsoft Visual C++).

A SafeArray Workout

What would you like to do with an array? Well, if you had an input array you might want to examine elements. If the array were an in/out array, you might want to modify the contents. Here's one way of doing that:

iMid = aiInOut.LBound() + (aiInOut.Elements() / 2);
// Get middle value of array.
iVal = aiInOut.Get(iMid);
// Double it
iVal *= 2; 
// Put modified version back.
aiInOut.Set(iVal, iMid);

There's nothing really wrong with this, and it may be the most efficient way to access a single array element. But how often do you access a single element? Every time you call theGet or Set method, the class in turn calls the SafeArrayGet or SafeArrayPut API function, which in turn calls the SafeArrayLock or SafeArrayUnlock API function. You don't need the extra function calls or the hassle.

SafeArray Looping in C++ or Basic Style

Here's how you modify the entire array in a loop:

// Square each value, C++ style.
aiInOut.Lock();
for (i = 0; i < aiInOut.Elements(); i++) {
    aiInOut[i] *= aiInOut[i];
}
aiInOut.Unlock();

You have to lock the array before accessing it, and unlock it when you're done. There's something strange here. This looks like a C++ array, starting at 0 and continuing as long as the index is less than the total number of elements. But what about the lower and upper boundaries the SafeArray is supposed to provide? Shouldn't you be looping from LBound() to UBound()? Well, it's a matter of taste. You can indeed do it that way if you prefer:

// Divide each by two, Visual Basic style.
aiInOut.Lock();
for (i = aiInOut.LBound(); i <= aiInOut.UBound(); i++) {
    aiInOut(i) /= 2;
}
aiInOut.Unlock();

Notice that in this case you index with parentheses instead of brackets in the Visual Basic style. It makes sense, but how can C++ have Visual Basic indexing? Through the magic of operator overloading. The SafeArray class overloads the subscript operator (square brackets) for zero-based indexing. It overloads the function operator (parentheses) for boundary-based indexing. It may not be what C++ designer Bjarne Stroustrup had in mind, but it works.

SafeArray Copying and Resizing

You can copy an array in two ways--through initialization:

// Copy an array.
ArrayInteger aiCopy = aiInOut;

Or through assignment:

aiCopy = aiInOut;

Either way you end up with a completely new array containing the same data. It's a deep copy--if the arrays contain BSTRs, each will have its own separate but identical string for each element. SafeArray doesn't support shallow copies in which two array pointers point to the same array data. SafeArray does allow you to redimension arrays--provided that they're sizable:

// Redimension to throw away last element.
if (aiInOut.IsSizable()) {
    aiInOut.ReDim(Dim(aiInOut.LBound(), aiInOut.UBound() - 1)); 
}

For this to work on an array passed from Visual Basic, the array must be created with ReDim rather than Dim.

' Use SafeArrayRedim on this one.
Dim aiModify() As Integer
Redim Preserve aiModify(1 To 8) As Integer

Returning SafeArrays

Often you'll want to create an array in a function and return it to Visual Basic through an out parameter. Here's an example:

// Create array of strings.
ArrayString as = Dim(4, 9);
String s = _W("Fan");
for (i = as.LBound(); i <= as.UBound(); i++) {
    s[0] = L'F' + (WCHAR)i;
    as(i) = s;
}
// Return it through out parameter.
asOut = as;

And here's how to return through a Variant return value:

// Create array of doubles.
ArrayDouble adbl = Dim(-5, 5);
for (i = adbl.LBound(); i <= adbl.UBound(); i++) {
    adbl(i) = i * 3.1416;
}
// Return through Variant return value.
Variant vRet = (Variant)adbl;
return vRet;

SafeArray has a Variant type conversion operator that makes the assignment to the Variant return value possible.

A ParamArray Workout

Visual Basic supports a passing a varying number of arguments to a procedure. To the caller of the function, it may look like you're calling a list of unrelated arguments, but the Visual Basic implementer knows that there is actually only one parameter--the last one--called a ParamArray. The C++ implementer knows that this final parameter is actually a variable-length array of Variants in which some elements may be missing.

The Visual Basic parser reads the caller's argument list and creates a Variant array of the same size. It fills the array with the arguments, setting any missing arguments to an error value (DISP_E_PARAMNOTFOUND).

The calls might looks like this on the Basic side:

Debug.Print AddEmUp(7, 9.4, "4")    ' 40.4
Debug.Print AddEmUp(, 9.4, , "24")  ' 33.4
Debug.Print AddEmUp(7, "4")         ' 31

Wait a minute! How can you add strings to numbers? That's what Variants are for. The C++ side of the code looks like this:

double DLLAPI AddEmUp(ParamArray & avParams)
{
  try {
    double dblRet = 0;
    // Loop through the array, retrieving parameters.
    avParams.Lock();
    for (long i = 0; i < avParams.Elements(); i++) {
        // Ignore missing ones.
        if (!avParams[i].IsMissing()) {
            dblRet += (double)avParams[i];
        }
    }
    avParams.Unlock();
    return dblRet;
  } catch(Long e) {
    ErrorHandler(e);
    return 0.0;
  } 
}

The ParamArray type is actually another typedef, which happens to be exactly the same as ArrayVariant. There's a matching ODL define in OLETYPE.ODL. You have to specify thevararg attribute in the type library file so that Visual Basic will know that it should treat the last parameter as a ParamArray:

[
entry("AddEmUp"),
helpstring("Tests ParamArrays"),
vararg
]
double WINAPI AddEmUp([in] ParamArray params);

The event handler for the ParamArray button in the Cpp4VB sample program tests the AddEmUp function.

SafeArray Implementation

As with the String and Variant classes, you don't have to understand how SafeArray is implemented to use it. But I strongly recommend taking a look at the SafeArrayimplementation because it's not just another class. Implementing a class as a C++ template requires a whole different mind set. Things you always thought were cast in concrete turn out to be sinking in wet cement.

Template Compilation and Overloading

The first thing you need to discard is the quaint notion that you should put your implementation in a .Cpp file and your declarations in an .H file. There--I've already saved some of you many hours of wasted effort. I wish someone had told me that right up front.

What you need to know about template classes is that when you define one, you are actually defining as many classes as you can think up to replace the template placeholder. If the compiler treated this as a traditional C++ module, your hard disk would be filled with object files on every interminable compile. So the compiler only compiles the template code that you need.

For example, if your program uses the SafeArray template class for arrays of Variant, Long, and Single, those classes will be compiled as part of the module that uses them, but the Double, Currency, and String versions won't be compiled. So there really isn't such thing as a separate SafeArray object module, just a template definition. The module using the template needs to see both declarations and implementation, and therefore most C++ compilers require that everything must go in the .H file. There isn't any .Cpp file. Some people have worked around this by putting the implementation in a .Cpp file and then including it at the end of the .H file.

It's probably better, though, to change old habits than to fit old techniques into new idioms. Templates require a new way of thinking. For example, you might have a bug that only shows up in certain instantiations of a template. A function overload that works fine with an array of Doubles might fail wretchedly with an array of Longs. Consider this:

void Do(Long i, T & t);
void Do(T & t, Long i);

When you create an array of Doubles, you get this:

void Do(Long i, Double & t);
void Do(Double & t, long i);

No problem. The compiler knows the different between Do(5.6, 5L) and Do(5L, 5.6). But if you create an array of Longs you get:

void Do(Long i, Long & t);
void Do(Long & t, Long i);

These are the same function and you'll get a compile-time error--but not until you try instantiating the Long version of the class. This is why (in short) I'm recommending that you create separate SafeArray2 and SafeArray3 classes for 2-D and 3-D arrays. If you think you can enhance the SafeArray class to handle multidimensional arrays without hitting this problem, be my guest.

SafeArray Implementation

I'm not going to go very deep into the implementation of the SafeArray class, but I will show you a few bits and pieces to give you the feel. Let's start with the class itself:

template<class T, VARTYPE vt> 
class SafeArray 
{
public:
    // Constructors
    SafeArray();
    SafeArray(SAFEARRAY * psaSrc);
    SafeArray(Dim & dim);
    // Copy constructor
    SafeArray(const SafeArray & saSrc);
    // Destructor
    ~SafeArray(); 
    // Operator equal
    const SafeArray & operator=(const SafeArray & saSrc);
    // Indexing
    T & Get(long i);
    T & Set(T & t, long i);
    T & operator[](const long i);    // C++ style (0-indexed)
    T & operator()(const long i);    // Basic style (LBound-indexed)
.
.
.
private:
    SAFEARRAY * psa;
    void Destroy();
    Boolean IsConstructed();
    void Constructed(Boolean f);
};

It looks just the same as any other class except for the template<class T, VARTYPE vt> at the start. This says that will represent whatever type the array will be instantiated to contain, and vt will represent the Variant type constant. (The vt parameter is actually needed in only one place--calls to SafeArrayCreate in constructors--but in that one case there's no alternative.) You can see how the is used in the declarations of GetSet, and the subscript and function operators.

The SafeArray class has only one data member--psa, which is a pointer to a SAFEARRAY structure. When you pass SAFEARRAY structures directly, you pass a pointer to a pointer to a SAFEARRAY.

Variant DLLAPI TestSA(SAFEARRAY ** ppsaiInOut, SAFEARRAY ** ppsasOut);

When you use SafeArrays, you pass a reference (or pointer) to a SafeArray, which contains a pointer to a SAFEARRAY.

Variant DLLAPI TestSafeArray(ArrayInteger & aiInOut, ArrayString & asOut);

It comes to the same thing.

SafeArray Methods

Implementations of SafeArray methods aren't nearly as clean as their declarations. You can probably guess the implementation of constructors (which simply call SafeArrayCreate), so let's skip to something more interesting:

template<class T, VARTYPE vt> 
inline T & SafeArray<T,vt>::Set(T & t, long i)
{
    HRESULT hres = SafeArrayPutElement(psa, &i, (T *)&t);
    if (hres) throw hres;
    return t;
}
template<class T, VARTYPE vt> 
inline T & SafeArray<T,vt>::operator[](const long i)
{
    if (i < 0 || i > Elements() - 1) throw DISP_E_BADINDEX;
    return ((T*)psa->pvData)[i];
}
template<class T, VARTYPE vt> 
T & SafeArray<T,vt>::operator()(const long i)
{
    if (i < LBound() || i > UBound()) throw DISP_E_BADINDEX;
    return ((T*)psa->pvData)[i - LBound()];
}

There's a whole lot of angle brackets floating around here, and there's no way to avoid them.

The Set function illustrates how system functions are called from template members. They almost always require the psa data member. Parameters of T type should normally be reference parameters so that if they turn out to be large data structures, they'll be passed by reference instead of on the stack.. Notice the use of the T type (whatever it may be) to cast the third parameter (a void pointer) of SafeArrayPutElement to a usable type.

In operator functions for indexing, the indexed value is returned by reference, so that you can either assign to it or read from it. So the statement:

ai[2] = iVal;

actually means:

*(ai.operator[](2)) = iVal;

The assignment actually takes place after the function call. That's why it wouldn't help to put lock and unlock statements in the operator function even if you wanted to.

What's Next?

From here I could go on to one additional article about how to use objects passed by Visual Basic to your C++ DLLs. Maybe I'll get around to writing that article someday.

But the real next step is to write your own OLE objects for Visual Basic. Objects can have many additional features, such as collections, Basic-style error handling, property pages, hidden or visible windows, and classification IDs. The problem is, how do you build objects?

Until a few months ago, you didn't have much choice. You could use MFC to create objects that were sometimes a little slow and heavy. Alternately, you could slog through acres of OLE documentation, figuring out how to write objects in raw C++ or, if you were really a wizard (some might say a masochist), in plain C. Now you have several other options, one of which is described on the CD published with my book Hardcore Visual Basic. Other alternatives are either available now or will be soon, using C++, Delphi®, Visual Basic, and perhaps even Java™. But regardless of the tools you choose to create your objects, you'll be working with the same standard OLE types described in this series.

Introduction

Article 1. Stealing Code with Type Libraries

Article 2. Libraries Made Too Easy

Article 3. Strings the OLE Way

Article 4. The Ultimate Data Type

2011. 1. 3. 22:29

OLE Automation 에서 배열을 반환하는 방법

MFC 에서 사용하는 방법 (COleSafeArray 클래스 이용)


/*
 * CMfcarrayDoc::GetArray
 *
 * Purpose:
 *  Creates and returns an array.
 *
 * Return Value:
 *  Returns the array.
 *
 */
VARIANT CMfcarrayDoc::GetArray() 
{
    VARIANT vaResult;
    VariantInit(&vaResult);

    SAFEARRAYBOUND sabound[1];  
    SAFEARRAY FAR* psa = NULL;
    BSTR bstr = NULL;     
    HRESULT hr;
    
    // Create an array of size 100
    sabound[0].cElements = 100;
    sabound[0].lLbound = 0;    
    psa = SafeArrayCreate(VT_BSTR, 1, sabound);
    if (psa == NULL)
        AfxThrowOleDispatchException(1004, "Out of Memory");
    
    // Fill each array element with the same string, "contents"
    bstr = SysAllocString(OLESTR("contents")); 
    for (long i=0; i<100; i++)
    {
        hr = SafeArrayPutElement(psa, &i, bstr);
        if (FAILED(hr))
            goto error;
    }  
    SysFreeString(bstr);  
    V_VT(&vaResult) = VT_ARRAY | VT_BSTR;
    V_ARRAY(&vaResult) = psa;    
    return vaResult;
         
error:
    if (bstr) SysFreeString(bstr); 
    if (psa) SafeArrayDestroy(psa);
    AfxThrowOleDispatchException(1003, "Unexpected Failure in GetArray method");
    return vaResult;
}
파일을 다운로드한 후 다음 명령을 사용하여 샘플의 압축을 풀고 적절한 디렉터리 구조에 빌드.
MFCArray.exe - d



      VARIANT _stdcall retVariantArray(void) {
         COleSafeArray saRet;
         DWORD numElements[] = {10, 10}; // 10x10

         // Create the safe-array...
         saRet.Create(VT_R8, 2, numElements);

         // Initialize it with values...
         long index[2];
         for(index[0]=0; index[0]<10; index[0]++) {
            for(index[1]=0; index[1]<10; index[1]++) {
               double val = index[0] + index[1]*10;
               saRet.PutElement(index, &val);
            }
         }

         // Return the safe-array encapsulated in a VARIANT...
         return saRet.Detach();
      }
ATL 에서 SAFEARRAY 구조체 이용


   ...
   #include 
   ...
   long CStrArrayDoc::Sort(VARIANT FAR* vArray)
   {

      long i, j, min;
      BSTR bstrTemp;
      SAFEARRAY FAR* psa = NULL;
      BSTR HUGEP *pbstr;
      HRESULT hr;
      DWORD dwTimeStart;
      LONG cElements, lLBound, lUBound;

      USES_CONVERSION;

      // Type check VARIANT parameter. It should contain a BSTR array
      // passed by reference. The array must be passed by reference it is
      // an in-out-parameter.
      if (V_VT(vArray) != (VT_ARRAY | VT_BSTR))
         AfxThrowOleDispatchException(1001,
           "Type Mismatch in Parameter. Pass a string array by reference");
      psa = V_ARRAY(vArray);
      // Check dimensions of the array.
      if (SafeArrayGetDim(psa) != 1)
         AfxThrowOleDispatchException(1002,
           "Type Mismatch in Parameter. Pass a one-dimensional array");

      dwTimeStart = GetTickCount();

      // Get array bounds.
      hr = SafeArrayGetLBound(psa, 1, &lLBound);
      if (FAILED(hr))
          goto error;
      hr = SafeArrayGetUBound(psa, 1, &lUBound);
      if (FAILED(hr))
          goto error;

      // Get a pointer to the elements of the array.
      hr = SafeArrayAccessData(psa, (void HUGEP* FAR*)&pbstr);
      if (FAILED(hr))
         goto error;

      // Bubble sort.
      cElements = lUBound-lLBound+1;
      for (i = 0; i < cElements-1; i++)
      {
         min = i;
         for (j = i+1; j < cElements; j++)
         {
            // NULL is a valid value for a BSTR. This code treats a NULL
            // BSTR as less than other string values.
            if (pbstr[min] == NULL)
               continue;
            else if (pbstr[j] == NULL
               || wcscmp(pbstr[j], pbstr[min]) < 0)
               min = j;
         }

         //Swap array[min] and array[i].
         bstrTemp = pbstr[min];
         pbstr[min] = pbstr[i];
         pbstr[i] = bstrTemp;
      }

      hr = SafeArrayUnaccessData(psa);
      if (FAILED(hr))
         goto error;

      return GetTickCount()-dwTimeStart;

   error:

      AfxThrowOleDispatchException(1003,
        "Unexpected Failure in FastSort method");
      return 0;

   }
	
서버 테스트

   Private Sub Command1_Click()
      Dim o As Object
      Dim v As Variant
      ReDim v(50) As String
      Dim SortTime As Long

      Set o = CreateObject("StrArray.Document")

      upperbound = 1
      lowerbound = 100
      For n = 0 To 50
          v(n) = "Entry " & Int((upperbound-lowerbound+1)*Rnd+lowerbound)
      Next n

      SortTime = o.Sort(v)
      MsgBox ("Done")
   End Sub

기타 참고: 
2010. 12. 8. 03:37

Windows XP 서비스 팩 2 웹 보관 파일 (.mht) 파일 이미지 관련 오류

Windows XP 에서 .mht 파일을 볼 때 이미지가 빈 상자로 나타날 수 있습니다. 이에 대한 관련 정보와 핫픽스는 링크를 참고하세요. 


그런데 제 경험으로는 개발했던 애플리케이션(T-Set)에서 mht 변환 기능을 제공하였는데, 이 핫픽스를 설치하더라도 이미지를 보는데 문제가 있었습니다. 동일한 파일을 비스타 또는 Windows 7 에서 열면 정상적으로 이미지가 보였지만, XP 에서는 'X' 표시만 나왔습니다. 

이는 이미지 경로의 공백과 관련이 있다는 내용을 구글링을 통해 본적이 있습니다.

2010. 12. 5. 22:39

Word-wrap 방지하기

TD 에서 자동으로 Word-wrap 되어, 지정된 폭이 무시되는 현상이 말썽이었는데, 일단은 아래의 CSS 로 word-wrap 을 방지해서 그러한 부작용을 막아 보았습니다.

스타일 지정 전


        td.brief {
        		word-wrap: break-word;
        }

스타일 지정 후